@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.cjs +815 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +285 -24
- package/dist/index.d.ts +285 -24
- package/dist/index.js +807 -65
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/analytics-service.ts","../src/cube-registry.ts","../src/strategies/filter-normalizer.ts","../src/read-scope-sql.ts","../src/strategies/native-sql-strategy.ts","../src/strategies/objectql-strategy.ts","../src/dataset-compiler.ts","../src/dataset-executor.ts","../src/dimension-labels.ts","../src/preview-evaluator.ts","../src/plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n// Core service\nexport { AnalyticsService } from './analytics-service.js';\nexport type { AnalyticsServiceConfig } from './analytics-service.js';\n\n// Kernel plugin\nexport { AnalyticsServicePlugin } from './plugin.js';\nexport type { AnalyticsServicePluginOptions } from './plugin.js';\n\n// Cube registry\nexport { CubeRegistry } from './cube-registry.js';\n\n// Dataset semantic layer (ADR-0021)\nexport { compileDataset } from './dataset-compiler.js';\nexport type { CompiledDataset, DerivedMeasureSpec, RelationshipResolver, RelationshipTarget } from './dataset-compiler.js';\n\nexport { resolveDimensionLabels, pickDisplayField } from './dimension-labels.js';\nexport type { DimensionLabelDeps, FieldMetaLite } from './dimension-labels.js';\nexport {\n DatasetExecutor,\n evaluateDerivedMeasures,\n combineFilters,\n shiftRange,\n mergeByDimensions,\n} from './dataset-executor.js';\nexport type { DatasetSelection, CompareTo } from './dataset-executor.js';\nexport { compileScopedFilterToSql } from './read-scope-sql.js';\n\n// Strategies\nexport { NativeSQLStrategy } from './strategies/native-sql-strategy.js';\nexport { ObjectQLStrategy } from './strategies/objectql-strategy.js';\nexport type { AnalyticsStrategy, StrategyContext, DriverCapabilities } from './strategies/types.js';\n\n// Note: InMemoryStrategy is exported from @objectstack/driver-memory\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IAnalyticsService,\n AnalyticsQuery,\n AnalyticsResult,\n CubeMeta,\n DatasetSelection,\n} from '@objectstack/spec/contracts';\nimport type { Cube, FilterCondition } from '@objectstack/spec/data';\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\nimport type { Dataset } from '@objectstack/spec/ui';\nimport type { Logger } from '@objectstack/spec/contracts';\nimport { createLogger, bucketKeyToCalendarRange, zonedDateStartToUtcMs } from '@objectstack/core';\nimport { CubeRegistry } from './cube-registry.js';\nimport type { AnalyticsStrategy, DriverCapabilities, StrategyContext } from './strategies/types.js';\nimport { NativeSQLStrategy } from './strategies/native-sql-strategy.js';\nimport { ObjectQLStrategy } from './strategies/objectql-strategy.js';\nimport { compileDataset, type CompiledDataset, type RelationshipResolver } from './dataset-compiler.js';\nimport { DatasetExecutor } from './dataset-executor.js';\nimport { resolveDimensionLabels, type DimensionLabelDeps } from './dimension-labels.js';\nimport { evaluateAnalyticsQueryOverRows } from './preview-evaluator.js';\n\n/**\n * Analytics result augmented with drill-through metadata (ADR-0021 D2; see\n * queryDataset). Carried alongside `rows` so the host can drill a clicked bucket\n * back to the underlying records without the renderer knowing field mappings.\n */\ntype AnalyticsResultWithDrill = AnalyticsResult & {\n /** The dataset's base object — the host drills into its records. */\n object?: string;\n /** Selected drillable dimension NAME → underlying object FIELD name. */\n dimensionFields?: Record<string, string>;\n /**\n * RAW grouped values per row, aligned to `rows` by index — each a map of\n * drillable dimension NAME → stored value (BEFORE label resolution rewrote\n * `rows[i][dim]` to the display label). The exact-match drill filter is built\n * from these, never from the display labels.\n */\n drillRawRows?: Array<Record<string, unknown>>;\n /**\n * RAW grouped values for the totals/subtotal rows (#3214), the totals-side\n * companion to `drillRawRows`: `drillRawTotals[i]` aligns to `result.totals[i]`\n * and `drillRawTotals[i][j]` to `result.totals[i].rows[j]`. Each map holds that\n * grouping's DRILLABLE dimension NAME → stored value, snapshotted in the SAME\n * pre-label-resolution pass (the totals loop below overwrites a subtotal row's\n * dimension value with its display label just like the data rows). Restricted\n * to the drillable dims present in the grouping, so the grand-total grouping\n * (`[]`) contributes an empty map per row — which keeps the index alignment\n * intact and correctly drills the whole (unfiltered) object.\n */\n drillRawTotals?: Array<Array<Record<string, unknown>>>;\n /**\n * #1752 — half-open date-range drill scope per row, the RANGE companion to\n * `drillRawRows` (which handles equality dims). A time-bucketed date\n * dimension (`dateGranularity`) groups a SPAN of records into one bucket\n * (\"2026-Q2\"), so its drill needs `[gte, lt)`, not equality — the humanized\n * bucket can't be exact-matched (which is why date dims are excluded from\n * `dimensionFields`/`drillRawRows`). Aligned to `rows` by index; each entry\n * maps a drillable date-dimension NAME → `{ field, gte, lt }` with `gte`\n * inclusive and `lt` exclusive (bounds as `YYYY-MM-DD`). Present only for\n * buckets whose boundaries are unambiguous — a `datetime` field under a\n * non-UTC reference timezone is omitted (host drills an unscoped superset)\n * until instant-boundary support lands.\n */\n drillRanges?: Array<Record<string, { field: string; gte: string; lt: string }>>;\n};\n\n/**\n * Detect the \"backing object/table isn't present in this kernel\" class of\n * error so a dataset query can degrade to an empty result instead of failing\n * the widget with a 500. Matches the missing-relation signatures across the\n * drivers ObjectStack runs on (sqlite/libsql, postgres, mysql) plus the\n * framework's own unknown-object signal. Deliberately scoped to MISSING SOURCE\n * (table/object/relation) — not column/syntax errors, which stay hard failures\n * so real query bugs still surface.\n */\nfunction isMissingSourceError(err: unknown): boolean {\n const msg = String((err as { message?: unknown })?.message ?? err ?? '').toLowerCase();\n return (\n msg.includes('no such table') || // sqlite / libsql\n (msg.includes('relation') && msg.includes('does not exist')) || // postgres\n msg.includes(\"doesn't exist\") || // mysql (\"table ... doesn't exist\")\n msg.includes('not registered') || // framework: object not in registry\n msg.includes('unknown object') ||\n msg.includes('is not a registered object')\n );\n}\n\n/**\n * Configuration for AnalyticsService.\n */\nexport interface AnalyticsServiceConfig {\n /** Pre-defined cube definitions (from manifest). */\n cubes?: Cube[];\n /** Logger instance. */\n logger?: Logger;\n /**\n * Probe driver capabilities for the object that backs a cube.\n * The service calls this function to decide which strategy can handle a query.\n */\n queryCapabilities?: (cubeName: string) => DriverCapabilities;\n /**\n * Execute raw SQL on the driver for a given object.\n * Required for NativeSQLStrategy.\n */\n executeRawSql?: (objectName: string, sql: string, params: unknown[]) => Promise<Record<string, unknown>[]>;\n /**\n * Execute an ObjectQL aggregate query.\n * Required for ObjectQLStrategy.\n */\n executeAggregate?: (objectName: string, options: {\n groupBy?: string[];\n aggregations?: Array<{ field: string; method: string; alias: string }>;\n filter?: Record<string, unknown>;\n }) => Promise<Record<string, unknown>[]>;\n /**\n * Fallback IAnalyticsService (e.g. MemoryAnalyticsService).\n * Used by InMemoryStrategy.\n */\n fallbackService?: IAnalyticsService;\n /**\n * Custom strategies to add/replace the defaults.\n * They are merged with the built-in strategies and sorted by priority.\n */\n strategies?: AnalyticsStrategy[];\n /**\n * ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). Supplied\n * by the runtime that owns the sharing middleware; receives the current\n * request's ExecutionContext and returns the RLS `FilterCondition` for the\n * object (exactly what `RLSCompiler` emits). The service binds the active\n * context per query and the strategy compiles the filter into alias-qualified\n * SQL injected into every base and joined table.\n *\n * MAY be async: the production bridge resolves RLS from the `security`\n * service's `getReadFilter`, which can hit the database. The service\n * pre-resolves the scope for every base + joined object of a query (before\n * the synchronous SQL builder runs), so a sync return still works unchanged.\n */\n getReadScope?: (\n objectName: string,\n context?: ExecutionContext,\n ) =>\n | FilterCondition\n | null\n | undefined\n | Promise<FilterCondition | null | undefined>;\n /**\n * ADR-0021 D-C — join allowlist per cube (the dataset's declared `include`).\n * Joins outside this set are rejected by the strategy. Compiled datasets\n * (via `queryDataset`/`registerDataset`) supply this automatically; this\n * config hook is a fallback for legacy hand-authored cubes.\n */\n getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;\n /**\n * Coerce a filter comparand to a temporal column's storage form so a\n * relative-date / ISO-string value compares correctly on the active driver\n * (SQLite `Field.datetime` → epoch ms; `Field.date` / native timestamp →\n * unchanged). Threaded into the StrategyContext and consulted by\n * `NativeSQLStrategy` when binding filter values. See the contract docs on\n * `StrategyContext.coerceTemporalFilterValue` for the full rationale.\n */\n coerceTemporalFilterValue?: (objectName: string, fieldName: string, value: unknown) => unknown;\n /**\n * ADR-0062 D6 — report whether an object is federated (external datasource).\n * Threaded into the StrategyContext so `NativeSQLStrategy` declines external\n * objects (which it would otherwise query against the wrong physical table),\n * routing them to the driver-correct ObjectQL aggregate path instead. See\n * `StrategyContext.isExternalObject`.\n */\n isExternalObject?: (objectName: string) => boolean;\n /**\n * ADR-0021 — optional object-graph resolver used when compiling datasets:\n * `(baseObject, relationshipName) => relatedObjectName | undefined`. When\n * provided, `queryDataset` validates that every declared `include` exists.\n */\n relationshipResolver?: RelationshipResolver;\n /**\n * ADR-0053 currency chain — resolve a measure's SOURCE FIELD currency\n * metadata so a monetary measure that omits an explicit `currency` falls back\n * to the field's declared currency, then the tenant default (`ctx.currency`).\n * Returns the source field's `type` and (fixed-mode) `defaultCurrency`;\n * `undefined` for an unknown field. Non-`currency` fields never get a code.\n */\n measureCurrency?: (object: string, field: string) => { type?: string; defaultCurrency?: string } | undefined;\n /** Pre-defined datasets to compile + register at construction (ADR-0021). */\n datasets?: Dataset[];\n /**\n * ADR-0021 — resolve raw dimension values to human display labels. When\n * provided, `queryDataset` post-processes result rows so a `select` dimension\n * shows its option label (not the stored value) and a `lookup`/`master_detail`\n * dimension shows the related record's display name (not the FK id). Injected\n * by the plugin from the `data` engine; omit to keep raw values.\n */\n labelResolver?: DimensionLabelDeps;\n\n /**\n * ADR-0037 Phase 3 — draft data preview. Resolve the PENDING `seed` draft\n * rows for an object (returns null when the object has no pending seed).\n * When provided and `queryDataset` is called with `previewDrafts`, the\n * selection is evaluated over these rows in memory instead of the engine —\n * the Live Canvas charts real numbers from the drafted sample data, and\n * because publish materializes the SAME seed, the numbers are continuous\n * across the publish boundary. Reads only; never touches physical tables.\n */\n draftRowsResolver?: (\n objectName: string,\n context?: ExecutionContext,\n ) => Promise<Record<string, unknown>[] | null>;\n}\n\n/**\n * Default capabilities when probing is not configured — assumes in-memory only.\n */\nconst DEFAULT_CAPABILITIES: DriverCapabilities = {\n nativeSql: false,\n objectqlAggregate: false,\n inMemory: true,\n};\n\n/**\n * AnalyticsService — Multi-driver analytics orchestrator.\n *\n * Implements `IAnalyticsService` by delegating to a priority-ordered\n * strategy chain:\n *\n * | Priority | Strategy | Condition |\n * |:---:|:---|:---|\n * | P1 (10) | NativeSQLStrategy | Driver supports raw SQL |\n * | P2 (20) | ObjectQLStrategy | Driver supports aggregate AST |\n * | P3 (30) | (custom / InMemoryStrategy from driver-memory) | Injected by user |\n *\n * When `fallbackService` is configured, an internal delegate strategy\n * is automatically appended at priority 30 as a safety net.\n *\n * The service also owns a `CubeRegistry` for metadata discovery and\n * auto-inference from object schemas.\n */\nexport class AnalyticsService implements IAnalyticsService {\n private readonly strategies: AnalyticsStrategy[];\n /** Context-independent part of the StrategyContext (no per-request scope). */\n private readonly baseCtx: StrategyContext;\n /** Context-aware read-scope provider (bound to the request's context per call). */\n private readonly readScopeProvider?: AnalyticsServiceConfig['getReadScope'];\n /** Compiled datasets by name — feeds the join allowlist (D-C) and queryDataset. */\n private readonly datasetRegistry = new Map<string, CompiledDataset>();\n /** Optional object-graph resolver used when compiling datasets. */\n private readonly relationshipResolver?: RelationshipResolver;\n private readonly measureCurrency?: AnalyticsServiceConfig['measureCurrency'];\n /** Optional dimension display-label resolver (select options / lookup names). */\n private readonly labelResolver?: DimensionLabelDeps;\n /** ADR-0037 P3: pending-seed row resolver for draft data preview. */\n private readonly draftRowsResolver?: AnalyticsServiceConfig['draftRowsResolver'];\n readonly cubeRegistry: CubeRegistry;\n private readonly logger: Logger;\n\n constructor(config: AnalyticsServiceConfig = {}) {\n this.logger = config.logger || createLogger({ level: 'info', format: 'pretty' });\n this.cubeRegistry = new CubeRegistry();\n\n // Register pre-defined cubes\n if (config.cubes) {\n this.cubeRegistry.registerAll(config.cubes);\n }\n\n this.readScopeProvider = config.getReadScope;\n this.relationshipResolver = config.relationshipResolver;\n this.measureCurrency = config.measureCurrency;\n this.labelResolver = config.labelResolver;\n this.draftRowsResolver = config.draftRowsResolver;\n\n // Compile + register pre-defined datasets (ADR-0021).\n if (config.datasets) {\n for (const ds of config.datasets) {\n try {\n this.registerDataset(ds);\n } catch (e) {\n this.logger?.warn?.(`[Analytics] Failed to register dataset \"${ds?.name}\": ${String((e as Error)?.message ?? e)}`);\n }\n }\n }\n\n // Build the context-independent strategy context. `getReadScope` is bound\n // per query in `callCtx(context)` so it can resolve the active tenant.\n this.baseCtx = {\n getCube: (name) => this.cubeRegistry.get(name),\n queryCapabilities: config.queryCapabilities || (() => DEFAULT_CAPABILITIES),\n executeRawSql: config.executeRawSql,\n executeAggregate: config.executeAggregate,\n fallbackService: config.fallbackService,\n // Prefer a compiled dataset's declared relationships (D-C join allowlist);\n // fall back to any explicitly-configured provider for legacy cubes.\n getAllowedRelationships: (cubeName: string) =>\n this.datasetRegistry.get(cubeName)?.allowedRelationships\n ?? config.getAllowedRelationships?.(cubeName),\n coerceTemporalFilterValue: config.coerceTemporalFilterValue,\n isExternalObject: config.isExternalObject,\n };\n\n // Build strategy chain (built-in + custom, sorted by priority)\n // InMemoryStrategy is NOT built-in — it lives in @objectstack/driver-memory\n // and should be passed via config.strategies when needed.\n // When fallbackService is configured, an internal delegate is added at P3.\n const builtIn: AnalyticsStrategy[] = [\n new NativeSQLStrategy(),\n new ObjectQLStrategy(),\n ];\n\n // Auto-add fallback delegate when fallbackService is provided\n if (config.fallbackService) {\n builtIn.push(new FallbackDelegateStrategy());\n }\n\n const custom = config.strategies || [];\n this.strategies = [...builtIn, ...custom].sort((a, b) => a.priority - b.priority);\n\n this.logger.info(\n `[Analytics] Initialized with ${this.cubeRegistry.size} cubes, ` +\n `${this.strategies.length} strategies: ${this.strategies.map(s => s.name).join(' → ')}`,\n );\n }\n\n /**\n * Build a per-call StrategyContext that binds the read-scope provider to the\n * current request's ExecutionContext (ADR-0021 D-C). The strategy then sees a\n * `getReadScope(objectName)` that already knows the active tenant.\n */\n private async callCtx(\n query: AnalyticsQuery,\n context?: ExecutionContext,\n ): Promise<StrategyContext> {\n if (!this.readScopeProvider) return this.baseCtx;\n // Pre-resolve the read scope for every object the strategy will scan (base\n // + all declared joins) BEFORE the synchronous SQL builder runs, since the\n // provider may be async (the production `security.getReadFilter` bridge).\n // The strategy then reads each object's filter synchronously from the map.\n const scopes = await this.resolveReadScopes(query, context);\n return {\n ...this.baseCtx,\n getReadScope: (objectName: string) => scopes.get(objectName) ?? null,\n };\n }\n\n /**\n * Resolve the read scope (tenant + RLS `FilterCondition`) for the base object\n * AND every joined object of the query's cube, keyed by object name. This is\n * the async pre-pass that lets the synchronous strategy enforce scoping even\n * when the provider (security `getReadFilter`) resolves asynchronously.\n *\n * The object set is `cube.sql` (base) plus every `cube.joins[*].name` — a\n * SUPERSET of what the strategy actually scans (the strategy only joins along\n * declared relationships), so no scanned object is ever left unscoped.\n *\n * Fail-closed: if the provider throws for an object, the whole query is\n * rejected rather than emitting SQL with that object unscoped.\n */\n private async resolveReadScopes(\n query: AnalyticsQuery,\n context?: ExecutionContext,\n ): Promise<Map<string, FilterCondition>> {\n const map = new Map<string, FilterCondition>();\n const provider = this.readScopeProvider;\n if (!provider || !query.cube) return map;\n const cube = this.cubeRegistry.get(query.cube);\n if (!cube) return map;\n\n const objects = new Set<string>();\n if (typeof cube.sql === 'string' && cube.sql.trim()) {\n objects.add(cube.sql.trim());\n }\n const joins = (cube as { joins?: Record<string, { name?: string }> }).joins;\n if (joins) {\n for (const [alias, j] of Object.entries(joins)) {\n objects.add(j?.name ?? alias);\n }\n }\n\n for (const object of objects) {\n let filter: FilterCondition | null | undefined;\n try {\n filter = await provider(object, context);\n } catch (e) {\n // Deny the entire query — never fall through to unscoped SQL.\n this.logger.error?.(\n `[Analytics] read-scope resolution failed for object \"${object}\" — ` +\n `rejecting query (fail-closed, ADR-0021 D-C)`,\n e instanceof Error ? e : new Error(String(e)),\n );\n throw new Error(\n `[Analytics] read-scope resolution failed for \"${object}\"; query denied (fail-closed).`,\n );\n }\n if (filter != null) map.set(object, filter);\n }\n return map;\n }\n\n /**\n * Execute an analytical query by delegating to the first capable strategy.\n *\n * A strategy can discover only AT EXECUTION TIME that the underlying driver\n * cannot serve it — the canonical case is NativeSQLStrategy on an in-memory\n * driver, whose `execute()` returns null for raw SQL (the auto-bridge throws\n * `RAW_SQL_UNSUPPORTED`). That is a capability miss, not a query error: fall\n * back to the next capable strategy (e.g. ObjectQLStrategy over the\n * aggregate bridge) instead of failing — or worse, fabricating empty rows.\n * Any other error propagates untouched.\n */\n async query(query: AnalyticsQuery, context?: ExecutionContext): Promise<AnalyticsResult> {\n if (!query.cube) {\n throw new Error('Cube name is required in analytics query');\n }\n\n this.ensureCube(query);\n const ctx = await this.callCtx(query, context);\n let skip: Set<AnalyticsStrategy> | undefined;\n for (;;) {\n const strategy = this.resolveStrategy(query, ctx, skip);\n this.logger.debug(`[Analytics] Query on cube \"${query.cube}\" → ${strategy.name}`);\n try {\n return await strategy.execute(query, ctx);\n } catch (e) {\n if ((e as { code?: string })?.code === 'RAW_SQL_UNSUPPORTED') {\n this.logger.warn(\n `[Analytics] ${strategy.name} cannot run on this driver (raw SQL unsupported) — falling back to the next strategy.`,\n );\n (skip ??= new Set()).add(strategy);\n continue;\n }\n throw e;\n }\n }\n }\n\n /**\n * Compile a `dataset` (ADR-0021) and register its Cube + join allowlist so it\n * can be queried by name. Idempotent (re-registering overwrites). Returns the\n * compiled dataset.\n */\n registerDataset(dataset: Dataset): CompiledDataset {\n const compiled = compileDataset(dataset, this.relationshipResolver);\n this.cubeRegistry.register(compiled.cube);\n this.datasetRegistry.set(dataset.name, compiled);\n return compiled;\n }\n\n /**\n * Execute a semantic-layer dataset (ADR-0021). Compiles the dataset (saved or\n * inline draft — Studio preview), registers its Cube + join allowlist, then\n * runs the selection through the `DatasetExecutor` with the request context so\n * tenant/RLS scoping (D-C) is applied. See {@link IAnalyticsService.queryDataset}.\n */\n async queryDataset(\n dataset: Dataset,\n selection: DatasetSelection,\n context?: ExecutionContext,\n options?: { previewDrafts?: boolean },\n ): Promise<AnalyticsResult> {\n const compiled = this.registerDataset(dataset);\n this.logger.debug(`[Analytics] queryDataset \"${dataset.name}\" (object=${dataset.object}, include=${(dataset.include ?? []).join(',') || '—'})`);\n\n // ── ADR-0037 P3 — draft data preview ────────────────────────────────────\n // When the request renders the as-if-published world AND the base object\n // has a PENDING seed draft, evaluate the selection over the seed's rows in\n // memory (a query-evaluating proxy feeds the unchanged DatasetExecutor, so\n // measure filters / compareTo / derived measures all behave identically).\n // No pending seed → fall through to the real engine: published objects\n // keep charting live data even inside a preview.\n if (options?.previewDrafts && this.draftRowsResolver) {\n let seedRows: Record<string, unknown>[] | null = null;\n try {\n seedRows = await this.draftRowsResolver(dataset.object, context);\n } catch (e) {\n this.logger.warn(`[Analytics] draft preview resolver failed for \"${dataset.object}\" — falling back to live data: ${String((e as Error)?.message ?? e)}`);\n }\n if (seedRows) {\n this.logger.debug(`[Analytics] queryDataset \"${dataset.name}\" → preview over ${seedRows.length} drafted seed row(s)`);\n const previewService = {\n query: async (q: AnalyticsQuery) => evaluateAnalyticsQueryOverRows(q, compiled.cube, seedRows!),\n } as IAnalyticsService;\n const previewResult = await new DatasetExecutor(previewService).execute(compiled, selection, context);\n // Label resolution is skipped on purpose: drafted seed rows reference\n // lookups by NAME (the seed convention), which already reads well.\n return previewResult;\n }\n }\n\n // Graceful degradation: a dashboard/report widget whose backing object or\n // table is not present in this kernel (e.g. a platform dashboard like\n // System Overview that charts `sys_audit_log`, opened in an environment\n // that never mounted the audit object) must render as \"no data\" — NOT\n // crash the widget with a 500. Datasets were the one read surface that\n // hard-failed on a missing source.\n let result: AnalyticsResult;\n try {\n result = await new DatasetExecutor(this).execute(compiled, selection, context);\n } catch (err) {\n if (isMissingSourceError(err)) {\n this.logger.warn(\n `[Analytics] dataset \"${dataset.name}\" backing object \"${dataset.object}\" is unavailable ` +\n `(${String((err as Error)?.message ?? err)}); returning an empty result instead of failing the widget`,\n );\n return { rows: [], fields: [], totals: [] };\n }\n throw err;\n }\n\n // Selected dimensions resolved against the dataset definition — shared by\n // drill metadata, label resolution, and dimension field-label enrichment.\n const selectedDims = (selection.dimensions ?? [])\n .map((name) => dataset.dimensions?.find((d) => d.name === name))\n .filter((d): d is NonNullable<typeof d> => !!d);\n\n // ADR-0021 D2 — drill-through metadata. A host (dashboard/report) drills a\n // clicked bucket back to the underlying records, but it only knows the\n // dimension NAMES, and the label resolution below OVERWRITES the raw grouped\n // value in each row with its display label. So before that happens, snapshot\n // the raw grouped values into a PARALLEL array (aligned to `rows` by index —\n // the result rows are NOT mutated) and expose the dataset's `object` +\n // dimension→field mapping so the renderer can build an exact-match filter.\n // Date buckets are excluded — a humanized bucket (\"2026-06\") can't be\n // exact-matched against the stored timestamp, so they are not drillable.\n const drillDims = selectedDims.filter((d) => !!d.field && d.type !== 'date');\n if (drillDims.length && result.rows.length) {\n (result as AnalyticsResultWithDrill).object = dataset.object;\n (result as AnalyticsResultWithDrill).dimensionFields = Object.fromEntries(\n drillDims.map((d) => [d.name, d.field as string]),\n );\n (result as AnalyticsResultWithDrill).drillRawRows = result.rows.map((row) => {\n const raw: Record<string, unknown> = {};\n for (const d of drillDims) raw[d.name] = row[d.name];\n return raw;\n });\n // #3214 — the totals/subtotal rows (#1753) carry dimension values too and\n // go through the SAME label resolution below, so snapshot their raw\n // grouped values here in the same pre-label pass. Aligned to `result.totals`\n // by index; each grouping is restricted to the drillable dims it actually\n // groups by (the grand-total grouping `[]` keeps empty maps, so a subtotal\n // drill filters by the stored value while the grand total drills unfiltered).\n if (result.totals?.length) {\n (result as AnalyticsResultWithDrill).drillRawTotals = result.totals.map((total) => {\n const groupingDims = drillDims.filter((d) => total.dimensions.includes(d.name));\n return total.rows.map((row) => {\n const raw: Record<string, unknown> = {};\n for (const d of groupingDims) raw[d.name] = row[d.name];\n return raw;\n });\n });\n }\n }\n\n // #1752 — date-range drill scope. A `dateGranularity` dimension groups a\n // SPAN of records into one bucket, so drilling it needs a half-open range\n // `[gte, lt)`, which the equality `drillRawRows` sidecar can't express\n // (that's exactly why date dims are excluded from `drillDims` above). Emit\n // a parallel range sidecar computed — via the shared inverse util so server\n // and client agree on boundaries — from the canonical bucket KEY, which is\n // still in `rows[i][dim]` here (this runs BEFORE label resolution rewrites\n // it to a display label).\n const rangeTz = selection.timezone ?? context?.timezone ?? 'UTC';\n // Per drillable date+granularity dim, decide how to serialize its bounds\n // (ADR-0053 temporal semantics):\n // - `datetime` → the reference tz's MIDNIGHT INSTANT (ISO), because the\n // bucket is defined on that tz's calendar (works under any tz, incl. DST);\n // - `date` → `YYYY-MM-DD` calendar bounds, a tz-naive calendar day that is\n // exact under ANY reference tz;\n // - unknown field type → safe only under UTC (where the calendar day and\n // its instant coincide); under a non-UTC tz we can't tell whether to\n // shift, so the dim is omitted and the host drills a superset.\n const rangeDims: Array<{ d: (typeof selectedDims)[number]; instant: boolean }> = [];\n for (const d of selectedDims) {\n if (!d.field || d.type !== 'date' || !d.dateGranularity) continue;\n const ftype = this.measureCurrency?.(dataset.object, d.field as string)?.type;\n if (ftype === 'datetime') rangeDims.push({ d, instant: true });\n else if (ftype === 'date') rangeDims.push({ d, instant: false });\n else if (rangeTz === 'UTC') rangeDims.push({ d, instant: false });\n // else: unknown field type under a non-UTC reference tz → omit (superset).\n }\n if (rangeDims.length && result.rows.length) {\n const bound = (ymd: string, instant: boolean): string =>\n instant ? new Date(zonedDateStartToUtcMs(ymd, rangeTz)).toISOString() : ymd;\n (result as AnalyticsResultWithDrill).drillRanges = result.rows.map((row) => {\n const ranges: Record<string, { field: string; gte: string; lt: string }> = {};\n for (const { d, instant } of rangeDims) {\n const cal = bucketKeyToCalendarRange(row[d.name] as string, d.dateGranularity!);\n if (cal) {\n ranges[d.name] = { field: d.field as string, gte: bound(cal.start, instant), lt: bound(cal.end, instant) };\n }\n }\n return ranges;\n });\n // The equality drill block sets `object` only when a NON-date drill dim\n // exists; a report grouped ONLY by time still needs the base object so the\n // host can open its list. Safe to (re)set to the same dataset object.\n (result as AnalyticsResultWithDrill).object = dataset.object;\n }\n\n // ADR-0021 — resolve grouped dimension values to human display labels\n // (select option label, lookup related-record name). Charts render the\n // dimension key verbatim, so this is the single place that turns a stored\n // value / FK id into the text a user expects to read.\n if (this.labelResolver && selectedDims.length) {\n const dims = selectedDims\n .filter((d) => !!d.field)\n .map((d) => ({ name: d.name, field: d.field, type: d.type, dateGranularity: d.dateGranularity }));\n if (dims.length) {\n try {\n await resolveDimensionLabels(dataset.object, dims, result.rows, this.labelResolver);\n // Totals rows (#1753) carry dimension values too (a row subtotal is\n // keyed by its row bucket) — resolve each grouping's own subset.\n for (const total of result.totals ?? []) {\n const subset = dims.filter((d) => total.dimensions.includes(d.name));\n if (subset.length) {\n await resolveDimensionLabels(dataset.object, subset, total.rows, this.labelResolver);\n }\n }\n } catch (e) {\n this.logger?.warn?.(`[Analytics] dimension label resolution failed for \"${dataset.name}\": ${String((e as Error)?.message ?? e)}`);\n }\n }\n }\n\n // ADR-0021 — enrich measure columns with their display `label` + `format`\n // so presentations show \"Tasks\" / \"$616,000\" instead of the raw measure\n // name \"task_count\" / \"616000\". Carried on the result fields; the renderer\n // applies the format (it can't be baked into the numeric row value).\n if (result.fields?.length && dataset.measures?.length) {\n const measureByName = new Map(dataset.measures.map((m) => [m.name, m]));\n for (const f of result.fields) {\n const m = measureByName.get(f.name) ?? measureByName.get(f.name.replace(/__compare$/, ''));\n if (!m) continue;\n if (f.label == null && typeof m.label === 'string') f.label = m.label;\n if (f.format == null && m.format) f.format = m.format;\n // ADR-0053 currency chain. A MONETARY measure resolves its display\n // currency from: explicit measure `currency` → source-field\n // `currencyConfig.defaultCurrency` → tenant default (`ctx.currency`). A\n // measure is monetary if it declares a currency OR aggregates a\n // `currency`-type field; non-monetary measures (count, avg of a plain\n // number) never receive a currency code.\n const fc = f as { currency?: string };\n const mc = m as { currency?: string };\n if (fc.currency == null) {\n const meta = m.field ? this.measureCurrency?.(dataset.object, m.field) : undefined;\n const monetary = !!mc.currency || meta?.type === 'currency';\n if (monetary) {\n const resolved = mc.currency ?? meta?.defaultCurrency ?? context?.currency;\n if (resolved) fc.currency = resolved;\n }\n }\n }\n }\n\n // Enrich DIMENSION columns with their display `label` too, so a grouped\n // table header reads \"Status\" instead of the raw field name \"status\". The\n // measure-only enrichment above left dimension headers bare (the renderer\n // then fell back to the raw dimension name).\n if (result.fields?.length && selectedDims.length) {\n const dimByName = new Map(selectedDims.map((d) => [d.name, d]));\n const dimByField = new Map(selectedDims.filter((d) => !!d.field).map((d) => [d.field as string, d]));\n for (const f of result.fields) {\n if (f.label != null) continue;\n // Result fields may be keyed by the dataset dimension NAME or the\n // underlying cube FIELD depending on strategy — match either.\n const d = dimByName.get(f.name) ?? dimByField.get(f.name);\n if (d && typeof d.label === 'string') f.label = d.label;\n }\n }\n return result;\n }\n\n /**\n * Get cube metadata for discovery.\n */\n async getMeta(cubeName?: string): Promise<CubeMeta[]> {\n // If a fallback service is configured, merge its metadata with the registry\n const cubes = cubeName\n ? [this.cubeRegistry.get(cubeName)].filter(Boolean) as Cube[]\n : this.cubeRegistry.getAll();\n\n return cubes.map(cube => ({\n name: cube.name,\n title: cube.title,\n measures: Object.entries(cube.measures).map(([key, measure]) => ({\n name: `${cube.name}.${key}`,\n type: measure.type,\n title: measure.label,\n })),\n dimensions: Object.entries(cube.dimensions).map(([key, dimension]) => ({\n name: `${cube.name}.${key}`,\n type: dimension.type,\n title: dimension.label,\n })),\n }));\n }\n\n /**\n * Generate SQL for a query without executing it (dry-run).\n */\n async generateSql(query: AnalyticsQuery, context?: ExecutionContext): Promise<{ sql: string; params: unknown[] }> {\n if (!query.cube) {\n throw new Error('Cube name is required for SQL generation');\n }\n\n this.ensureCube(query);\n const ctx = await this.callCtx(query, context);\n const strategy = this.resolveStrategy(query, ctx);\n this.logger.debug(`[Analytics] generateSql on cube \"${query.cube}\" → ${strategy.name}`);\n\n return strategy.generateSql(query, ctx);\n }\n\n // ── Internal ─────────────────────────────────────────────────────\n\n /**\n * Ensure a cube exists for the given query and that it knows about every\n * measure referenced by the query.\n *\n * - If no cube is registered for `query.cube`, infer a minimal cube from\n * the query so downstream strategies (which assume `cube.sql` exists)\n * don't crash.\n * - If a cube exists but the query references measures that aren't in\n * `cube.measures` (e.g. `amount_sum`, `amount_avg` emitted by dashboard\n * widget translators), inject suffix-inferred Metric entries so the\n * strategies pick the right aggregation function and field.\n */\n private ensureCube(query: AnalyticsQuery): void {\n const name = query.cube!;\n let cube = this.cubeRegistry.get(name);\n\n if (!cube) {\n cube = this.inferCubeFromQuery(query);\n this.cubeRegistry.register(cube);\n // A scalar query — only measures, no grouping (no `dimensions`/\n // `timeDimensions`) — is the first-class \"metric over an object\" path\n // (e.g. the `object-metric` KPI widget). Auto-inferring a count/sum cube\n // is the intended behaviour there, so log at debug. A query that groups\n // by an explicit dimension or time bucket almost certainly meant to hit a\n // registered cube; keep that at warn so a forgotten registration is loud.\n const isScalarMetric =\n (query.dimensions?.length ?? 0) === 0 && (query.timeDimensions?.length ?? 0) === 0;\n const message =\n `[Analytics] No cube registered for \"${name}\"; auto-inferred a minimal cube ` +\n `(sql=\"${name}\", measures=${Object.keys(cube.measures).join(',') || '(none)'}, ` +\n `dimensions=${Object.keys(cube.dimensions).join(',') || '(none)'}). ` +\n `Define an explicit Cube in your stack for full control.`;\n if (isScalarMetric) this.logger.debug(message);\n else this.logger.warn(message);\n return;\n }\n\n // Cube exists — check for unknown measures referenced by the query and\n // augment the cube with suffix-inferred Metric definitions so callers\n // that pass `<field>_sum` / `<field>_avg` etc. get the right aggregation.\n const stripPrefix = (m: string) => (m.includes('.') ? m.split('.').slice(1).join('.') : m);\n const extraMeasures: Record<string, any> = {};\n for (const m of query.measures || []) {\n const key = stripPrefix(m);\n if (cube.measures[key] || extraMeasures[key]) continue;\n extraMeasures[key] = inferMeasure(key);\n }\n if (Object.keys(extraMeasures).length > 0) {\n const augmented: Cube = {\n ...cube,\n measures: { ...cube.measures, ...extraMeasures },\n };\n this.cubeRegistry.register(augmented);\n this.logger.debug(\n `[Analytics] Augmented cube \"${name}\" with inferred measures: ${Object.keys(extraMeasures).join(',')}`,\n );\n }\n }\n\n /** Build a minimal Cube from the fields referenced by an AnalyticsQuery. */\n private inferCubeFromQuery(query: AnalyticsQuery): Cube {\n const cubeName = query.cube!;\n const measures: Record<string, any> = {};\n const dimensions: Record<string, any> = {};\n\n const stripPrefix = (m: string) => (m.includes('.') ? m.split('.').slice(1).join('.') : m);\n\n // Always provide a default `count` measure\n measures.count = { name: 'count', label: 'Count', type: 'count', sql: '*' };\n\n for (const m of query.measures || []) {\n const key = stripPrefix(m);\n if (measures[key]) continue;\n const inferred = inferMeasure(key);\n measures[key] = inferred;\n }\n\n for (const d of query.dimensions || []) {\n const key = stripPrefix(d);\n if (dimensions[key]) continue;\n dimensions[key] = { name: key, label: key, type: 'string', sql: key };\n }\n\n if (query.where && typeof query.where === 'object' && !Array.isArray(query.where)) {\n // Canonical FilterCondition: top-level keys (excluding logical\n // combinators) are field names. We only need them to seed an\n // ad-hoc cube definition for free-form queries.\n for (const key of Object.keys(query.where as Record<string, unknown>)) {\n if (key.startsWith('$')) continue;\n const stripped = stripPrefix(key);\n if (dimensions[stripped] || measures[stripped]) continue;\n dimensions[stripped] = { name: stripped, label: stripped, type: 'string', sql: stripped };\n }\n }\n\n for (const td of query.timeDimensions || []) {\n const key = stripPrefix(td.dimension);\n if (dimensions[key]) continue;\n dimensions[key] = {\n name: key, label: key, type: 'time', sql: key,\n granularities: ['day', 'week', 'month', 'quarter', 'year'],\n };\n }\n\n return {\n name: cubeName,\n title: cubeName,\n sql: cubeName,\n measures,\n dimensions,\n public: false,\n };\n }\n\n /**\n * Walk the strategy chain and return the first strategy that can handle the\n * query. `skip` excludes strategies that already proved incapable at\n * execution time (see {@link query}'s RAW_SQL_UNSUPPORTED fallback).\n */\n private resolveStrategy(\n query: AnalyticsQuery,\n ctx: StrategyContext,\n skip?: Set<AnalyticsStrategy>,\n ): AnalyticsStrategy {\n for (const strategy of this.strategies) {\n if (skip?.has(strategy)) continue;\n if (strategy.canHandle(query, ctx)) {\n return strategy;\n }\n }\n throw new Error(\n `[Analytics] No strategy can handle query for cube \"${query.cube}\". ` +\n `Checked: ${this.strategies.map(s => s.name).join(', ')}${skip?.size ? ` (skipped at runtime: ${[...skip].map((s) => s.name).join(', ')})` : ''}. ` +\n 'Ensure a compatible driver is configured or a fallback service is registered.',\n );\n }\n}\n\n/**\n * Infer a Metric definition from a measure key name.\n *\n * Recognised suffix conventions (matches dashboard widget translators that\n * emit measures like `<field>_sum`, `<field>_avg`):\n *\n * | Suffix | Aggregation |\n * |:-------------------|:----------------|\n * | `count` | `count(*)` |\n * | `_sum` | `sum(field)` |\n * | `_avg` / `_average`| `avg(field)` |\n * | `_min` | `min(field)` |\n * | `_max` | `max(field)` |\n * | `_count_distinct` | `count(distinct field)` |\n *\n * Anything else is treated as a `sum(<key>)` — best-effort default for an\n * unknown numeric measure.\n */\nexport function inferMeasure(key: string): { name: string; label: string; type: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'; sql: string } {\n if (key === 'count') {\n return { name: 'count', label: 'Count', type: 'count', sql: '*' };\n }\n const suffixes: Array<[string, 'sum' | 'avg' | 'min' | 'max' | 'count_distinct']> = [\n ['_count_distinct', 'count_distinct'],\n ['_sum', 'sum'],\n ['_avg', 'avg'],\n ['_average', 'avg'],\n ['_min', 'min'],\n ['_max', 'max'],\n ];\n for (const [suffix, type] of suffixes) {\n if (key.endsWith(suffix)) {\n const field = key.slice(0, -suffix.length) || '*';\n return { name: key, label: key, type, sql: field };\n }\n }\n return { name: key, label: key, type: 'sum', sql: key };\n}\n\n/**\n * FallbackDelegateStrategy — Internal strategy for fallback service delegation.\n *\n * Automatically added to the strategy chain when `fallbackService` is configured.\n * Not exported — consumers who need explicit in-memory support should use\n * `InMemoryStrategy` from `@objectstack/driver-memory`.\n */\nclass FallbackDelegateStrategy implements AnalyticsStrategy {\n readonly name = 'FallbackDelegateStrategy';\n readonly priority = 30;\n\n canHandle(query: AnalyticsQuery, ctx: StrategyContext): boolean {\n if (!query.cube) return false;\n return !!ctx.fallbackService;\n }\n\n async execute(query: AnalyticsQuery, ctx: StrategyContext): Promise<AnalyticsResult> {\n return ctx.fallbackService!.query(query);\n }\n\n async generateSql(query: AnalyticsQuery, ctx: StrategyContext): Promise<{ sql: string; params: unknown[] }> {\n if (ctx.fallbackService?.generateSql) {\n return ctx.fallbackService.generateSql(query);\n }\n return {\n sql: `-- FallbackDelegateStrategy: SQL generation not supported for cube \"${query.cube}\"`,\n params: [],\n };\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Cube } from '@objectstack/spec/data';\n\n/**\n * CubeRegistry — Central registry for analytics cube definitions.\n *\n * Cubes can be registered from two sources:\n * 1. **Manifest definitions** — Explicit cube definitions in `objectstack.config.ts`.\n * 2. **Object schema inference** — Auto-generated cubes from ObjectQL object schemas.\n *\n * The registry is the single source of truth for cube metadata discovery\n * (used by `getMeta()` and the strategy chain).\n */\nexport class CubeRegistry {\n private cubes = new Map<string, Cube>();\n\n /** Register a single cube definition. Overwrites if name already exists. */\n register(cube: Cube): void {\n this.cubes.set(cube.name, cube);\n }\n\n /** Register multiple cube definitions at once. */\n registerAll(cubes: Cube[]): void {\n for (const cube of cubes) {\n this.register(cube);\n }\n }\n\n /** Get a cube definition by name. */\n get(name: string): Cube | undefined {\n return this.cubes.get(name);\n }\n\n /** Check if a cube is registered. */\n has(name: string): boolean {\n return this.cubes.has(name);\n }\n\n /** Return all registered cubes. */\n getAll(): Cube[] {\n return Array.from(this.cubes.values());\n }\n\n /** Return all cube names. */\n names(): string[] {\n return Array.from(this.cubes.keys());\n }\n\n /** Number of registered cubes. */\n get size(): number {\n return this.cubes.size;\n }\n\n /** Remove all cubes. */\n clear(): void {\n this.cubes.clear();\n }\n\n /**\n * Auto-generate a cube definition from an object schema.\n *\n * Heuristic rules:\n * - `number` fields → `sum`, `avg`, `min`, `max` measures\n * - `boolean` fields → `count` measure (count where true)\n * - All non-computed fields → dimensions\n * - `date`/`datetime` fields → time dimensions with standard granularities\n * - A default `count` measure is always added\n *\n * @param objectName - The snake_case object name (used as table/cube name)\n * @param fields - Array of field descriptors `{ name, type, label? }`\n */\n inferFromObject(\n objectName: string,\n fields: Array<{ name: string; type: string; label?: string }>,\n ): Cube {\n const measures: Record<string, any> = {\n count: {\n name: 'count',\n label: 'Count',\n type: 'count',\n sql: '*',\n },\n };\n const dimensions: Record<string, any> = {};\n\n for (const field of fields) {\n const label = field.label || field.name;\n\n // All fields become dimensions\n const dimType = this.fieldTypeToDimensionType(field.type);\n dimensions[field.name] = {\n name: field.name,\n label,\n type: dimType,\n sql: field.name,\n ...(dimType === 'time'\n ? { granularities: ['day', 'week', 'month', 'quarter', 'year'] }\n : {}),\n };\n\n // Numeric fields also become aggregation measures\n if (field.type === 'number' || field.type === 'currency' || field.type === 'percent') {\n measures[`${field.name}_sum`] = {\n name: `${field.name}_sum`,\n label: `${label} (Sum)`,\n type: 'sum',\n sql: field.name,\n };\n measures[`${field.name}_avg`] = {\n name: `${field.name}_avg`,\n label: `${label} (Avg)`,\n type: 'avg',\n sql: field.name,\n };\n }\n }\n\n const cube: Cube = {\n name: objectName,\n title: objectName,\n sql: objectName,\n measures,\n dimensions,\n public: false,\n };\n\n this.register(cube);\n return cube;\n }\n\n private fieldTypeToDimensionType(fieldType: string): string {\n switch (fieldType) {\n case 'number':\n case 'currency':\n case 'percent':\n return 'number';\n case 'boolean':\n return 'boolean';\n case 'date':\n case 'datetime':\n return 'time';\n default:\n return 'string';\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Filter Normalization for the Analytics Layer\n *\n * The analytics endpoint accepts filters via the canonical `where`\n * field per the unified Query DSL (`spec/data/query.zod.ts`):\n *\n * - MongoDB-style FilterCondition: `{ field: value }` /\n * `{ field: { $op: value } }` / `{ $and: [...] }` — defined in\n * `spec/data/filter.zod.ts` and used by `find()`, dashboard\n * widget `filter`, RLS, etc.\n *\n * `normalizeAnalyticsFilters` flattens the FilterCondition tree into\n * the internal array form used by the SQL/Mongo pipeline strategies.\n * Strategies stay simple — they only need to know one shape — and the\n * spec is honoured: dashboard metadata is authored once in the\n * canonical MongoDB form and the server normalizes at the boundary.\n */\n\nexport interface NormalizedAnalyticsFilter {\n member: string;\n operator: string;\n values: string[];\n}\n\nconst MONGO_TO_CUBE_OP: Record<string, string> = {\n $eq: 'equals',\n $ne: 'notEquals',\n $gt: 'gt',\n $gte: 'gte',\n $lt: 'lt',\n $lte: 'lte',\n $in: 'in',\n $nin: 'notIn',\n $contains: 'contains',\n $notContains: 'notContains',\n $exists: 'set',\n};\n\n/**\n * Stringify a filter value as the internal pipeline requires `values: string[]`.\n *\n * Booleans serialize as the tokens `'true'`/`'false'` (NOT `'1'`/`'0'`) so the\n * boolean identity survives the string roundtrip: the consuming strategies can\n * recover a real boolean for the ObjectQL engine (which compares against the\n * stored boolean type) while still binding `1`/`0` for SQL. Stringifying to\n * `'1'`/`'0'` was indistinguishable from a numeric 1/0 and made every boolean\n * equality filter / boolean group-by compare a number against a boolean — and\n * never match.\n */\nfunction stringifyForCube(v: unknown): string {\n if (v == null) return '';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (v instanceof Date) return v.toISOString();\n if (typeof v === 'object') return JSON.stringify(v);\n return String(v);\n}\n\nfunction flattenCondition(cond: Record<string, unknown>, out: NormalizedAnalyticsFilter[]): void {\n for (const [key, raw] of Object.entries(cond)) {\n if (raw === undefined) continue;\n\n if (key === '$and' && Array.isArray(raw)) {\n for (const sub of raw) {\n if (sub && typeof sub === 'object') {\n flattenCondition(sub as Record<string, unknown>, out);\n }\n }\n continue;\n }\n // Logical $or / $not require recursive WHERE building which the\n // current strategies don't yet support; ignore so partial queries\n // still run.\n if (key === '$or' || key === '$not') continue;\n\n if (raw === null) {\n out.push({ member: key, operator: 'notSet', values: [] });\n continue;\n }\n\n if (typeof raw === 'object' && !Array.isArray(raw) && !(raw instanceof Date)) {\n const wrapper = raw as Record<string, unknown>;\n const opKeys = Object.keys(wrapper).filter(k => k.startsWith('$'));\n if (opKeys.length > 0) {\n for (const opKey of opKeys) {\n const cubeOp = MONGO_TO_CUBE_OP[opKey];\n if (!cubeOp) continue;\n const v = wrapper[opKey];\n const values = Array.isArray(v)\n ? v.map(stringifyForCube)\n : [stringifyForCube(v)];\n out.push({ member: key, operator: cubeOp, values });\n }\n continue;\n }\n // Nested relation (e.g. {profile: {verified: true}}). Flatten with\n // dot-prefixed keys so cube field path resolution still works.\n for (const [nestedKey, nestedVal] of Object.entries(wrapper)) {\n flattenCondition({ [`${key}.${nestedKey}`]: nestedVal }, out);\n }\n continue;\n }\n\n // Implicit equality / array → in\n if (Array.isArray(raw)) {\n out.push({ member: key, operator: 'in', values: raw.map(stringifyForCube) });\n } else {\n out.push({ member: key, operator: 'equals', values: [stringifyForCube(raw)] });\n }\n }\n}\n\n/**\n * Normalize an analytics query's `where` (FilterCondition) into the\n * internal array form used by all strategies.\n */\nexport function normalizeAnalyticsFilters(query: { where?: unknown } | unknown): NormalizedAnalyticsFilter[] {\n if (!query || typeof query !== 'object') return [];\n\n const out: NormalizedAnalyticsFilter[] = [];\n const where = (query as { where?: unknown }).where;\n\n if (where && typeof where === 'object' && !Array.isArray(where)) {\n flattenCondition(where as Record<string, unknown>, out);\n }\n\n return out;\n}\n\n/** Recover a finite number from a purely-numeric token, else undefined. */\nfunction recoverNumber(s: string): number | undefined {\n if (/^-?\\d+(\\.\\d+)?$/.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) return n;\n }\n return undefined;\n}\n\n/**\n * Coerce a stringified filter value back into a runtime type for SQL\n * parameter binding. Better-sqlite3 (and most drivers) cannot bind a JS\n * boolean, so booleans are recovered as `1`/`0` integers; numbers are\n * recovered as numbers — avoiding string-vs-number mismatches against typed\n * columns.\n */\nexport function coerceFilterValueForSql(s: string): unknown {\n if (s === 'true') return 1;\n if (s === 'false') return 0;\n if (s === 'null') return null;\n return recoverNumber(s) ?? s;\n}\n\n/**\n * Coerce a stringified filter value back into a runtime type for the ObjectQL\n * aggregate engine. Unlike the SQL path, the engine compares against the\n * *stored* runtime type, so a boolean field holds a real `true`/`false` — bind\n * the boolean itself, NOT `1`/`0`, or the equality never matches.\n */\nexport function coerceFilterValueForObjectQL(s: string): unknown {\n if (s === 'true') return true;\n if (s === 'false') return false;\n if (s === 'null') return null;\n return recoverNumber(s) ?? s;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { FilterCondition } from '@objectstack/spec/data';\n\n/**\n * Compile an RLS / tenant read-scope `FilterCondition` into a parameterized,\n * alias-qualified SQL predicate (ADR-0021 D-C).\n *\n * This is the single, security-critical translation point between the\n * canonical Mongo-style filter the `RLSCompiler` emits and the raw SQL the\n * analytics `NativeSQLStrategy` runs. It is deliberately:\n *\n * - **Fail-closed.** Any operator, value shape, or identifier it cannot\n * translate THROWS. A read-scope predicate must never be silently dropped —\n * dropping it would run the query unscoped and leak cross-tenant data.\n * - **Injection-safe.** Field/alias identifiers are validated against a strict\n * snake_case pattern and every value is bound as a `?` placeholder (the\n * strategy renumbers `?` → `$N`). No value is ever interpolated into SQL.\n * - **Alias-qualified.** Bare fields become `\"alias\".\"field\"` so the same\n * predicate applies to the base table or any joined table.\n *\n * Supports the operators the RLS layer and common policies emit: implicit\n * equality, `$eq/$ne/$gt/$gte/$lt/$lte/$in/$nin/$between/$contains/$notContains/\n * $startsWith/$endsWith/$null/$exists`, and `$and/$or/$not` combinators.\n */\n\nconst IDENT = /^[a-z_][a-z0-9_]*$/i;\n\nfunction quoteIdent(name: string, kind: string): string {\n if (typeof name !== 'string' || !IDENT.test(name)) {\n throw new Error(`[read-scope-sql] unsafe ${kind} identifier \"${String(name)}\" — refusing to build read scope (fail-closed).`);\n }\n return `\"${name}\"`;\n}\n\nexport function compileScopedFilterToSql(\n filter: FilterCondition,\n alias: string,\n): { sql: string; params: unknown[] } {\n const quotedAlias = quoteIdent(alias, 'alias');\n const params: unknown[] = [];\n const sql = compileNode(filter, quotedAlias, params);\n return { sql, params };\n}\n\n/** Compile a filter node into a boolean SQL expression ('' = empty/no constraint). */\nfunction compileNode(node: unknown, qAlias: string, params: unknown[]): string {\n if (node === null || typeof node !== 'object' || Array.isArray(node)) {\n throw new Error('[read-scope-sql] read scope must be a filter object (fail-closed).');\n }\n const clauses: string[] = [];\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n if (key === '$and' || key === '$or') {\n if (!Array.isArray(value) || value.length === 0) {\n throw new Error(`[read-scope-sql] \"${key}\" requires a non-empty array (fail-closed).`);\n }\n const parts = (value as unknown[])\n .map((child) => compileNode(child, qAlias, params))\n .filter((s) => s.length > 0);\n if (parts.length === 0) continue;\n const joiner = key === '$and' ? ' AND ' : ' OR ';\n clauses.push(`(${parts.join(joiner)})`);\n } else if (key === '$not') {\n const inner = compileNode(value, qAlias, params);\n if (inner) clauses.push(`NOT (${inner})`);\n } else if (key.startsWith('$')) {\n throw new Error(`[read-scope-sql] unsupported top-level operator \"${key}\" (fail-closed).`);\n } else {\n clauses.push(compileField(key, value, qAlias, params));\n }\n }\n return clauses.join(' AND ');\n}\n\n/** Compile a single `field: value | { $op: ... }` entry. */\nfunction compileField(field: string, value: unknown, qAlias: string, params: unknown[]): string {\n const col = `${qAlias}.${quoteIdent(field, 'field')}`;\n\n // Scalar / null → implicit equality.\n if (value === null) return `${col} IS NULL`;\n if (typeof value !== 'object' || value instanceof Date) {\n params.push(value);\n return `${col} = ?`;\n }\n if (Array.isArray(value)) {\n throw new Error(`[read-scope-sql] bare array value for \"${field}\" — use { $in: [...] } (fail-closed).`);\n }\n\n const ops = value as Record<string, unknown>;\n const keys = Object.keys(ops);\n // A value object must be ALL operators; a non-$ key means a nested relation,\n // which a flat read scope cannot join — fail closed.\n if (keys.length === 0 || keys.some((k) => !k.startsWith('$'))) {\n throw new Error(`[read-scope-sql] \"${field}\" has a nested/relation value which is not supported in a read scope (fail-closed).`);\n }\n\n const parts: string[] = [];\n for (const op of keys) {\n parts.push(compileOperator(col, op, ops[op], field, params));\n }\n return parts.length === 1 ? parts[0] : `(${parts.join(' AND ')})`;\n}\n\nfunction bind(params: unknown[], v: unknown): string {\n params.push(v);\n return '?';\n}\n\nfunction compileOperator(col: string, op: string, val: unknown, field: string, params: unknown[]): string {\n switch (op) {\n case '$eq': return val === null ? `${col} IS NULL` : `${col} = ${bind(params, val)}`;\n case '$ne': return val === null ? `${col} IS NOT NULL` : `${col} <> ${bind(params, val)}`;\n case '$gt': return `${col} > ${bind(params, val)}`;\n case '$gte': return `${col} >= ${bind(params, val)}`;\n case '$lt': return `${col} < ${bind(params, val)}`;\n case '$lte': return `${col} <= ${bind(params, val)}`;\n case '$in': {\n if (!Array.isArray(val)) throw new Error(`[read-scope-sql] $in for \"${field}\" needs an array (fail-closed).`);\n if (val.length === 0) return '1 = 0'; // IN () matches nothing — safe\n return `${col} IN (${val.map((v) => bind(params, v)).join(', ')})`;\n }\n case '$nin': {\n if (!Array.isArray(val)) throw new Error(`[read-scope-sql] $nin for \"${field}\" needs an array (fail-closed).`);\n if (val.length === 0) return '1 = 1'; // NOT IN () excludes nothing\n return `${col} NOT IN (${val.map((v) => bind(params, v)).join(', ')})`;\n }\n case '$between': {\n if (!Array.isArray(val) || val.length !== 2) throw new Error(`[read-scope-sql] $between for \"${field}\" needs [min,max] (fail-closed).`);\n return `${col} BETWEEN ${bind(params, val[0])} AND ${bind(params, val[1])}`;\n }\n case '$contains': return `${col} LIKE ${bind(params, `%${String(val)}%`)}`;\n case '$notContains': return `${col} NOT LIKE ${bind(params, `%${String(val)}%`)}`;\n case '$startsWith': return `${col} LIKE ${bind(params, `${String(val)}%`)}`;\n case '$endsWith': return `${col} LIKE ${bind(params, `%${String(val)}`)}`;\n case '$null': return val ? `${col} IS NULL` : `${col} IS NOT NULL`;\n case '$exists': return val ? `${col} IS NOT NULL` : `${col} IS NULL`;\n default:\n throw new Error(`[read-scope-sql] unsupported operator \"${op}\" on \"${field}\" (fail-closed).`);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { AnalyticsQuery, AnalyticsResult } from '@objectstack/spec/contracts';\nimport type { Cube } from '@objectstack/spec/data';\nimport type { AnalyticsStrategy, StrategyContext } from './types.js';\nimport { normalizeAnalyticsFilters, coerceFilterValueForSql } from './filter-normalizer.js';\nimport { compileScopedFilterToSql } from '../read-scope-sql.js';\n\n/**\n * NativeSQLStrategy — Priority 1\n *\n * Pushes the analytics query down to the database as a native SQL statement.\n * This is the most efficient path and is preferred whenever the backing driver\n * supports raw SQL execution (e.g. Postgres, MySQL, SQLite).\n */\nexport class NativeSQLStrategy implements AnalyticsStrategy {\n readonly name = 'NativeSQLStrategy';\n readonly priority = 10;\n\n canHandle(query: AnalyticsQuery, ctx: StrategyContext): boolean {\n if (!query.cube) return false;\n // This strategy groups by the raw column expression (`GROUP BY <col>`) and\n // emits no `date_trunc` — it cannot bucket a date dimension to a coarser\n // granularity, nor resolve buckets on a non-UTC calendar. When the query\n // asks for granularity bucketing we therefore DECLINE so the lower-priority\n // ObjectQLStrategy handles it via `engine.aggregate` (native date_trunc when\n // UTC-safe, else uniform in-memory bucketing). Without this, a date-bucketed\n // query silently grouped by the raw timestamp — one bucket per row — and a\n // non-UTC reference timezone was ignored entirely (ADR-0053 Phase 2, #1982).\n if (query.timeDimensions?.some((td) => !!td.granularity)) return false;\n // ADR-0062 D6 — DECLINE federated (external-datasource) objects. This\n // strategy hand-compiles `FROM \"<object>\"` and bare column references, which\n // bypass the driver's physical-table resolution (`external.remoteName` /\n // `remoteSchema` / `columnMap`) and would query the WRONG table. Routing the\n // query to the lower-priority ObjectQL aggregate path keeps it correct —\n // that path goes through the driver's `getBuilder` (#2138/#2149). Applies to\n // the base object AND any joined object (a join would also hit the wrong\n // table). Until native-SQL learns the driver's resolution, \"disabled\" beats\n // \"silently wrong\".\n if (typeof ctx.isExternalObject === 'function') {\n const cube = ctx.getCube(query.cube);\n if (cube) {\n if (ctx.isExternalObject(this.extractObjectName(cube))) return false;\n const joinTargets = cube.joins ? Object.values(cube.joins) : [];\n for (const j of joinTargets) {\n const joinedObject = (j as { name?: string })?.name;\n if (joinedObject && ctx.isExternalObject(joinedObject)) return false;\n }\n }\n }\n const caps = ctx.queryCapabilities(query.cube);\n return caps.nativeSql && typeof ctx.executeRawSql === 'function';\n }\n\n async execute(query: AnalyticsQuery, ctx: StrategyContext): Promise<AnalyticsResult> {\n const { sql, params } = await this.generateSql(query, ctx);\n const cube = ctx.getCube(query.cube!)!;\n const objectName = this.extractObjectName(cube);\n\n const rows = await ctx.executeRawSql!(objectName, sql, params);\n\n // Build field metadata\n const fields = this.buildFieldMeta(query, cube);\n\n return { rows, fields, sql };\n }\n\n async generateSql(query: AnalyticsQuery, ctx: StrategyContext): Promise<{ sql: string; params: unknown[] }> {\n const cube = ctx.getCube(query.cube!);\n if (!cube) {\n throw new Error(`Cube not found: ${query.cube}`);\n }\n\n const params: unknown[] = [];\n const selectClauses: string[] = [];\n const groupByClauses: string[] = [];\n const tableName = this.extractObjectName(cube);\n // Map of relation alias → JOIN clause. Populated lazily as dotted\n // dimensions/measures/filters are resolved.\n const joins = new Map<string, string>();\n\n // Build SELECT for dimensions\n if (query.dimensions && query.dimensions.length > 0) {\n for (const dim of query.dimensions) {\n const colExpr = this.resolveDimensionSql(cube, dim, tableName, joins);\n selectClauses.push(`${colExpr} AS \"${dim}\"`);\n groupByClauses.push(colExpr);\n }\n }\n\n // Build SELECT for measures\n if (query.measures && query.measures.length > 0) {\n for (const measure of query.measures) {\n const aggExpr = this.resolveMeasureSql(cube, measure, tableName, joins);\n selectClauses.push(`${aggExpr} AS \"${measure}\"`);\n }\n }\n\n // Build WHERE clause\n const whereClauses: string[] = [];\n const normalizedFilters = normalizeAnalyticsFilters(query);\n if (normalizedFilters.length > 0) {\n for (const filter of normalizedFilters) {\n const colExpr = this.resolveFieldSql(cube, filter.member, tableName, joins);\n // Resolve the (object, column) this member binds against so the value\n // can be coerced to the column's storage form (see buildFilterClause).\n const target = this.resolveStorageTarget(cube, filter.member, tableName);\n const clause = this.buildFilterClause(colExpr, filter.operator, filter.values, params, ctx, target);\n if (clause) whereClauses.push(clause);\n }\n }\n\n // Build time dimension filters\n if (query.timeDimensions && query.timeDimensions.length > 0) {\n for (const td of query.timeDimensions) {\n const colExpr = this.resolveFieldSql(cube, td.dimension, tableName, joins);\n if (td.dateRange) {\n const range = Array.isArray(td.dateRange) ? td.dateRange : [td.dateRange, td.dateRange];\n if (range.length === 2) {\n // Same epoch-vs-text root cause as buildFilterClause: a dateRange on a\n // SQLite `Field.datetime` column compares ISO TEXT against an INTEGER\n // epoch and matches nothing. Coerce both bounds to the storage form.\n const td2 = this.resolveStorageTarget(cube, td.dimension, tableName);\n params.push(\n this.coerceTemporal(ctx, td2, range[0]),\n this.coerceTemporal(ctx, td2, range[1]),\n );\n whereClauses.push(`${colExpr} BETWEEN $${params.length - 1} AND $${params.length}`);\n }\n }\n }\n }\n\n // ── ADR-0021 D-C — enforce the join allowlist + inject per-object RLS ──\n // 1. Reject any join not backed by a relationship the dataset declared.\n const allowed = ctx.getAllowedRelationships?.(query.cube!);\n if (allowed) {\n for (const alias of joins.keys()) {\n if (!allowed.has(alias)) {\n throw new Error(\n `[NativeSQLStrategy] join \"${alias}\" is not backed by a declared relationship on ` +\n `cube \"${query.cube}\". v1 only joins along relationships listed in the dataset's \\`include\\`.`,\n );\n }\n }\n }\n // 2. Inject the tenant/RLS read scope for the base table AND every joined\n // object — this is the predicate the raw-SQL path would otherwise skip.\n this.applyReadScope(this.extractObjectName(cube), tableName, ctx, whereClauses, params);\n for (const alias of joins.keys()) {\n // The joined OBJECT (for the RLS lookup) is the target table from the\n // cube's join map; the ALIAS is how it's referenced in SQL. These differ\n // for namespaced objects (alias `account` → object `crm_account`).\n const joinedObject = cube.joins?.[alias]?.name ?? alias;\n this.applyReadScope(joinedObject, alias, ctx, whereClauses, params);\n }\n\n let sql = `SELECT ${selectClauses.join(', ')} FROM \"${tableName}\"`;\n if (joins.size > 0) {\n sql += ' ' + Array.from(joins.values()).join(' ');\n }\n if (whereClauses.length > 0) {\n sql += ` WHERE ${whereClauses.join(' AND ')}`;\n }\n if (groupByClauses.length > 0) {\n sql += ` GROUP BY ${groupByClauses.join(', ')}`;\n }\n if (query.order && Object.keys(query.order).length > 0) {\n const orderClauses = Object.entries(query.order).map(([f, d]) => `\"${f}\" ${d.toUpperCase()}`);\n sql += ` ORDER BY ${orderClauses.join(', ')}`;\n }\n if (query.limit != null) {\n sql += ` LIMIT ${query.limit}`;\n }\n if (query.offset != null) {\n sql += ` OFFSET ${query.offset}`;\n }\n\n return { sql, params };\n }\n\n // ── Helpers ──────────────────────────────────────────────────────\n\n /**\n * ADR-0021 D-C — inject an object's read scope (tenant + RLS predicate) into\n * the WHERE clause. The scope is a canonical `FilterCondition` (what the\n * RLSCompiler emits); `compileScopedFilterToSql` turns it into alias-qualified,\n * parameterized SQL (fail-closed — it throws rather than drop a predicate).\n * The `?` placeholders are then renumbered into the strategy's `$N` scheme.\n * No-op when the runtime provides no scope hook (the caller is then\n * responsible for isolation — see contract note).\n */\n private applyReadScope(\n objectName: string,\n alias: string,\n ctx: StrategyContext,\n whereClauses: string[],\n params: unknown[],\n ): void {\n if (typeof ctx.getReadScope !== 'function') return;\n const filter = ctx.getReadScope(objectName);\n if (filter === undefined || filter === null) return;\n const { sql, params: scopeParams } = compileScopedFilterToSql(filter, alias);\n if (!sql) return;\n let i = 0;\n const rendered = sql.replace(/\\?/g, () => {\n params.push(scopeParams[i++]);\n return `$${params.length}`;\n });\n whereClauses.push(`(${rendered})`);\n }\n\n /** SQL-safe join alias for a relationship path (dots → `__`); single-segment\n * paths are unchanged. Mirrors the dataset compiler's `cube.joins` keying so\n * alias, allowlist, and per-hop RLS all agree on one valid identifier. */\n private joinAlias(path: string): string {\n return path.replace(/\\./g, '__');\n }\n\n /**\n * Resolve a dimension/measure/filter SQL expression that may reference a\n * related table via dot notation (e.g. `account.industry`).\n *\n * A dotted `sql` is a relationship PATH (ADR-0071 multi-hop): every segment\n * but the last is a to-one relationship hop, the last is the column. Each hop\n * synthesises a `LEFT JOIN` aliased by its full path prefix, chained\n * parent→child. The convention (matching the auto-cube generator and\n * ObjectStack object schemas) for a single hop is:\n *\n * <parentTable>.<lookupField> = <lookupField>.id\n *\n * i.e. the lookup field name on the parent table equals the related\n * table name. This holds for all `Field.lookup({ object: '...' })`\n * declarations where the field is named after its target object.\n *\n * Returns the qualified SQL reference (e.g. `\"account\".\"industry\"`).\n * Pure column references (no dot) are returned as-is.\n */\n private qualifyAndRegisterJoin(\n rawSql: string,\n parentTable: string,\n joins: Map<string, string>,\n cube?: Cube,\n ): string {\n if (!rawSql.includes('.')) {\n // Base-table column. When the cube can join other tables, a bare column\n // that also exists on a joined table (e.g. base `status` vs joined\n // `account.status`) makes the SQL engine raise \"ambiguous column name\".\n // Qualify plain identifiers with the base table; leave SQL expressions\n // and `*` untouched. Single-object cubes (no joins) keep bare columns so\n // their generated SQL is byte-for-byte unchanged.\n const canJoin = !!cube?.joins && Object.keys(cube.joins).length > 0;\n if (canJoin && /^[A-Za-z_][A-Za-z0-9_]*$/.test(rawSql)) {\n return `\"${parentTable}\".\"${rawSql}\"`;\n }\n return rawSql;\n }\n // Multi-hop (ADR-0071): the dotted path IS the join chain. Every segment but\n // the last is a relationship hop; the last is the column. The join ALIAS at\n // each hop is the full path PREFIX (`account`, then `account.owner`), which\n // encodes its own parent (the prefix minus its last segment) and FK column\n // (that segment). Register one LEFT JOIN per prefix, chaining parent→child.\n const segments = rawSql.split('.');\n const column = segments[segments.length - 1];\n const hops = segments.slice(0, -1);\n if (hops.length === 0 || !column) return rawSql;\n let parentAlias = parentTable;\n let prefix = '';\n for (const seg of hops) {\n prefix = prefix ? `${prefix}.${seg}` : seg;\n const alias = this.joinAlias(prefix);\n if (!joins.has(alias)) {\n // The joined TABLE is resolved from the Cube's `joins` map (emitted by\n // the dataset compiler, keyed by the same alias); fall back to the alias\n // as the table for legacy/same-name cubes.\n const joinTable = cube?.joins?.[alias]?.name ?? alias;\n // Only emit an explicit alias when the table differs from it; when they\n // match, `LEFT JOIN \"account\" ON …` is cleaner (and back-compat).\n const tableRef = joinTable === alias ? `\"${alias}\"` : `\"${joinTable}\" \"${alias}\"`;\n joins.set(\n alias,\n `LEFT JOIN ${tableRef} ON \"${parentAlias}\".\"${seg}\" = \"${alias}\".\"id\"`,\n );\n }\n parentAlias = alias;\n }\n return `\"${parentAlias}\".\"${column}\"`;\n }\n\n /**\n * Resolve a member reference (dimension, measure, or filter field) to its\n * cube definition.\n *\n * Accepts three naming conventions:\n * 1. `<cube>.<field>` — the canonical analytics qualifier (stripped to `<field>`).\n * 2. `<lookup>.<field>` — a relation traversal (e.g. `account.industry`).\n * First tried as the literal key, then as the underscore-flattened\n * key (`account_industry`), and finally returned as a synthetic\n * definition whose `sql` is the dotted reference so the JOIN\n * machinery can pick it up.\n * 3. `<field>` — a bare field name on the cube's table.\n */\n private lookupMember(\n cube: Cube,\n member: string,\n kind: 'dimension' | 'measure',\n ): { sql: string; type?: string } | undefined {\n const bag = kind === 'dimension' ? cube.dimensions : cube.measures;\n // Direct hit on the registered key (handles `cube.field` and exact dotted keys).\n if (bag[member]) return bag[member];\n if (member.includes('.')) {\n const [first, ...rest] = member.split('.');\n const tail = rest.join('.');\n // `<cube>.<field>` style.\n if (first === cube.name && bag[tail]) return bag[tail];\n // Plain second-segment lookup (legacy behaviour).\n if (bag[tail]) return bag[tail];\n // Underscore-flattened relation lookup (e.g. `account_industry`).\n const flat = member.replace(/\\./g, '_');\n if (bag[flat]) return bag[flat];\n // Synthetic relation traversal — let qualifyAndRegisterJoin handle it.\n if (kind === 'dimension') {\n return { sql: member, type: 'string' };\n }\n } else if (bag[member]) {\n return bag[member];\n }\n return undefined;\n }\n\n private resolveDimensionSql(\n cube: Cube,\n member: string,\n parentTable: string,\n joins: Map<string, string>,\n ): string {\n const dim = this.lookupMember(cube, member, 'dimension');\n const raw = dim ? dim.sql : (member.includes('.') ? member.split('.')[1] : member);\n return this.qualifyAndRegisterJoin(raw, parentTable, joins, cube);\n }\n\n private resolveMeasureSql(\n cube: Cube,\n member: string,\n parentTable: string,\n joins: Map<string, string>,\n ): string {\n const measure = this.lookupMember(cube, member, 'measure') as\n | { sql: string; type: string }\n | undefined;\n if (!measure) return `COUNT(*)`;\n\n const col = measure.sql === '*'\n ? '*'\n : this.qualifyAndRegisterJoin(measure.sql, parentTable, joins, cube);\n switch (measure.type) {\n case 'count': return 'COUNT(*)';\n case 'sum': return `SUM(${col})`;\n case 'avg': return `AVG(${col})`;\n case 'min': return `MIN(${col})`;\n case 'max': return `MAX(${col})`;\n case 'count_distinct': return `COUNT(DISTINCT ${col})`;\n default: return `COUNT(*)`;\n }\n }\n\n private resolveFieldSql(\n cube: Cube,\n member: string,\n parentTable: string,\n joins: Map<string, string>,\n ): string {\n const dim = this.lookupMember(cube, member, 'dimension');\n if (dim) return this.qualifyAndRegisterJoin(dim.sql, parentTable, joins, cube);\n const measure = this.lookupMember(cube, member, 'measure');\n if (measure) return this.qualifyAndRegisterJoin(measure.sql, parentTable, joins, cube);\n const fieldName = member.includes('.') ? member.split('.')[1] : member;\n return fieldName;\n }\n\n /**\n * Resolve the (object, column) a filter member binds against, so its\n * comparand can be coerced to that column's on-disk storage form.\n *\n * Mirrors `resolveFieldSql`'s `sql` resolution but yields the *logical*\n * target rather than the qualified SQL:\n * - A dotted column (`account.region`, emitted for a relation traversal)\n * belongs to the JOINED object — resolve the alias → target table via the\n * cube's `joins` map (alias `account` → object `crm_account` when\n * namespaced) and take the tail as the column.\n * - Otherwise the column lives on the cube's BASE table. Use the dimension's\n * resolved `sql` (the real column, which may differ from the member name,\n * e.g. dimension `assessed` → column `assessed_at`) rather than the member.\n */\n private resolveStorageTarget(\n cube: Cube,\n member: string,\n baseTable: string,\n ): { object: string; field: string } {\n const dim = this.lookupMember(cube, member, 'dimension');\n const measure = dim ? undefined : this.lookupMember(cube, member, 'measure');\n const rawSql = dim?.sql ?? measure?.sql ?? (member.includes('.') ? member.split('.').slice(1).join('.') : member);\n\n if (rawSql.includes('.')) {\n // Multi-hop (ADR-0071): the column's owning object is the join at the\n // relationship PATH (all segments but the last); the column is the last.\n const segments = rawSql.split('.');\n const field = segments[segments.length - 1];\n const relPath = segments.slice(0, -1).join('.');\n const object = cube.joins?.[this.joinAlias(relPath)]?.name ?? relPath;\n return { object, field };\n }\n return { object: baseTable, field: rawSql };\n }\n\n /**\n * Apply the storage-form coercion for a single comparand. Prefers the\n * driver-backed `coerceTemporalFilterValue` hook (single source of truth for\n * the date/datetime storage convention — see StrategyContext); when the hook\n * is absent, or returns the value unchanged (the field is not a temporal\n * column, or the dialect stores it as a native timestamp), falls back to the\n * generic boolean/number recovery so non-temporal typed columns still bind\n * correctly.\n */\n private coerceTemporal(\n ctx: StrategyContext,\n target: { object: string; field: string },\n value: string,\n ): unknown {\n if (typeof ctx.coerceTemporalFilterValue === 'function') {\n const coerced = ctx.coerceTemporalFilterValue(target.object, target.field, value);\n // Hook returns the value untouched for non-temporal / native-timestamp\n // columns; only short-circuit when it actually changed the value.\n if (coerced !== value) return coerced;\n }\n return coerceFilterValueForSql(value);\n }\n\n private buildFilterClause(\n col: string,\n operator: string,\n values: string[] | undefined,\n params: unknown[],\n ctx: StrategyContext,\n target: { object: string; field: string },\n ): string | null {\n const opMap: Record<string, string> = {\n equals: '=', notEquals: '!=', gt: '>', gte: '>=', lt: '<', lte: '<=',\n contains: 'LIKE', notContains: 'NOT LIKE',\n };\n\n if (operator === 'set') return `${col} IS NOT NULL`;\n if (operator === 'notSet') return `${col} IS NULL`;\n\n if (operator === 'in' || operator === 'notIn') {\n if (!values || values.length === 0) return null;\n // Dates can legitimately appear in an `in`/`notIn` set (e.g. a multi-day\n // KPI), so coerce each element to the column's storage form too — same\n // SQLite epoch-vs-text root cause as the scalar operators below.\n const placeholders = values.map(v => { params.push(this.coerceTemporal(ctx, target, v)); return `$${params.length}`; }).join(', ');\n return `${col} ${operator === 'in' ? 'IN' : 'NOT IN'} (${placeholders})`;\n }\n\n const sqlOp = opMap[operator];\n if (!sqlOp || !values || values.length === 0) return null;\n\n if (operator === 'contains' || operator === 'notContains') {\n params.push(`%${values[0]}%`);\n } else {\n // Coerce so booleans/numbers bind as their native SQL types AND so a\n // relative-date / ISO-string comparand on a SQLite `Field.datetime`\n // column is converted to its INTEGER epoch storage form. Without this a\n // dashboard filter like `assessed_at >= '2025-06-18'` compiles to a\n // TEXT-vs-INTEGER affinity compare that is always false → \"No rows\",\n // even though the rows exist (the confirmed time-series chart bug).\n params.push(this.coerceTemporal(ctx, target, values[0]));\n }\n return `${col} ${sqlOp} $${params.length}`;\n }\n\n private extractObjectName(cube: Cube): string {\n return cube.sql.trim();\n }\n\n private buildFieldMeta(query: AnalyticsQuery, cube: Cube): Array<{ name: string; type: string }> {\n const fields: Array<{ name: string; type: string }> = [];\n if (query.dimensions) {\n for (const dim of query.dimensions) {\n const d = this.lookupMember(cube, dim, 'dimension');\n fields.push({ name: dim, type: d?.type || 'string' });\n }\n }\n if (query.measures) {\n for (const m of query.measures) {\n fields.push({ name: m, type: 'number' });\n }\n }\n return fields;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { AnalyticsQuery, AnalyticsResult } from '@objectstack/spec/contracts';\nimport type { Cube } from '@objectstack/spec/data';\nimport type { AnalyticsStrategy, StrategyContext } from './types.js';\nimport { normalizeAnalyticsFilters, coerceFilterValueForObjectQL } from './filter-normalizer.js';\n\n/**\n * ObjectQLStrategy — Priority 2\n *\n * Translates an analytics query into an ObjectQL `engine.aggregate()` call.\n * This path works with any driver that supports the ObjectQL aggregate AST\n * (Postgres, Mongo, SQLite, etc.) without requiring raw SQL access.\n */\nexport class ObjectQLStrategy implements AnalyticsStrategy {\n readonly name = 'ObjectQLStrategy';\n readonly priority = 20;\n\n canHandle(query: AnalyticsQuery, ctx: StrategyContext): boolean {\n if (!query.cube) return false;\n const caps = ctx.queryCapabilities(query.cube);\n return caps.objectqlAggregate && typeof ctx.executeAggregate === 'function';\n }\n\n async execute(query: AnalyticsQuery, ctx: StrategyContext): Promise<AnalyticsResult> {\n const cube = ctx.getCube(query.cube!)!;\n const objectName = this.extractObjectName(cube);\n\n // Build groupBy from dimensions, honouring `timeDimensions` granularity.\n // A date dimension with a granularity becomes a STRUCTURED groupBy item\n // `{ field, dateGranularity }` — which `engine.aggregate()` buckets (driver\n // date_trunc or in-memory). Without this the ObjectQL path grouped raw\n // timestamps (one bucket per row) and date-bucketed dataset widgets never\n // matched their legacy `categoryGranularity` counterpart.\n type GroupByItem = string | { field: string; dateGranularity: string };\n const granByDim = new Map<string, string>();\n for (const td of query.timeDimensions ?? []) {\n if (td.granularity) granByDim.set(td.dimension, td.granularity);\n }\n const groupBy: GroupByItem[] = [];\n if (query.dimensions && query.dimensions.length > 0) {\n for (const dim of query.dimensions) {\n const field = this.resolveFieldName(cube, dim, 'dimension');\n const gran = granByDim.get(dim);\n groupBy.push(gran ? { field, dateGranularity: gran } : field);\n granByDim.delete(dim);\n }\n }\n // Time dimensions not also listed in `dimensions` still bucket + group.\n for (const [dim, gran] of granByDim) {\n groupBy.push({ field: this.resolveFieldName(cube, dim, 'dimension'), dateGranularity: gran });\n }\n\n // Build aggregations from measures\n const aggregations: Array<{ field: string; method: string; alias: string }> = [];\n if (query.measures && query.measures.length > 0) {\n for (const measure of query.measures) {\n const { field, method } = this.resolveMeasureAggregation(cube, measure);\n aggregations.push({ field, method, alias: measure });\n }\n }\n\n // Build filter from query filters. A single field may carry MULTIPLE\n // operators (e.g. a range `{$gte, $lte}` from `close_date` between two\n // bounds). Merge same-field operator objects instead of overwriting, or a\n // range would silently lose a bound (only the last operator would survive).\n const filter: Record<string, unknown> = {};\n const normalizedFilters = normalizeAnalyticsFilters(query);\n if (normalizedFilters.length > 0) {\n for (const f of normalizedFilters) {\n const fieldName = this.resolveFieldName(cube, f.member, 'any');\n const converted = this.convertFilter(f.operator, f.values);\n const existing = filter[fieldName];\n const mergeable = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === 'object' && !Array.isArray(v);\n filter[fieldName] = mergeable(existing) && mergeable(converted)\n ? { ...existing, ...converted }\n : converted;\n }\n }\n\n const rows = await ctx.executeAggregate!(objectName, {\n // Structured groupBy items ({field, dateGranularity}) pass through the\n // executeAggregate bridge to engine.aggregate, which buckets them. The\n // contract types groupBy as string[]; the cast carries the richer shape.\n groupBy: groupBy.length > 0 ? (groupBy as unknown as string[]) : undefined,\n aggregations: aggregations.length > 0 ? aggregations : undefined,\n filter: Object.keys(filter).length > 0 ? filter : undefined,\n // ADR-0053 Phase 2 (D2): forward the reference tz so date buckets resolve\n // on that zone's calendar days. A non-UTC zone makes the engine bucket\n // in-memory (uniform across drivers); UTC/unset keeps the DB fast path.\n timezone: query.timezone,\n });\n\n // Remap short field names back to cube-qualified names\n const mappedRows = rows.map(row => {\n const mapped: Record<string, unknown> = {};\n if (query.dimensions) {\n for (const dim of query.dimensions) {\n const shortName = this.resolveFieldName(cube, dim, 'dimension');\n if (shortName in row) mapped[dim] = row[shortName];\n }\n }\n if (query.measures) {\n for (const m of query.measures) {\n // Alias was set to the full measure name\n if (m in row) mapped[m] = row[m];\n }\n }\n return mapped;\n });\n\n const fields = this.buildFieldMeta(query, cube);\n return { rows: mappedRows, fields };\n }\n\n async generateSql(query: AnalyticsQuery, ctx: StrategyContext): Promise<{ sql: string; params: unknown[] }> {\n const cube = ctx.getCube(query.cube!);\n if (!cube) {\n throw new Error(`Cube not found: ${query.cube}`);\n }\n\n // Generate a representative SQL even though ObjectQL uses AST internally\n const selectParts: string[] = [];\n const groupByParts: string[] = [];\n\n if (query.dimensions) {\n for (const dim of query.dimensions) {\n const col = this.resolveFieldName(cube, dim, 'dimension');\n selectParts.push(`${col} AS \"${dim}\"`);\n groupByParts.push(col);\n }\n }\n if (query.measures) {\n for (const m of query.measures) {\n const { field, method } = this.resolveMeasureAggregation(cube, m);\n const aggSql = method === 'count' ? 'COUNT(*)' : `${method.toUpperCase()}(${field})`;\n selectParts.push(`${aggSql} AS \"${m}\"`);\n }\n }\n\n const tableName = this.extractObjectName(cube);\n let sql = `SELECT ${selectParts.join(', ')} FROM \"${tableName}\"`;\n if (groupByParts.length > 0) {\n sql += ` GROUP BY ${groupByParts.join(', ')}`;\n }\n\n return { sql, params: [] };\n }\n\n // ── Helpers ──────────────────────────────────────────────────────\n\n /**\n * Resolve a member ref to a `{ sql, type? }` definition.\n *\n * Mirrors `NativeSQLStrategy.lookupMember` so the two strategies\n * accept the same naming conventions:\n * 1. `<cube>.<field>` — canonical analytics qualifier.\n * 2. `<lookup>.<field>` — relation traversal (e.g. `account.industry`).\n * Tries literal key, then underscore-flattened key, then falls\n * back to a synthetic dim whose `sql` is the dotted path so the\n * ObjectQL aggregate engine can traverse it via the lookup field.\n * 3. `<field>` — bare column on the cube's table.\n */\n private lookupMember(\n cube: Cube,\n member: string,\n kind: 'dimension' | 'measure',\n ): { sql: string; type?: string } | undefined {\n const bag = kind === 'dimension' ? cube.dimensions : cube.measures;\n if (bag[member]) return bag[member];\n if (member.includes('.')) {\n const [first, ...rest] = member.split('.');\n const tail = rest.join('.');\n if (first === cube.name && bag[tail]) return bag[tail];\n if (bag[tail]) return bag[tail];\n const flat = member.replace(/\\./g, '_');\n if (bag[flat]) return bag[flat];\n if (kind === 'dimension') return { sql: member, type: 'string' };\n } else if (bag[member]) {\n return bag[member];\n }\n return undefined;\n }\n\n private resolveFieldName(cube: Cube, member: string, kind: 'dimension' | 'measure' | 'any'): string {\n if (kind === 'dimension' || kind === 'any') {\n const dim = this.lookupMember(cube, member, 'dimension');\n if (dim) return dim.sql.replace(/^\\$/, '');\n }\n if (kind === 'measure' || kind === 'any') {\n const measure = this.lookupMember(cube, member, 'measure');\n if (measure) return measure.sql.replace(/^\\$/, '');\n }\n return member.includes('.') ? member.split('.')[1] : member;\n }\n\n private resolveMeasureAggregation(cube: Cube, measureName: string): { field: string; method: string } {\n const direct = this.lookupMember(cube, measureName, 'measure') as\n | { sql: string; type: string }\n | undefined;\n if (direct) {\n return {\n field: direct.sql.replace(/^\\$/, ''),\n method: direct.type === 'count_distinct' ? 'count_distinct' : direct.type,\n };\n }\n // Accept `${field}_${type}` aliases (e.g. 'amount_sum') for measures whose\n // canonical name is just `${field}` (e.g. measure 'amount' of type 'sum').\n // This matches the convention used by clients that build measure names\n // from (field, function) pairs (e.g. the data-objectstack adapter).\n const fieldName = measureName.includes('.') ? measureName.split('.')[1] : measureName;\n const aggTypes = ['count', 'sum', 'avg', 'min', 'max', 'count_distinct'];\n for (const type of aggTypes) {\n const suffix = `_${type}`;\n if (fieldName.endsWith(suffix)) {\n const baseField = fieldName.slice(0, -suffix.length);\n const candidate = cube.measures[baseField];\n if (candidate && candidate.type === type) {\n return {\n field: candidate.sql.replace(/^\\$/, ''),\n method: candidate.type === 'count_distinct' ? 'count_distinct' : candidate.type,\n };\n }\n }\n }\n return { field: '*', method: 'count' };\n }\n\n private convertFilter(operator: string, values?: string[]): unknown {\n if (operator === 'set') return { $ne: null };\n if (operator === 'notSet') return null;\n if (!values || values.length === 0) return undefined;\n\n const v0 = coerceFilterValueForObjectQL(values[0]);\n const all = values.map(coerceFilterValueForObjectQL);\n switch (operator) {\n case 'equals': return v0;\n case 'notEquals': return { $ne: v0 };\n case 'gt': return { $gt: v0 };\n case 'gte': return { $gte: v0 };\n case 'lt': return { $lt: v0 };\n case 'lte': return { $lte: v0 };\n case 'contains': return { $regex: values[0] };\n case 'in': return { $in: all };\n case 'notIn': return { $nin: all };\n default: return v0;\n }\n }\n\n private extractObjectName(cube: Cube): string {\n return cube.sql.trim();\n }\n\n private buildFieldMeta(query: AnalyticsQuery, cube: Cube): Array<{ name: string; type: string }> {\n const fields: Array<{ name: string; type: string }> = [];\n if (query.dimensions) {\n for (const dim of query.dimensions) {\n const d = this.lookupMember(cube, dim, 'dimension');\n fields.push({ name: dim, type: d?.type || 'string' });\n }\n }\n if (query.measures) {\n for (const m of query.measures) {\n fields.push({ name: m, type: 'number' });\n }\n }\n return fields;\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Cube, Metric, Dimension as CubeDimension, CubeJoin } from '@objectstack/spec/data';\nimport type { Dataset, DatasetMeasure, DatasetDimension } from '@objectstack/spec/ui';\nimport type { FilterCondition } from '@objectstack/spec/data';\n\n/**\n * Dataset → Cube compiler (ADR-0021 D-A=(c), WS2).\n *\n * Lowers a declarative `dataset` (base object + included relationships +\n * declared dimensions/measures + derived measures) into the existing Cube\n * analytics runtime model. The author never writes an `ON` clause: joins are\n * DERIVED from the `include` relationship names and the dotted `relationship.field`\n * references on dimensions/measures, matching the NativeSQLStrategy convention\n * `<parentTable>.<relationship> = <relationship>.id`.\n *\n * Safety (D-C): every dotted field reference must point at a relationship that\n * the dataset explicitly declared in `include`; otherwise the compile fails.\n * The returned `allowedRelationships` set is the join allowlist the strategy\n * enforces at SQL-build time.\n */\n\n/** Operators v1 does NOT compile to the Cube SQL switch — surfaced as a clear error. */\nconst UNSUPPORTED_AGGREGATES = new Set(['array_agg', 'string_agg']);\n\nexport interface DerivedMeasureSpec {\n name: string;\n op: 'ratio' | 'sum' | 'difference' | 'product';\n of: string[];\n}\n\nexport interface CompiledDataset {\n /** The Cube the dataset compiles to (consumed by the strategy chain). */\n cube: Cube;\n /**\n * Every join alias the dataset may use — each declared `include` path AND its\n * intermediate prefixes (ADR-0071). The join allowlist (D-C): the\n * NativeSQLStrategy rejects any join alias not in this set.\n */\n allowedRelationships: Set<string>;\n /** Derived measures, computed post-aggregation by the executor (Q1). */\n derived: DerivedMeasureSpec[];\n /** Definition-level filter (the dataset's intrinsic scope). */\n filter?: FilterCondition;\n /** Per-measure scoped filters, keyed by measure name (applied by executor). */\n measureFilters: Record<string, FilterCondition>;\n}\n\n/**\n * The related object reached by traversing a relationship: its logical object\n * name (used to resolve the NEXT hop in a multi-hop chain — ADR-0071) and its\n * physical table name (the join target).\n */\nexport interface RelationshipTarget {\n object: string;\n table: string;\n}\n\n/**\n * Resolves a relationship name on a base object to the related object/table,\n * using the runtime's object graph. Optional: when omitted the compiler trusts\n * the declared `include` names (the NativeSQLStrategy convention assumes the\n * relationship name equals the related table name).\n *\n * May return a bare table-name `string` (legacy single-hop: object name is\n * assumed equal to the table) or a {@link RelationshipTarget} (required to\n * traverse further along a multi-hop path, where object differs from table for\n * namespaced objects).\n */\nexport type RelationshipResolver = (\n baseObject: string,\n relationshipName: string,\n) => string | RelationshipTarget | undefined;\n\n/** Map a dataset measure's aggregate to the Cube metric `type`. */\nfunction aggregateToMetricType(m: DatasetMeasure): Metric['type'] {\n // Only reached for non-derived measures, where the spec refinement guarantees\n // an aggregate; guard defensively so the type narrows from `optional`.\n if (!m.aggregate) {\n throw new Error(`[dataset-compiler] non-derived measure \"${m.name}\" has no aggregate`);\n }\n if (UNSUPPORTED_AGGREGATES.has(m.aggregate)) {\n throw new Error(\n `[dataset-compiler] measure \"${m.name}\" uses aggregate \"${m.aggregate}\" which is ` +\n `not supported by the v1 dataset runtime (supported: count, sum, avg, min, max, count_distinct).`,\n );\n }\n return m.aggregate as Metric['type'];\n}\n\n/** Map a dataset dimension type to the Cube dimension `type`. */\nfunction dimensionType(d: DatasetDimension): CubeDimension['type'] {\n switch (d.type) {\n case 'date': return 'time';\n case 'number': return 'number';\n case 'boolean': return 'boolean';\n case 'lookup': return 'string';\n case 'string': return 'string';\n default: return 'string';\n }\n}\n\n/** The relationship PATH a dotted field traverses — all segments but the final\n * column — or null for a base-object field. E.g. `account.owner.region` →\n * `account.owner`; `account.region` → `account`; `region` → null. */\nfunction fieldRelationshipPath(field: string): string | null {\n const idx = field.lastIndexOf('.');\n return idx > 0 ? field.slice(0, idx) : null;\n}\n\n/** Max relationship hops in one `include` path — base → 3 hops = 4 objects\n * (ADR-0071; Salesforce-report-type parity). To-one chains never fan out, so\n * this is a performance/complexity guard, not a correctness limit. */\nconst MAX_JOIN_HOPS = 3;\n\n/** SQL-safe join alias for a relationship PATH. The dotted path is the author-\n * facing form; the alias replaces dots with `__` (Cube.js convention) so each\n * prefix is one valid identifier — quoted dotted identifiers are rejected by\n * the read-scope SQL guard (fail-closed). Single-segment paths are unchanged,\n * so single-hop joins stay byte-for-byte identical. */\nconst joinAlias = (path: string): string => path.replace(/\\./g, '__');\n\nexport function compileDataset(\n dataset: Dataset,\n resolver?: RelationshipResolver,\n): CompiledDataset {\n const include = dataset.include ?? [];\n\n // Resolve each declared relationship PATH into its ordered join chain, emitting\n // one Cube join per PATH PREFIX (ADR-0071 multi-hop, to-one only). The join\n // ALIAS is the full dotted path (`account.owner`), which self-describes the\n // chain: the parent alias is the path minus its last segment, the FK column is\n // that last segment. So declaring `account.owner` auto-adds the intermediate\n // `account` join, and the strategy can rebuild every `ON` from the alias alone.\n // Without a resolver, each segment's relationship name is assumed to equal both\n // the related object and its table (legacy convention / unit tests).\n const resolveHop = (fromObject: string, rel: string): RelationshipTarget => {\n if (!resolver) return { object: rel, table: rel };\n const resolved = resolver(fromObject, rel);\n if (!resolved) {\n throw new Error(\n `[dataset-compiler] dataset \"${dataset.name}\" includes relationship \"${rel}\" ` +\n `which does not exist on object \"${fromObject}\".`,\n );\n }\n return typeof resolved === 'string' ? { object: resolved, table: resolved } : resolved;\n };\n const joins: Record<string, CubeJoin> = {};\n for (const path of include) {\n const segments = path.split('.');\n if (segments.length > MAX_JOIN_HOPS) {\n throw new Error(\n `[dataset-compiler] dataset \"${dataset.name}\" include path \"${path}\" exceeds the ` +\n `${MAX_JOIN_HOPS}-hop limit (${segments.length} hops). Deeper traversal is not supported.`,\n );\n }\n let fromObject = dataset.object;\n let parentAlias = dataset.object;\n let prefix = '';\n for (const seg of segments) {\n prefix = prefix ? `${prefix}.${seg}` : seg;\n const target = resolveHop(fromObject, seg);\n const alias = joinAlias(prefix);\n if (!joins[alias]) {\n // KEY is the SQL-safe alias; `name` carries the join TABLE; the strategy\n // rebuilds the ON clause from the alias convention (`<parent>.<seg> = <alias>.id`).\n joins[alias] = {\n name: target.table,\n relationship: 'many_to_one',\n sql: `${parentAlias}.${seg} = ${prefix}.id`,\n };\n }\n fromObject = target.object;\n parentAlias = prefix;\n }\n }\n\n // The join allowlist (D-C) is every registered alias — each declared path AND\n // its intermediate prefixes — so a multi-hop field's intermediate joins pass.\n const allowedRelationships = new Set(Object.keys(joins));\n\n // Assert any dotted field only traverses a DECLARED relationship PATH (D-C).\n const assertDeclared = (field: string, ownerKind: string, ownerName: string) => {\n const relPath = fieldRelationshipPath(field);\n if (relPath && !joins[joinAlias(relPath)]) {\n throw new Error(\n `[dataset-compiler] ${ownerKind} \"${ownerName}\" references relationship path \"${relPath}\" ` +\n `via \"${field}\", but \"${relPath}\" is not declared in the dataset's \\`include\\`. ` +\n `Only fields along a declared relationship path are joinable.`,\n );\n }\n };\n\n // Compile dimensions.\n const dimensions: Record<string, CubeDimension> = {};\n for (const d of dataset.dimensions) {\n assertDeclared(d.field, 'dimension', d.name);\n const dim: CubeDimension = {\n name: d.name,\n label: typeof d.label === 'string' ? d.label : d.name,\n type: dimensionType(d),\n sql: d.field,\n };\n if (dim.type === 'time') {\n dim.granularities = d.dateGranularity\n ? [d.dateGranularity]\n : ['day', 'week', 'month', 'quarter', 'year'];\n }\n dimensions[d.name] = dim;\n }\n\n // Compile measures (non-derived → Cube metrics; derived → sidecar).\n const measures: Record<string, Metric> = {};\n const derived: DerivedMeasureSpec[] = [];\n const measureFilters: Record<string, FilterCondition> = {};\n\n for (const m of dataset.measures) {\n if (m.derived) {\n derived.push({ name: m.name, op: m.derived.op, of: m.derived.of });\n continue;\n }\n if (m.field) assertDeclared(m.field, 'measure', m.name);\n const metric: Metric = {\n name: m.name,\n label: typeof m.label === 'string' ? m.label : m.name,\n type: aggregateToMetricType(m),\n // `count` with no field aggregates over rows (*).\n sql: m.field ?? '*',\n };\n if (typeof m.format === 'string') metric.format = m.format;\n measures[m.name] = metric;\n if (m.filter) measureFilters[m.name] = m.filter;\n }\n\n const cube: Cube = {\n name: dataset.name,\n title: typeof dataset.label === 'string' ? dataset.label : dataset.name,\n sql: dataset.object,\n measures,\n dimensions,\n public: false,\n };\n if (Object.keys(joins).length > 0) cube.joins = joins;\n\n return {\n cube,\n allowedRelationships,\n derived,\n filter: dataset.filter,\n measureFilters,\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IAnalyticsService,\n AnalyticsQuery,\n AnalyticsResult,\n DatasetSelection,\n DatasetCompareTo,\n} from '@objectstack/spec/contracts';\nimport type { FilterCondition } from '@objectstack/spec/data';\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\nimport type { CompiledDataset, DerivedMeasureSpec } from './dataset-compiler.js';\n\n// Re-export the shared protocol shapes so existing importers keep working.\nexport type { DatasetSelection } from '@objectstack/spec/contracts';\n/** @deprecated use DatasetCompareTo from @objectstack/spec/contracts */\nexport type CompareTo = DatasetCompareTo;\n\n/**\n * Dataset executor (ADR-0021 WS2).\n *\n * Turns a compiled dataset + a presentation's selection (dimensions, measures,\n * runtime filter, compareTo) into one or more `AnalyticsQuery`s against the Cube\n * runtime, then post-processes the results:\n * - resolves the base measures a selection needs (including derived deps),\n * - applies measure-scoped filters via supplementary grouped queries,\n * - evaluates derived measures (ratio/sum/difference/product) row-by-row (Q1),\n * - shifts the query for `compareTo` (previousPeriod / previousYear) and\n * attaches `<measure>__compare` columns,\n * - computes server-side totals (`selection.totals.groupings`, #1753) by\n * re-running the selection per dimension subset, so matrix subtotals and\n * the grand total use each measure's true aggregate.\n *\n * RLS/tenant scoping is NOT handled here — it is enforced inside the strategy\n * via the StrategyContext read-scope hook (D-C). This layer is pure query\n * shaping + arithmetic.\n */\n\n/** AND two optional FilterConditions into one (MongoDB-style). */\nexport function combineFilters(\n a?: FilterCondition,\n b?: FilterCondition,\n): FilterCondition | undefined {\n if (a && b) return { $and: [a, b] } as FilterCondition;\n return a ?? b;\n}\n\n/**\n * Evaluate derived measures on each aggregated row, mutating a shallow copy.\n * Division by zero (and missing operands) yields `null` rather than Infinity/NaN.\n */\nexport function evaluateDerivedMeasures(\n rows: Record<string, unknown>[],\n derived: DerivedMeasureSpec[],\n): Record<string, unknown>[] {\n if (derived.length === 0) return rows;\n return rows.map((row) => {\n const out = { ...row };\n for (const d of derived) {\n out[d.name] = computeDerived(d, out);\n }\n return out;\n });\n}\n\nfunction num(v: unknown): number | null {\n if (v == null) return null;\n const n = typeof v === 'number' ? v : Number(v);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction computeDerived(d: DerivedMeasureSpec, row: Record<string, unknown>): number | null {\n const vals = d.of.map((name) => num(row[name]));\n if (vals.some((v) => v === null)) return null;\n const nums = vals as number[];\n switch (d.op) {\n case 'ratio': {\n if (nums.length < 2 || nums[1] === 0) return null;\n return nums[0] / nums[1];\n }\n case 'difference':\n return nums.slice(1).reduce((acc, v) => acc - v, nums[0]);\n case 'sum':\n return nums.reduce((acc, v) => acc + v, 0);\n case 'product':\n return nums.reduce((acc, v) => acc * v, 1);\n default:\n return null;\n }\n}\n\n// ── compareTo date math (deterministic — no Date.now) ────────────────────────\n\nfunction parseUTC(date: string): number {\n // Accepts 'YYYY-MM-DD' (and ISO datetimes); interpreted as UTC.\n const ms = Date.parse(date.length === 10 ? `${date}T00:00:00Z` : date);\n if (Number.isNaN(ms)) throw new Error(`[dataset-executor] invalid date in dateRange: \"${date}\"`);\n return ms;\n}\n\nconst DAY_MS = 86_400_000;\n\nfunction toISODate(ms: number): string {\n return new Date(ms).toISOString().slice(0, 10);\n}\n\nfunction shiftYear(date: string, years: number): string {\n const d = new Date(parseUTC(date));\n d.setUTCFullYear(d.getUTCFullYear() + years);\n return toISODate(d.getTime());\n}\n\n/** Compute the comparison window for a [start,end] range. */\nexport function shiftRange(range: [string, string], kind: CompareTo['kind']): [string, string] {\n const [start, end] = range;\n if (kind === 'previousYear') {\n return [shiftYear(start, -1), shiftYear(end, -1)];\n }\n // previousPeriod — the equal-length window ending the day before `start`.\n const startMs = parseUTC(start);\n const endMs = parseUTC(end);\n const lengthDays = Math.round((endMs - startMs) / DAY_MS) + 1;\n const prevEndMs = startMs - DAY_MS;\n const prevStartMs = prevEndMs - (lengthDays - 1) * DAY_MS;\n return [toISODate(prevStartMs), toISODate(prevEndMs)];\n}\n\nexport class DatasetExecutor {\n constructor(private readonly service: IAnalyticsService) {}\n\n /**\n * Execute a dataset selection and return the shaped rows (+ field metadata).\n *\n * @param context - The request's ExecutionContext, threaded into every\n * underlying `IAnalyticsService.query` so the tenant/RLS read scope is\n * applied per request (ADR-0021 D-C).\n */\n async execute(\n compiled: CompiledDataset,\n selection: DatasetSelection,\n context?: ExecutionContext,\n ): Promise<AnalyticsResult> {\n const result = await this.executeSelection(compiled, selection, context);\n\n // Server-side totals (#1753) — re-run the selection grouped by each\n // requested dimension subset, so a subtotal/grand total is the measure's\n // TRUE aggregate over the underlying rows (an avg total is the average of\n // all rows, not of bucket averages). Re-running the full pipeline keeps\n // measure-scoped filters, derived measures, and compareTo consistent with\n // the primary grid. order/limit/offset are dropped: totals cover the whole\n // selection, and an order key may reference a dimension the grouping drops.\n const groupings = selection.totals?.groupings;\n if (groupings?.length) {\n const selected = new Set(selection.dimensions ?? []);\n const totals: NonNullable<AnalyticsResult['totals']> = [];\n for (const grouping of groupings) {\n const unknown = grouping.filter((d) => !selected.has(d));\n if (unknown.length) {\n throw new Error(\n `[dataset-executor] totals grouping [${grouping.join(', ')}] is not a subset of the selected dimensions — unknown: ${unknown.join(', ')}.`,\n );\n }\n const sub = await this.executeSelection(compiled, {\n ...selection,\n dimensions: grouping,\n totals: undefined,\n order: undefined,\n limit: undefined,\n offset: undefined,\n }, context);\n totals.push({ dimensions: grouping, rows: sub.rows });\n }\n result.totals = totals;\n }\n\n return result;\n }\n\n private async executeSelection(\n compiled: CompiledDataset,\n selection: DatasetSelection,\n context?: ExecutionContext,\n ): Promise<AnalyticsResult> {\n const derivedByName = new Map(compiled.derived.map((d) => [d.name, d]));\n const selectedDerived = selection.measures\n .map((m) => derivedByName.get(m))\n .filter((d): d is DerivedMeasureSpec => !!d);\n\n // Base measures = selected non-derived + dependencies of selected derived.\n const baseMeasures = new Set<string>();\n for (const m of selection.measures) {\n if (!derivedByName.has(m)) baseMeasures.add(m);\n }\n for (const d of selectedDerived) {\n for (const dep of d.of) baseMeasures.add(dep);\n }\n\n // Split measures into those with a scoped filter and those without.\n const unfiltered: string[] = [];\n const filtered: string[] = [];\n for (const m of baseMeasures) {\n (compiled.measureFilters[m] ? filtered : unfiltered).push(m);\n }\n\n const baseFilter = combineFilters(compiled.filter, selection.runtimeFilter);\n const dimensions = selection.dimensions ?? [];\n\n // Primary query: all unfiltered base measures in one pass. When every base\n // measure is filter-scoped, the supplementary queries below build the grid.\n let result: AnalyticsResult;\n if (unfiltered.length > 0 || filtered.length === 0) {\n result = await this.service.query(this.buildQuery(compiled, {\n measures: unfiltered,\n dimensions,\n where: baseFilter,\n selection,\n contextTimezone: context?.timezone,\n }), context);\n } else {\n result = { rows: [], fields: [] };\n }\n\n // Supplementary queries: one per measure-scoped filter, merged by dimension key.\n for (const m of filtered) {\n const mFilter = combineFilters(baseFilter, compiled.measureFilters[m]);\n const sub = await this.service.query(this.buildQuery(compiled, {\n measures: [m], dimensions, where: mFilter, selection,\n contextTimezone: context?.timezone,\n }), context);\n result.rows = mergeByDimensions(result.rows, sub.rows, dimensions, [m]);\n result.fields.push({ name: m, type: 'number' });\n }\n\n // compareTo — run a shifted query over the same base measures and attach.\n if (selection.compareTo) {\n const compareRows = await this.runCompare(compiled, selection, [...baseMeasures], dimensions, baseFilter, context);\n result.rows = mergeByDimensions(\n result.rows,\n compareRows,\n dimensions,\n [...baseMeasures].map((m) => `${m}__compare`),\n );\n for (const m of baseMeasures) result.fields.push({ name: `${m}__compare`, type: 'number' });\n }\n\n // Derived measures (computed from base + compare columns already present).\n result.rows = evaluateDerivedMeasures(result.rows, selectedDerived);\n for (const d of selectedDerived) result.fields.push({ name: d.name, type: 'number' });\n\n return result;\n }\n\n private buildQuery(\n compiled: CompiledDataset,\n opts: {\n measures: string[];\n dimensions: string[];\n where?: FilterCondition;\n selection: DatasetSelection;\n contextTimezone?: string;\n },\n ): AnalyticsQuery {\n const q: AnalyticsQuery = {\n cube: compiled.cube.name,\n measures: opts.measures,\n dimensions: opts.dimensions,\n // Precedence: explicit selection tz → request's reference tz\n // (ExecutionContext.timezone, ADR-0053 Phase 2) → UTC.\n timezone: opts.selection.timezone ?? opts.contextTimezone ?? 'UTC',\n };\n if (opts.where) q.where = opts.where as Record<string, unknown>;\n // Bucket selected date dimensions that declare an explicit `dateGranularity`\n // (the dataset compiled a single-entry `granularities`). Without this a date\n // dimension groups by the raw timestamp — one bucket per row, rendering epoch\n // millis on trend charts. A dimension already carried by `selection.timeDimensions`\n // (e.g. compareTo) keeps its entry; we never override it.\n const selTimeDims = opts.selection.timeDimensions ?? [];\n const selDims = new Set(selTimeDims.map((t) => t.dimension));\n const explicitTimeDims: Array<{ dimension: string; granularity: string }> = [];\n for (const name of opts.dimensions) {\n const cd = compiled.cube.dimensions[name];\n if (cd?.type === 'time' && cd.granularities?.length === 1 && !selDims.has(name)) {\n explicitTimeDims.push({ dimension: name, granularity: String(cd.granularities[0]) });\n }\n }\n const mergedTimeDims = [...selTimeDims, ...explicitTimeDims];\n if (mergedTimeDims.length > 0) q.timeDimensions = mergedTimeDims as AnalyticsQuery['timeDimensions'];\n if (opts.selection.order) q.order = opts.selection.order;\n if (opts.selection.limit != null) q.limit = opts.selection.limit;\n if (opts.selection.offset != null) q.offset = opts.selection.offset;\n return q;\n }\n\n private async runCompare(\n compiled: CompiledDataset,\n selection: DatasetSelection,\n measures: string[],\n dimensions: string[],\n baseFilter: FilterCondition | undefined,\n context?: ExecutionContext,\n ): Promise<Record<string, unknown>[]> {\n const cmp = selection.compareTo!;\n const td = (selection.timeDimensions ?? []).find((t) => t.dimension === cmp.dimension);\n if (!td || !td.dateRange) {\n throw new Error(\n `[dataset-executor] compareTo requires a timeDimension \"${cmp.dimension}\" with a dateRange.`,\n );\n }\n const range: [string, string] = Array.isArray(td.dateRange)\n ? [td.dateRange[0], td.dateRange[1] ?? td.dateRange[0]]\n : [td.dateRange, td.dateRange];\n const shifted = shiftRange(range, cmp.kind);\n const shiftedTd = (selection.timeDimensions ?? []).map((t) =>\n t.dimension === cmp.dimension ? { ...t, dateRange: shifted } : t,\n );\n const sub = await this.service.query({\n cube: compiled.cube.name,\n measures,\n dimensions,\n where: baseFilter as Record<string, unknown> | undefined,\n timeDimensions: shiftedTd,\n timezone: selection.timezone ?? context?.timezone ?? 'UTC',\n }, context);\n // Rename measure columns to `<measure>__compare` so they merge alongside primary.\n return sub.rows.map((row) => {\n const out: Record<string, unknown> = {};\n for (const dim of dimensions) out[dim] = row[dim];\n for (const m of measures) out[`${m}__compare`] = row[m];\n return out;\n });\n }\n}\n\n/**\n * Left-merge `extra` rows onto `base` rows by their dimension-key tuple,\n * copying the listed value columns. Rows in `extra` with no base match are\n * appended (outer-ish merge so comparison-only buckets still surface).\n */\nexport function mergeByDimensions(\n base: Record<string, unknown>[],\n extra: Record<string, unknown>[],\n dimensions: string[],\n valueColumns: string[],\n): Record<string, unknown>[] {\n const keyOf = (row: Record<string, unknown>) => dimensions.map((d) => String(row[d] ?? '')).join('\u0001');\n const index = new Map<string, Record<string, unknown>>();\n for (const row of base) index.set(keyOf(row), row);\n\n for (const row of extra) {\n const key = keyOf(row);\n const target = index.get(key);\n if (target) {\n for (const c of valueColumns) target[c] = row[c];\n } else {\n const fresh: Record<string, unknown> = {};\n for (const d of dimensions) fresh[d] = row[d];\n for (const c of valueColumns) fresh[c] = row[c];\n index.set(key, fresh);\n base.push(fresh);\n }\n }\n return base;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Dimension display-label resolution (ADR-0021).\n *\n * Analytics groups by the raw stored value of a dimension field. For two field\n * kinds that value is NOT human-readable:\n *\n * - **select** — grouped by the stored option `value` (e.g. `backlog`), but the\n * user-facing text is the option `label` (e.g. `Backlog`).\n * - **lookup / master_detail** — grouped by the foreign-key `id` (e.g.\n * `8eqtuKI4G9IhUsPS`), but the user-facing text is the related record's\n * display field (its name/title).\n *\n * `resolveDimensionLabels` post-processes the result rows IN PLACE, replacing the\n * raw value at `row[dimension.name]` with its display label when one is found.\n * Unresolved values are left untouched so an orphaned id still renders as itself\n * rather than blanking out. Date / number / plain-string dimensions are no-ops.\n *\n * The resolution LOGIC lives here (and is unit-tested); the low-level capabilities\n * — reading an object's field map and fetching id→label pairs — are injected via\n * {@link DimensionLabelDeps} so this module stays free of any engine dependency.\n */\n\n/** The minimal field shape this resolver needs. */\nexport interface FieldMetaLite {\n type?: string;\n /** Lookup / master_detail target object name. */\n reference?: string;\n /** Select options — the value→label source. */\n options?: Array<{ value: unknown; label?: string }>;\n}\n\n/** Capabilities the resolver needs from the runtime (injected by the plugin). */\nexport interface DimensionLabelDeps {\n /** Return the field map for an object, or `undefined` if unknown. */\n getObjectFields(objectName: string): Record<string, FieldMetaLite> | undefined;\n /**\n * Fetch a map of `id → display label` for the given ids of a target object.\n * The implementation chooses the target's display field. Returning an empty\n * map (e.g. no display field, no data access) leaves the ids unresolved.\n */\n fetchRecordLabels(targetObject: string, ids: unknown[]): Promise<Map<unknown, string>>;\n}\n\nconst LOOKUP_TYPES = new Set(['lookup', 'master_detail']);\n\n/** Date-dimension granularity (mirrors the dataset `dateGranularity` enum). */\nexport type DateGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year';\n\nconst pad = (n: number) => String(n).padStart(2, '0');\n\n/**\n * Format a raw date value (epoch-ms number, numeric string, ISO string, or\n * Date) to a human, sort-stable bucket label per granularity. Returns the input\n * unchanged when it isn't a parseable date, so a non-date value never blanks.\n *\n * year → \"2026\"\n * quarter → \"2026-Q2\"\n * month → \"2026-04\"\n * week → \"2026-04-13\" (ISO date of the bucket)\n * day → \"2026-04-15\"\n *\n * Intentionally UTC-only (ADR-0053 Phase 2): timezone bucketing happens\n * upstream in `bucketDate` / `bucketDateValue`, so by the time a value reaches\n * here it is *already* the reference-zone bucket (often a label string like\n * \"2026-Q2\"). Re-applying a timezone here would shift an already-correct\n * `YYYY-MM-DD` day bucket by a day — this is a pure, idempotent re-labeler.\n */\nexport function formatDateBucket(value: unknown, granularity?: DateGranularity | string): unknown {\n if (value == null || value instanceof Date === false) {\n if (typeof value !== 'number' && typeof value !== 'string') return value;\n }\n let d: Date;\n if (value instanceof Date) d = value;\n else if (typeof value === 'number') d = new Date(value);\n else {\n const s = String(value).trim();\n // Pure-digit strings are epoch millis (or seconds); otherwise let Date parse ISO.\n d = /^\\d+$/.test(s) ? new Date(Number(s) < 1e12 ? Number(s) * 1000 : Number(s)) : new Date(s);\n }\n if (Number.isNaN(d.getTime())) return value;\n const y = d.getUTCFullYear();\n const m = d.getUTCMonth(); // 0-11\n switch (granularity) {\n case 'year': return String(y);\n case 'quarter': return `${y}-Q${Math.floor(m / 3) + 1}`;\n case 'month': return `${y}-${pad(m + 1)}`;\n case 'week':\n case 'day':\n default: return `${y}-${pad(m + 1)}-${pad(d.getUTCDate())}`;\n }\n}\n\n/**\n * Replace raw dimension values with display labels, in place.\n *\n * @param baseObject - the dataset's base object (where the dimension fields live)\n * @param dims - selected dimensions as `{ name, field, type?, dateGranularity? }`\n * (row key = `name`)\n * @param rows - result rows, mutated in place\n * @param deps - injected runtime capabilities\n */\nexport async function resolveDimensionLabels(\n baseObject: string,\n dims: Array<{ name: string; field: string; type?: string; dateGranularity?: DateGranularity | string }>,\n rows: Record<string, unknown>[],\n deps: DimensionLabelDeps,\n): Promise<void> {\n if (!rows.length || !dims.length) return;\n const fields = deps.getObjectFields(baseObject);\n if (!fields) return;\n\n for (const dim of dims) {\n const meta = fields[dim.field];\n\n // ── date: epoch / ISO → human bucket label ────────────────────────\n // A date dimension's grouped value is a raw timestamp (or a bucket start);\n // either way it must render as a readable date, not epoch millis.\n if (dim.type === 'date' || (meta && meta.type === 'date')) {\n for (const row of rows) {\n const formatted = formatDateBucket(row[dim.name], dim.dateGranularity);\n if (formatted != null) row[dim.name] = formatted;\n }\n continue;\n }\n\n if (!meta) continue;\n\n // ── select: value → option label ──────────────────────────────────\n if (Array.isArray(meta.options) && meta.options.length > 0) {\n const labelByValue = new Map<unknown, string>();\n for (const opt of meta.options) {\n if (opt && opt.label != null) labelByValue.set(opt.value, String(opt.label));\n }\n if (labelByValue.size === 0) continue;\n for (const row of rows) {\n const raw = row[dim.name];\n const label = labelByValue.get(raw);\n if (label != null) row[dim.name] = label;\n }\n continue;\n }\n\n // ── lookup / master_detail: id → related record display name ───────\n if (meta.type && LOOKUP_TYPES.has(meta.type) && meta.reference) {\n const ids = Array.from(\n new Set(rows.map((r) => r[dim.name]).filter((v) => v != null)),\n );\n if (ids.length === 0) continue;\n const labelById = await deps.fetchRecordLabels(meta.reference, ids);\n if (!labelById || labelById.size === 0) continue;\n for (const row of rows) {\n const label = labelById.get(row[dim.name]);\n if (label != null) row[dim.name] = label;\n }\n }\n }\n}\n\n/**\n * Pick the display field for an object from its field map, by convention:\n * an explicit `name`/`title`/`label` field, else the first text-like field.\n * Returns `undefined` when nothing suitable exists.\n */\nexport function pickDisplayField(\n fields: Record<string, FieldMetaLite> | undefined,\n): string | undefined {\n if (!fields) return undefined;\n for (const preferred of ['name', 'title', 'label']) {\n if (fields[preferred]) return preferred;\n }\n for (const [name, meta] of Object.entries(fields)) {\n if (meta.type === 'text' || meta.type === 'string') return name;\n }\n return undefined;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n//\n// ADR-0037 Phase 3 — draft data preview: evaluate an AnalyticsQuery over an\n// in-memory row set (the pending `seed` draft's records) instead of the real\n// data engine. This is what lets a Live Canvas dashboard chart REAL numbers\n// from the DRAFTED sample data before anything is published — and because\n// publish materializes the *same* seed, the numbers are continuous across\n// the publish boundary.\n//\n// Scope (deliberately the dataset-query subset, not a general engine):\n// • Mongo-style `where` filters ($eq implicit, $ne/$gt/$gte/$lt/$lte/\n// $in/$nin/$contains, $and/$or/$not)\n// • timeDimensions date-range filtering + granularity bucketing\n// (day/week/month/quarter/year)\n// • group-by dimensions; count / countDistinct / sum / avg / min / max\n// • order + limit/offset\n// Anything beyond (joins via `include`, raw SQL) falls back to the caller's\n// normal execution path — the preview simply doesn't claim it.\n\nimport { calendarPartsInTzOrUtc } from '@objectstack/core';\nimport type { AnalyticsQuery, AnalyticsResult } from '@objectstack/spec/contracts';\nimport type { Cube } from '@objectstack/spec/data';\n\ntype Row = Record<string, unknown>;\n\n// ── Filters (the unified Query DSL subset) ──────────────────────────────────\n\nfunction compare(a: unknown, b: unknown): number {\n if (typeof a === 'number' && typeof b === 'number') return a - b;\n return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;\n}\n\nfunction matchOp(value: unknown, op: string, expected: unknown): boolean {\n switch (op) {\n case '$eq': return value === expected || String(value) === String(expected);\n case '$ne': return !(value === expected || String(value) === String(expected));\n case '$gt': return value != null && compare(value, expected) > 0;\n case '$gte': return value != null && compare(value, expected) >= 0;\n case '$lt': return value != null && compare(value, expected) < 0;\n case '$lte': return value != null && compare(value, expected) <= 0;\n case '$in': return Array.isArray(expected) && expected.some((e) => value === e || String(value) === String(e));\n case '$nin': return Array.isArray(expected) && !expected.some((e) => value === e || String(value) === String(e));\n case '$contains': return String(value ?? '').toLowerCase().includes(String(expected ?? '').toLowerCase());\n default: return true; // unknown operator — permissive (preview, reads only)\n }\n}\n\nexport function matchesWhere(row: Row, where: Record<string, unknown> | undefined): boolean {\n if (!where) return true;\n for (const [key, cond] of Object.entries(where)) {\n if (key === '$and') {\n if (!(cond as Row[]).every((c) => matchesWhere(row, c as Row))) return false;\n } else if (key === '$or') {\n if (!(cond as Row[]).some((c) => matchesWhere(row, c as Row))) return false;\n } else if (key === '$not') {\n if (matchesWhere(row, cond as Row)) return false;\n } else if (cond !== null && typeof cond === 'object' && !Array.isArray(cond)) {\n for (const [op, expected] of Object.entries(cond as Row)) {\n if (!matchOp(row[key], op, expected)) return false;\n }\n } else if (!(row[key] === cond || String(row[key]) === String(cond))) {\n return false; // implicit equality\n }\n }\n return true;\n}\n\n// ── Time bucketing ──────────────────────────────────────────────────────────\n\nexport function bucketDate(value: unknown, granularity: string, timezone?: string): string | null {\n const d = new Date(String(value));\n if (Number.isNaN(d.getTime())) return null;\n // ADR-0053 Phase 2: resolve the calendar day in the reference zone so an\n // instant near a tz day-boundary buckets where a user in that zone expects.\n // Unset / 'UTC' / invalid keeps the historical UTC bucketing.\n const { year: y, month, day: dayNum } = calendarPartsInTzOrUtc(d, timezone);\n const m = `${month}`.padStart(2, '0');\n const day = `${dayNum}`.padStart(2, '0');\n switch (granularity) {\n case 'year': return `${y}`;\n case 'quarter': return `${y}-Q${Math.floor((month - 1) / 3) + 1}`;\n case 'month': return `${y}-${m}`;\n case 'week': {\n // Build a UTC date from the zone-shifted parts, then step back to Monday.\n const monday = new Date(Date.UTC(y, month - 1, dayNum));\n const dow = (monday.getUTCDay() + 6) % 7; // Monday=0\n monday.setUTCDate(monday.getUTCDate() - dow);\n return monday.toISOString().slice(0, 10);\n }\n case 'day':\n default:\n return `${y}-${m}-${day}`;\n }\n}\n\n// ── Aggregation ─────────────────────────────────────────────────────────────\n\nfunction aggregate(rows: Row[], metricType: string, field: string): number {\n if (metricType === 'count' || field === '*') {\n if (metricType === 'countDistinct') {\n return new Set(rows.map((r) => r[field]).filter((v) => v != null)).size;\n }\n return rows.length;\n }\n const nums = rows.map((r) => Number(r[field])).filter((n) => Number.isFinite(n));\n switch (metricType) {\n case 'countDistinct': return new Set(rows.map((r) => r[field]).filter((v) => v != null)).size;\n case 'sum': return nums.reduce((a, b) => a + b, 0);\n case 'avg': return nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : 0;\n case 'min': return nums.length ? Math.min(...nums) : 0;\n case 'max': return nums.length ? Math.max(...nums) : 0;\n default: return nums.length ? nums.reduce((a, b) => a + b, 0) : rows.length;\n }\n}\n\n/**\n * Evaluate `query` over `rows` using the cube's measure/dimension specs.\n * Mirrors the engine strategies' output contract: rows keyed by bare\n * measure/dimension names, `fields` describing each output column.\n */\nexport function evaluateAnalyticsQueryOverRows(\n query: AnalyticsQuery,\n cube: Cube,\n rows: Row[],\n): AnalyticsResult {\n // 1. Row-level filters: `where`, then timeDimension dateRanges.\n let filtered = rows.filter((r) => matchesWhere(r, query.where));\n const timeDims = query.timeDimensions ?? [];\n for (const td of timeDims) {\n const dim = cube.dimensions?.[td.dimension];\n const field = String(dim?.sql ?? td.dimension);\n if (!td.dateRange) continue;\n const [start, end] = Array.isArray(td.dateRange) ? td.dateRange : [td.dateRange, td.dateRange];\n filtered = filtered.filter((r) => {\n const v = String(r[field] ?? '');\n return v >= String(start) && v <= `${end}~`; // '~' > any date char: inclusive end-day\n });\n }\n\n // 2. Grouping keys: each selected dimension (time dims bucketed).\n const dimensions = query.dimensions ?? [];\n const timezone = query.timezone; // ADR-0053 Phase 2: reference tz for bucketing\n const granByDim = new Map(timeDims.filter((t) => t.granularity).map((t) => [t.dimension, t.granularity!]));\n const keyOf = (r: Row): { key: string; values: Row } => {\n const values: Row = {};\n for (const name of dimensions) {\n const dim = cube.dimensions?.[name];\n const field = String(dim?.sql ?? name);\n const raw = r[field];\n const gran = granByDim.get(name) ?? (dim?.type === 'time' && dim.granularities?.length === 1 ? String(dim.granularities[0]) : undefined);\n values[name] = gran ? bucketDate(raw, gran, timezone) : (raw ?? null);\n }\n return { key: JSON.stringify(values), values };\n };\n\n const groups = new Map<string, { values: Row; rows: Row[] }>();\n for (const r of filtered) {\n const { key, values } = keyOf(r);\n const g = groups.get(key) ?? { values, rows: [] };\n g.rows.push(r);\n groups.set(key, g);\n }\n // No dimensions → a single overall group (even over zero rows: count = 0).\n if (dimensions.length === 0 && groups.size === 0) {\n groups.set('{}', { values: {}, rows: [] });\n }\n\n // 3. Aggregate each measure per group.\n const out: Row[] = [];\n for (const g of groups.values()) {\n const row: Row = { ...g.values };\n for (const m of query.measures) {\n const metric = cube.measures?.[m];\n row[m] = aggregate(g.rows, String(metric?.type ?? 'count'), String(metric?.sql ?? '*'));\n }\n out.push(row);\n }\n\n // 4. Order + paging.\n for (const [col, dir] of Object.entries(query.order ?? {}).reverse()) {\n out.sort((a, b) => (dir === 'desc' ? -1 : 1) * compare(a[col], b[col]));\n }\n const offset = query.offset ?? 0;\n const limited = out.slice(offset, query.limit != null ? offset + query.limit : undefined);\n\n return {\n rows: limited,\n fields: [\n ...dimensions.map((d) => ({ name: d, type: 'string' })),\n ...query.measures.map((m) => ({ name: m, type: 'number' })),\n ],\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { Cube, FilterCondition } from '@objectstack/spec/data';\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\nimport type { IAnalyticsService } from '@objectstack/spec/contracts';\nimport { AnalyticsService } from './analytics-service.js';\nimport type { AnalyticsServiceConfig } from './analytics-service.js';\nimport type { DriverCapabilities } from './strategies/types.js';\nimport { pickDisplayField, type DimensionLabelDeps } from './dimension-labels.js';\n\n/**\n * Minimal IDataEngine surface required for the auto-bridge.\n * ObjectQL exposes:\n * - `aggregate(object, { where, groupBy, aggregations: [{ function, field, alias }] })`\n * - `execute(sql, options)` for raw SQL pass-through (enables NativeSQLStrategy\n * and lets the analytics layer emit JOINs for relation traversal).\n */\ninterface DataEngineLike {\n aggregate(object: string, options: {\n where?: Record<string, unknown>;\n groupBy?: string[];\n aggregations?: Array<{ function: string; field: string; alias: string }>;\n /** Reference timezone (IANA) for date bucketing — ADR-0053 Phase 2. */\n timezone?: string;\n }): Promise<unknown[]>;\n execute?(command: unknown, options?: Record<string, unknown>): Promise<unknown>;\n /** Return the registered object schema (relationship → target + display-label resolution). */\n getObject?(name: string): {\n fields?: Record<string, {\n type?: string;\n reference?: string;\n options?: Array<{ value: unknown; label?: string }>;\n }>;\n /** Federation marker (ADR-0015): set on objects bound to an external datasource. */\n external?: unknown;\n /** The datasource this object is bound to (ADR-0062 D6 external detection). */\n datasource?: string;\n } | undefined;\n /**\n * Resolve the storage driver backing an object (public ObjectQL accessor).\n * Used to delegate temporal filter-value coercion to the driver, which is the\n * single source of truth for how a `Field.date`/`Field.datetime` is stored on\n * the active dialect. The driver may expose `temporalFilterValue(object, field,\n * value)` (SqlDriver does); when absent we leave the value untouched.\n */\n getDriverForObject?(objectName: string): DriverLike | undefined;\n}\n\n/** Minimal driver surface the analytics layer probes for temporal coercion. */\ninterface DriverLike {\n /**\n * Coerce a filter comparand to the column's on-disk storage form\n * (SQLite `Field.datetime` → epoch ms; `Field.date` → YYYY-MM-DD; native\n * timestamp / non-temporal → unchanged). Optional — only SqlDriver implements it.\n */\n temporalFilterValue?(objectName: string, field: string, value: unknown): unknown;\n}\n\n/**\n * Configuration for AnalyticsServicePlugin.\n */\nexport interface AnalyticsServicePluginOptions {\n /** Pre-defined cube definitions (from manifest). */\n cubes?: Cube[];\n /**\n * Probe driver capabilities for a given cube.\n * When omitted, defaults to in-memory only.\n */\n queryCapabilities?: (cubeName: string) => DriverCapabilities;\n /**\n * Execute raw SQL on a driver. Enables NativeSQLStrategy.\n */\n executeRawSql?: (objectName: string, sql: string, params: unknown[]) => Promise<Record<string, unknown>[]>;\n /**\n * Execute ObjectQL aggregate. Enables ObjectQLStrategy.\n */\n executeAggregate?: (objectName: string, options: {\n groupBy?: string[];\n aggregations?: Array<{ field: string; method: string; alias: string }>;\n filter?: Record<string, unknown>;\n /** Reference timezone (IANA) for date bucketing — ADR-0053 Phase 2. */\n timezone?: string;\n }) => Promise<Record<string, unknown>[]>;\n /**\n * ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). The\n * runtime supplies this from its sharing middleware so the analytics raw-SQL\n * path cannot bypass tenant isolation. Receives the request's ExecutionContext\n * and returns the RLS `FilterCondition` for the object (what `RLSCompiler`\n * emits). When omitted, the plugin auto-bridges to a registered `'security'`\n * service exposing `getReadFilter(object, context)` if one is present.\n */\n getReadScope?: (\n objectName: string,\n context?: ExecutionContext,\n ) =>\n | FilterCondition\n | null\n | undefined\n | Promise<FilterCondition | null | undefined>;\n /**\n * ADR-0021 D-C — join allowlist per cube (the dataset's declared `include`).\n * Typically wired from the dataset registry's compiled `allowedRelationships`.\n */\n getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;\n /** Enable debug logging. */\n debug?: boolean;\n}\n\n/**\n * AnalyticsServicePlugin — Kernel plugin for multi-driver analytics.\n *\n * Lifecycle:\n * 1. **init** — Creates `AnalyticsService`, registers as `'analytics'` service.\n * If an existing analytics service is already registered (e.g. MemoryAnalyticsService\n * from dev-plugin), it is captured as the `fallbackService`.\n * 2. **start** — Triggers `'analytics:ready'` hook so other plugins can\n * register cubes or extend the service.\n * 3. **destroy** — Cleans up references.\n *\n * @example\n * ```ts\n * import { LiteKernel } from '@objectstack/core';\n * import { AnalyticsServicePlugin } from '@objectstack/service-analytics';\n *\n * const kernel = new LiteKernel();\n * kernel.use(new AnalyticsServicePlugin({\n * cubes: [ordersCube],\n * queryCapabilities: (cube) => ({ nativeSql: true, objectqlAggregate: true, inMemory: false }),\n * executeRawSql: async (obj, sql, params) => pgPool.query(sql, params).then(r => r.rows),\n * }));\n * await kernel.bootstrap();\n *\n * const analytics = kernel.getService<IAnalyticsService>('analytics');\n * const result = await analytics.query({ cube: 'orders', measures: ['orders.count'] });\n * ```\n */\nexport class AnalyticsServicePlugin implements Plugin {\n name = 'com.objectstack.service-analytics';\n version = '1.0.0';\n type = 'standard' as const;\n dependencies: string[] = [];\n\n private service?: AnalyticsService;\n private readonly options: AnalyticsServicePluginOptions;\n\n constructor(options: AnalyticsServicePluginOptions = {}) {\n this.options = options;\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Check if there is an existing analytics service (e.g. from dev-plugin)\n let fallbackService: IAnalyticsService | undefined;\n try {\n const existing = ctx.getService<IAnalyticsService>('analytics');\n if (existing && typeof existing.query === 'function') {\n fallbackService = existing;\n ctx.logger.debug('[Analytics] Found existing analytics service, using as fallback');\n }\n } catch {\n // No existing service — that's fine\n }\n\n // Auto-bridge: when caller did not supply executeAggregate, look up the\n // kernel's IDataEngine (registered as 'data' by ObjectQLPlugin) lazily and\n // translate AnalyticsStrategy's `{method, filter}` shape into the engine's\n // `{function, where}` shape. This lets users write\n // `new AnalyticsServicePlugin({ cubes })`\n // without re-implementing the bridge in every app.\n let executeAggregate = this.options.executeAggregate;\n let autoBridged = false;\n if (!executeAggregate) {\n const tryGetDataEngine = (): DataEngineLike | undefined => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n return svc && typeof svc.aggregate === 'function' ? svc : undefined;\n } catch {\n return undefined;\n }\n };\n // Probe now (warn if missing) but resolve at call time so plugin order\n // does not matter as long as 'data' exists by the time a query runs.\n if (!tryGetDataEngine()) {\n ctx.logger.warn(\n '[Analytics] No \"data\" service registered yet at init; ' +\n 'will retry per-query. Register ObjectQLPlugin or pass executeAggregate.',\n );\n }\n executeAggregate = async (objectName, { groupBy, aggregations, filter, timezone }) => {\n const engine = tryGetDataEngine();\n if (!engine) {\n throw new Error(\n '[Analytics] Cannot execute aggregate: no IDataEngine (\"data\") service is registered. ' +\n 'Add ObjectQLPlugin to the kernel or supply AnalyticsServicePlugin({ executeAggregate }).',\n );\n }\n const rows = await engine.aggregate(objectName, {\n where: filter,\n groupBy,\n aggregations: aggregations?.map((a) => ({\n function: a.method,\n field: a.field,\n alias: a.alias,\n })),\n // ADR-0053 Phase 2: thread the reference tz so date buckets resolve on\n // that zone's calendar days (engine buckets in-memory when non-UTC).\n timezone,\n });\n return rows as Record<string, unknown>[];\n };\n autoBridged = true;\n }\n\n // Auto-bridge raw SQL when the data engine exposes `execute()` and the\n // caller did not supply their own `executeRawSql`. This unlocks\n // NativeSQLStrategy (priority 10) which can emit `LEFT JOIN`s for\n // dotted dimension/measure references like `account.industry`.\n let executeRawSql = this.options.executeRawSql;\n let autoBridgedRawSql = false;\n if (!executeRawSql) {\n const tryGetExecutor = (): DataEngineLike | undefined => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n return svc && typeof svc.execute === 'function' ? svc : undefined;\n } catch {\n return undefined;\n }\n };\n // Always wire the bridge — resolution happens at call time, mirroring\n // the executeAggregate auto-bridge above. This way plugin-init order\n // does not matter as long as `data` exists by the time a query runs.\n executeRawSql = async (_objectName, sql, params) => {\n const engine = tryGetExecutor();\n if (!engine || !engine.execute) {\n throw new Error(\n '[Analytics] Cannot execute raw SQL: no IDataEngine (\"data\") service with execute() is registered.',\n );\n }\n // NativeSQLStrategy emits `$1, $2, …` placeholders. Knex (used by\n // driver-sql) speaks `?` placeholders, so translate.\n const knexSql = sql.replace(/\\$(\\d+)/g, '?');\n const result = await engine.execute(knexSql, { args: params });\n // A driver that cannot run SQL (e.g. the in-memory driver) returns\n // null from execute(). Silently mapping that to [] made EVERY dataset\n // query on such environments report \"No rows\" while looking healthy\n // (HTTP 200, compiled SQL attached). Throw a TYPED error instead so\n // the orchestrator can fall back to an aggregate-based strategy —\n // never fabricate an empty result.\n if (result === null || result === undefined) {\n const err = new Error(\n '[Analytics] The \"data\" engine\\'s driver returned null for raw SQL — ' +\n 'this driver does not support SQL execution. The query will fall back ' +\n 'to an aggregate-based strategy when one is available.',\n ) as Error & { code: string };\n err.code = 'RAW_SQL_UNSUPPORTED';\n throw err;\n }\n if (Array.isArray(result)) return result as Record<string, unknown>[];\n if (typeof result === 'object' && 'rows' in (result as Record<string, unknown>)) {\n return (result as { rows: Record<string, unknown>[] }).rows;\n }\n return [];\n };\n autoBridgedRawSql = true;\n }\n\n // Default capabilities: when we have an aggregate bridge, advertise\n // ObjectQL support so ObjectQLStrategy is selected. Callers can still\n // override via options.queryCapabilities.\n const queryCapabilities = this.options.queryCapabilities\n ?? (() => ({\n nativeSql: !!executeRawSql,\n objectqlAggregate: !!executeAggregate,\n inMemory: false,\n }));\n\n // ADR-0021 D-C — wire the read-scope provider. Prefer an explicit option;\n // otherwise auto-bridge to a registered `'security'` service that exposes\n // `getReadFilter(object, context)` (resolved at call time so plugin-init\n // order does not matter). This keeps analytics decoupled from security.\n interface SecurityReadFilter {\n getReadFilter(\n object: string,\n context?: ExecutionContext,\n ):\n | FilterCondition\n | null\n | undefined\n | Promise<FilterCondition | null | undefined>;\n }\n let getReadScope = this.options.getReadScope;\n let autoBridgedReadScope = false;\n if (!getReadScope) {\n const trySecurity = (): SecurityReadFilter | undefined => {\n try {\n const svc = ctx.getService<SecurityReadFilter>('security');\n return svc && typeof svc.getReadFilter === 'function' ? svc : undefined;\n } catch {\n return undefined;\n }\n };\n if (trySecurity()) {\n getReadScope = (object, context) => trySecurity()?.getReadFilter(object, context);\n autoBridgedReadScope = true;\n }\n }\n\n // ADR-0021 — relationship → target-object resolver. A dataset's `include`\n // names lookup/master_detail FIELDS on the base object; the joined TABLE is\n // each field's `reference` target (which can differ from the field name,\n // e.g. lookup `account` → object `crm_account`). Resolve from the 'data'\n // engine's object schema at compile time so cross-object joins target the\n // right table. Resolved lazily so plugin-init order doesn't matter.\n const relationshipResolver = (baseObject: string, relationshipName: string): string | undefined => {\n const engine = (() => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n return svc && typeof svc.getObject === 'function' ? svc : undefined;\n } catch { return undefined; }\n })();\n const obj = engine?.getObject?.(baseObject);\n const field = obj?.fields?.[relationshipName];\n if (field && (field.type === 'lookup' || field.type === 'master_detail') && field.reference) {\n return field.reference;\n }\n // Unknown to the schema — fall back to the relationship name as the table\n // (legacy same-name convention). Returning undefined would make the\n // compiler reject the dataset; the name-as-table fallback is safer for\n // engines that don't expose getObject.\n return engine ? undefined : relationshipName;\n };\n\n // ADR-0021 — dimension display-label resolution. `queryDataset` groups by a\n // dimension's raw stored value; for `select` fields the user-facing text is\n // the option label, and for `lookup`/`master_detail` fields it's the related\n // record's display name. Wire the two low-level capabilities the resolver\n // needs from the 'data' engine (resolved lazily so plugin-init order is free):\n // - field metadata (select options + lookup target), via getObject\n // - id→name pairs, via the executeAggregate bridge (group by id + name)\n const dataEngine = (): DataEngineLike | undefined => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n return svc && typeof svc.getObject === 'function' ? svc : undefined;\n } catch { return undefined; }\n };\n const labelResolver: DimensionLabelDeps = {\n getObjectFields: (objectName) => dataEngine()?.getObject?.(objectName)?.fields,\n fetchRecordLabels: async (targetObject, ids) => {\n const map = new Map<unknown, string>();\n const displayField = pickDisplayField(dataEngine()?.getObject?.(targetObject)?.fields);\n if (!displayField || !executeAggregate || ids.length === 0) return map;\n // Group by (id, displayField) — one row per record — reusing the aggregate\n // bridge rather than adding a record-fetch capability. A count keeps engines\n // that require ≥1 aggregation happy; the count itself is unused.\n const rows = await executeAggregate(targetObject, {\n groupBy: ['id', displayField],\n aggregations: [{ field: 'id', method: 'count', alias: '_c' }],\n filter: { id: { $in: ids } },\n });\n for (const r of rows) {\n if (r.id != null && r[displayField] != null) map.set(r.id, String(r[displayField]));\n }\n return map;\n },\n };\n\n // ADR-0037 P3 — draft data preview: resolve the PENDING seed draft's rows\n // for an object via the kernel protocol (state:'draft' read — a published\n // seed's rows are already in the real table and must NOT overlay). Lazy\n // service lookup so plugin order doesn't matter; null ⇒ no pending seed ⇒\n // queryDataset falls through to live data.\n const draftRowsResolver = async (objectName: string): Promise<Record<string, unknown>[] | null> => {\n type ProtocolLike = {\n getMetaItems?(req: { type: string; previewDrafts?: boolean }): Promise<unknown>;\n getMetaItem?(req: { type: string; name: string; state?: string }): Promise<unknown>;\n };\n let protocol: ProtocolLike | undefined;\n try {\n protocol = ctx.getService<ProtocolLike>('protocol');\n } catch { return null; }\n if (!protocol?.getMetaItems || !protocol.getMetaItem) return null;\n const res = await protocol.getMetaItems({ type: 'seed', previewDrafts: true }).catch(() => null);\n const list = Array.isArray(res)\n ? res\n : (res && typeof res === 'object' && Array.isArray((res as { items?: unknown[] }).items)\n ? (res as { items: unknown[] }).items\n : []);\n const rows: Record<string, unknown>[] = [];\n let pending = false;\n for (const entry of list) {\n const body = ((entry as { item?: unknown })?.item ?? entry) as { name?: string; object?: string } | null;\n if (!body?.name || body.object !== objectName) continue;\n // Only a PENDING draft row qualifies; getMetaItem({state:'draft'})\n // throws no_draft when the seed is already published.\n const draft = await protocol.getMetaItem({ type: 'seed', name: body.name, state: 'draft' }).catch(() => null);\n const draftBody = (draft as { item?: { records?: unknown[] } } | null)?.item;\n if (!draftBody) continue;\n pending = true;\n for (const r of Array.isArray(draftBody.records) ? draftBody.records : []) {\n if (r && typeof r === 'object') rows.push(r as Record<string, unknown>);\n }\n }\n return pending ? rows : null;\n };\n\n // Temporal storage-form coercion (fixes the SQLite datetime \"No rows\" bug).\n // The raw-SQL strategy binds dashboard relative-date tokens (already expanded\n // to ISO strings) directly, bypassing the driver's CRUD coercion. Delegate to\n // the driver — the single source of truth for the on-disk storage convention —\n // so a `Field.datetime` ISO comparand becomes epoch ms on SQLite, while\n // `Field.date` text and native-timestamp (Postgres) columns pass through\n // unchanged. Resolved at call time so plugin-init order does not matter.\n const coerceTemporalFilterValue = (\n objectName: string,\n fieldName: string,\n value: unknown,\n ): unknown => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n const driver = svc?.getDriverForObject?.(objectName);\n if (driver && typeof driver.temporalFilterValue === 'function') {\n return driver.temporalFilterValue(objectName, fieldName, value);\n }\n } catch {\n // No data engine / driver, or it doesn't support coercion — leave the\n // value as-is (today's behaviour; safe for text/native-timestamp paths).\n }\n return value;\n };\n\n const config: AnalyticsServiceConfig = {\n cubes: this.options.cubes,\n logger: ctx.logger,\n queryCapabilities,\n executeRawSql,\n executeAggregate,\n fallbackService,\n getReadScope,\n getAllowedRelationships: this.options.getAllowedRelationships,\n coerceTemporalFilterValue,\n relationshipResolver,\n labelResolver,\n // ADR-0053 — source-field currency metadata for the measure currency chain.\n measureCurrency: (object: string, field: string) => {\n const f = dataEngine()?.getObject?.(object)?.fields?.[field] as\n | { type?: string; currencyConfig?: { defaultCurrency?: string } }\n | undefined;\n return f ? { type: f.type, defaultCurrency: f.currencyConfig?.defaultCurrency } : undefined;\n },\n // ADR-0062 D6 — a federated object carries an `external` block (ADR-0015).\n // Reported so NativeSQLStrategy declines it (its hand-compiled FROM would\n // hit the wrong physical table) and the driver-correct ObjectQL path runs.\n isExternalObject: (objectName: string) => {\n const obj = dataEngine()?.getObject?.(objectName);\n return !!(obj && obj.external != null);\n },\n draftRowsResolver,\n };\n\n if (autoBridgedReadScope) {\n ctx.logger.info('[Analytics] Auto-bridged getReadScope → \"security\" service (getReadFilter)');\n } else if (!getReadScope) {\n ctx.logger.warn(\n '[Analytics] No getReadScope configured and no \"security\" service with getReadFilter found — ' +\n 'the raw-SQL analytics path will NOT enforce tenant/RLS scoping on joined objects (ADR-0021 D-C). ' +\n 'Supply getReadScope or register a security service in multi-tenant deployments.',\n );\n }\n\n if (autoBridged) {\n ctx.logger.info('[Analytics] Auto-bridged executeAggregate → \"data\" service (IDataEngine)');\n }\n if (autoBridgedRawSql) {\n ctx.logger.info('[Analytics] Auto-bridged executeRawSql → \"data\" service (IDataEngine.execute)');\n }\n\n this.service = new AnalyticsService(config);\n\n // Register or replace the analytics service\n if (fallbackService) {\n ctx.replaceService('analytics', this.service);\n } else {\n ctx.registerService('analytics', this.service);\n }\n\n if (this.options.debug) {\n ctx.hook('analytics:beforeQuery', async (query: unknown) => {\n ctx.logger.debug('[Analytics] Before query', { query });\n });\n }\n\n ctx.logger.info('[Analytics] Service initialized');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n if (!this.service) return;\n\n // Notify other plugins that analytics is ready\n await ctx.trigger('analytics:ready', this.service);\n\n ctx.logger.info(\n `[Analytics] Service started with ${this.service.cubeRegistry.size} cubes: ` +\n `${this.service.cubeRegistry.names().join(', ') || '(none)'}`,\n );\n }\n\n async destroy(): Promise<void> {\n this.service = undefined;\n }\n}\n"],"mappings":"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","names":["import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/analytics-service.ts","../src/cube-registry.ts","../src/strategies/filter-normalizer.ts","../src/read-scope-sql.ts","../src/strategies/native-sql-strategy.ts","../src/strategies/cross-object-rebucket.ts","../src/strategies/objectql-strategy.ts","../src/dataset-compiler.ts","../src/dataset-executor.ts","../src/dimension-labels.ts","../src/preview-evaluator.ts","../src/plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n// Core service\nexport { AnalyticsService } from './analytics-service.js';\nexport type { AnalyticsServiceConfig } from './analytics-service.js';\n\n// Kernel plugin\nexport { AnalyticsServicePlugin } from './plugin.js';\nexport type { AnalyticsServicePluginOptions } from './plugin.js';\n\n// Cube registry\nexport { CubeRegistry } from './cube-registry.js';\n\n// Dataset semantic layer (ADR-0021)\nexport { compileDataset } from './dataset-compiler.js';\nexport type { CompiledDataset, DerivedMeasureSpec, RelationshipResolver, RelationshipTarget } from './dataset-compiler.js';\n\nexport {\n resolveDimensionLabels,\n pickDisplayField,\n createOrderLabelResolver,\n withLabelFetchCache,\n} from './dimension-labels.js';\nexport type { DimensionLabelDeps, FieldMetaLite, OrderLabelResolver } from './dimension-labels.js';\nexport {\n DatasetExecutor,\n evaluateDerivedMeasures,\n combineFilters,\n shiftRange,\n mergeByDimensions,\n} from './dataset-executor.js';\nexport type { DatasetSelection, CompareTo } from './dataset-executor.js';\nexport { compileScopedFilterToSql } from './read-scope-sql.js';\n\n// Strategies\nexport { NativeSQLStrategy } from './strategies/native-sql-strategy.js';\nexport { ObjectQLStrategy } from './strategies/objectql-strategy.js';\nexport type { AnalyticsStrategy, StrategyContext, DriverCapabilities } from './strategies/types.js';\n\n// Note: InMemoryStrategy is exported from @objectstack/driver-memory\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IAnalyticsService,\n AnalyticsQuery,\n AnalyticsResult,\n CubeMeta,\n DatasetSelection,\n} from '@objectstack/spec/contracts';\nimport type { Cube, FilterCondition } from '@objectstack/spec/data';\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\nimport type { Dataset } from '@objectstack/spec/ui';\nimport type { Logger } from '@objectstack/spec/contracts';\nimport { createLogger, bucketKeyToCalendarRange, zonedDateStartToUtcMs } from '@objectstack/core';\nimport { CubeRegistry } from './cube-registry.js';\nimport type { AnalyticsStrategy, DriverCapabilities, StrategyContext } from './strategies/types.js';\nimport { NativeSQLStrategy } from './strategies/native-sql-strategy.js';\nimport { ObjectQLStrategy } from './strategies/objectql-strategy.js';\nimport { compileDataset, type CompiledDataset, type RelationshipResolver } from './dataset-compiler.js';\nimport { DatasetExecutor, resolveDimensionGranularity, type DateGranularityValue } from './dataset-executor.js';\nimport {\n resolveDimensionLabels,\n createOrderLabelResolver,\n withLabelFetchCache,\n type DimensionLabelDeps,\n} from './dimension-labels.js';\nimport { evaluateAnalyticsQueryOverRows } from './preview-evaluator.js';\n\n/**\n * Analytics result augmented with drill-through metadata (ADR-0021 D2; see\n * queryDataset). Carried alongside `rows` so the host can drill a clicked bucket\n * back to the underlying records without the renderer knowing field mappings.\n */\ntype AnalyticsResultWithDrill = AnalyticsResult & {\n /** The dataset's base object — the host drills into its records. */\n object?: string;\n /** Selected drillable dimension NAME → underlying object FIELD name. */\n dimensionFields?: Record<string, string>;\n /**\n * RAW grouped values per row, aligned to `rows` by index — each a map of\n * drillable dimension NAME → stored value (BEFORE label resolution rewrote\n * `rows[i][dim]` to the display label). The exact-match drill filter is built\n * from these, never from the display labels.\n */\n drillRawRows?: Array<Record<string, unknown>>;\n /**\n * RAW grouped values for the totals/subtotal rows (#3214), the totals-side\n * companion to `drillRawRows`: `drillRawTotals[i]` aligns to `result.totals[i]`\n * and `drillRawTotals[i][j]` to `result.totals[i].rows[j]`. Each map holds that\n * grouping's DRILLABLE dimension NAME → stored value, snapshotted in the SAME\n * pre-label-resolution pass (the totals loop below overwrites a subtotal row's\n * dimension value with its display label just like the data rows). Restricted\n * to the drillable dims present in the grouping, so the grand-total grouping\n * (`[]`) contributes an empty map per row — which keeps the index alignment\n * intact and correctly drills the whole (unfiltered) object.\n */\n drillRawTotals?: Array<Array<Record<string, unknown>>>;\n /**\n * #1752 — half-open date-range drill scope per row, the RANGE companion to\n * `drillRawRows` (which handles equality dims). A time-bucketed date\n * dimension (`dateGranularity`) groups a SPAN of records into one bucket\n * (\"2026-Q2\"), so its drill needs `[gte, lt)`, not equality — the humanized\n * bucket can't be exact-matched (which is why date dims are excluded from\n * `dimensionFields`/`drillRawRows`). Aligned to `rows` by index; each entry\n * maps a drillable date-dimension NAME → `{ field, gte, lt }` with `gte`\n * inclusive and `lt` exclusive (bounds as `YYYY-MM-DD`). Present only for\n * buckets whose boundaries are unambiguous — a `datetime` field under a\n * non-UTC reference timezone is omitted (host drills an unscoped superset)\n * until instant-boundary support lands.\n */\n drillRanges?: Array<Record<string, { field: string; gte: string; lt: string }>>;\n};\n\n/**\n * Detect the \"backing object/table isn't present in this kernel\" class of\n * error so a dataset query can degrade to an empty result instead of failing\n * the widget with a 500. Matches the missing-relation signatures across the\n * drivers ObjectStack runs on (sqlite/libsql, postgres, mysql) plus the\n * framework's own unknown-object signal. Deliberately scoped to MISSING SOURCE\n * (table/object/relation) — not column/syntax errors, which stay hard failures\n * so real query bugs still surface.\n */\nfunction isMissingSourceError(err: unknown): boolean {\n const msg = String((err as { message?: unknown })?.message ?? err ?? '').toLowerCase();\n return (\n msg.includes('no such table') || // sqlite / libsql\n (msg.includes('relation') && msg.includes('does not exist')) || // postgres\n msg.includes(\"doesn't exist\") || // mysql (\"table ... doesn't exist\")\n msg.includes('not registered') || // framework: object not in registry\n msg.includes('unknown object') ||\n msg.includes('is not a registered object')\n );\n}\n\n/**\n * Configuration for AnalyticsService.\n */\nexport interface AnalyticsServiceConfig {\n /** Pre-defined cube definitions (from manifest). */\n cubes?: Cube[];\n /** Logger instance. */\n logger?: Logger;\n /**\n * Probe driver capabilities for the object that backs a cube.\n * The service calls this function to decide which strategy can handle a query.\n */\n queryCapabilities?: (cubeName: string) => DriverCapabilities;\n /**\n * Execute raw SQL on the driver for a given object.\n * Required for NativeSQLStrategy.\n */\n executeRawSql?: (objectName: string, sql: string, params: unknown[]) => Promise<Record<string, unknown>[]>;\n /**\n * Execute an ObjectQL aggregate query.\n * Required for ObjectQLStrategy.\n */\n executeAggregate?: (objectName: string, options: {\n groupBy?: string[];\n aggregations?: Array<{ field: string; method: string; alias: string }>;\n filter?: Record<string, unknown>;\n /** Reference timezone (IANA) for date bucketing — ADR-0053 Phase 2. */\n timezone?: string;\n /**\n * ADR-0021 D-C (#3602) — the request's ExecutionContext. Bridges MUST\n * forward it to `engine.aggregate` so engine-side RLS applies; see\n * `StrategyContext.executeAggregate` for why this is a second belt rather\n * than a replacement for `getReadScope`.\n */\n context?: ExecutionContext;\n }) => Promise<Record<string, unknown>[]>;\n /**\n * Fallback IAnalyticsService (e.g. MemoryAnalyticsService).\n * Used by InMemoryStrategy.\n */\n fallbackService?: IAnalyticsService;\n /**\n * Custom strategies to add/replace the defaults.\n * They are merged with the built-in strategies and sorted by priority.\n */\n strategies?: AnalyticsStrategy[];\n /**\n * ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). Supplied\n * by the runtime that owns the sharing middleware; receives the current\n * request's ExecutionContext and returns the RLS `FilterCondition` for the\n * object (exactly what `RLSCompiler` emits). The service binds the active\n * context per query and the strategy compiles the filter into alias-qualified\n * SQL injected into every base and joined table.\n *\n * MAY be async: the production bridge resolves RLS from the `security`\n * service's `getReadFilter`, which can hit the database. The service\n * pre-resolves the scope for every base + joined object of a query (before\n * the synchronous SQL builder runs), so a sync return still works unchanged.\n */\n getReadScope?: (\n objectName: string,\n context?: ExecutionContext,\n ) =>\n | FilterCondition\n | null\n | undefined\n | Promise<FilterCondition | null | undefined>;\n /**\n * ADR-0021 D-C — join allowlist per cube (the dataset's declared `include`).\n * Joins outside this set are rejected by the strategy. Compiled datasets\n * (via `queryDataset`/`registerDataset`) supply this automatically; this\n * config hook is a fallback for legacy hand-authored cubes.\n */\n getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;\n /**\n * Coerce a filter comparand to a temporal column's storage form so a\n * relative-date / ISO-string value compares correctly on the active driver\n * (SQLite `Field.datetime` → epoch ms; `Field.date` / native timestamp →\n * unchanged). Threaded into the StrategyContext and consulted by\n * `NativeSQLStrategy` when binding filter values. See the contract docs on\n * `StrategyContext.coerceTemporalFilterValue` for the full rationale.\n */\n coerceTemporalFilterValue?: (objectName: string, fieldName: string, value: unknown) => unknown;\n /**\n * ADR-0062 D6 — report whether an object is federated (external datasource).\n * Threaded into the StrategyContext so `NativeSQLStrategy` declines external\n * objects (which it would otherwise query against the wrong physical table),\n * routing them to the driver-correct ObjectQL aggregate path instead. See\n * `StrategyContext.isExternalObject`.\n */\n isExternalObject?: (objectName: string) => boolean;\n /**\n * [#3867] Is `name` a registered object in this kernel's schema registry?\n *\n * Consulted by {@link AnalyticsService.ensureCube} on the auto-inference\n * path only. When no Cube is registered under the queried name, the service\n * infers a minimal one whose `sql` IS that name — the intended \"metric over\n * an object\" path (an `object-metric` KPI widget queries `crm_account`\n * without anyone authoring a Cube). Without this hook that inference accepts\n * ANY string, so an arbitrary physical table name reached the driver: the\n * analytics-side twin of the data-path gap closed in #3770.\n *\n * Optional, and absence means \"skip the check\" — same tiering as #3770's\n * `assertObjectRegistered`: with no registry to consult the question cannot\n * be answered, and failing closed would break every embedding that runs\n * analytics without a data engine. The production bridge in `plugin.ts`\n * always wires it.\n */\n isRegisteredObject?: (name: string) => boolean;\n /**\n * ADR-0021 — optional object-graph resolver used when compiling datasets:\n * `(baseObject, relationshipName) => relatedObjectName | undefined`. When\n * provided, `queryDataset` validates that every declared `include` exists.\n */\n relationshipResolver?: RelationshipResolver;\n /**\n * ADR-0053 currency chain — resolve a measure's SOURCE FIELD currency\n * metadata so a monetary measure that omits an explicit `currency` falls back\n * to the field's declared currency, then the tenant default (`ctx.currency`).\n * Returns the source field's `type` and (fixed-mode) `defaultCurrency`;\n * `undefined` for an unknown field. Non-`currency` fields never get a code.\n */\n measureCurrency?: (object: string, field: string) => { type?: string; defaultCurrency?: string } | undefined;\n /** Pre-defined datasets to compile + register at construction (ADR-0021). */\n datasets?: Dataset[];\n /**\n * ADR-0021 — resolve raw dimension values to human display labels. When\n * provided, `queryDataset` post-processes result rows so a `select` dimension\n * shows its option label (not the stored value) and a `lookup`/`master_detail`\n * dimension shows the related record's display name (not the FK id). Injected\n * by the plugin from the `data` engine; omit to keep raw values.\n */\n labelResolver?: DimensionLabelDeps;\n\n /**\n * ADR-0037 Phase 3 — draft data preview. Resolve the PENDING `seed` draft\n * rows for an object (returns null when the object has no pending seed).\n * When provided and `queryDataset` is called with `previewDrafts`, the\n * selection is evaluated over these rows in memory instead of the engine —\n * the Live Canvas charts real numbers from the drafted sample data, and\n * because publish materializes the SAME seed, the numbers are continuous\n * across the publish boundary. Reads only; never touches physical tables.\n */\n draftRowsResolver?: (\n objectName: string,\n context?: ExecutionContext,\n ) => Promise<Record<string, unknown>[] | null>;\n}\n\n/**\n * Default capabilities when probing is not configured — assumes in-memory only.\n */\nconst DEFAULT_CAPABILITIES: DriverCapabilities = {\n nativeSql: false,\n objectqlAggregate: false,\n inMemory: true,\n};\n\n/**\n * AnalyticsService — Multi-driver analytics orchestrator.\n *\n * Implements `IAnalyticsService` by delegating to a priority-ordered\n * strategy chain:\n *\n * | Priority | Strategy | Condition |\n * |:---:|:---|:---|\n * | P1 (10) | NativeSQLStrategy | Driver supports raw SQL |\n * | P2 (20) | ObjectQLStrategy | Driver supports aggregate AST |\n * | P3 (30) | (custom / InMemoryStrategy from driver-memory) | Injected by user |\n *\n * When `fallbackService` is configured, an internal delegate strategy\n * is automatically appended at priority 30 as a safety net.\n *\n * The service also owns a `CubeRegistry` for metadata discovery and\n * auto-inference from object schemas.\n */\nexport class AnalyticsService implements IAnalyticsService {\n private readonly strategies: AnalyticsStrategy[];\n /** Context-independent part of the StrategyContext (no per-request scope). */\n private readonly baseCtx: StrategyContext;\n /** Context-aware read-scope provider (bound to the request's context per call). */\n private readonly readScopeProvider?: AnalyticsServiceConfig['getReadScope'];\n /** Compiled datasets by name — feeds the join allowlist (D-C) and queryDataset. */\n private readonly datasetRegistry = new Map<string, CompiledDataset>();\n /** Optional object-graph resolver used when compiling datasets. */\n private readonly relationshipResolver?: RelationshipResolver;\n private readonly measureCurrency?: AnalyticsServiceConfig['measureCurrency'];\n /** Optional dimension display-label resolver (select options / lookup names). */\n private readonly labelResolver?: DimensionLabelDeps;\n /** ADR-0037 P3: pending-seed row resolver for draft data preview. */\n private readonly draftRowsResolver?: AnalyticsServiceConfig['draftRowsResolver'];\n /** [#3867] Schema-registry probe gating cube auto-inference. */\n private readonly isRegisteredObject?: AnalyticsServiceConfig['isRegisteredObject'];\n /** [#3867] One-shot flag for the {@link assertInferableCube} stand-down warning. */\n private warnedNoObjectRegistry = false;\n readonly cubeRegistry: CubeRegistry;\n private readonly logger: Logger;\n\n constructor(config: AnalyticsServiceConfig = {}) {\n this.logger = config.logger || createLogger({ level: 'info', format: 'pretty' });\n this.cubeRegistry = new CubeRegistry();\n\n // Register pre-defined cubes\n if (config.cubes) {\n this.cubeRegistry.registerAll(config.cubes);\n }\n\n this.readScopeProvider = config.getReadScope;\n this.relationshipResolver = config.relationshipResolver;\n this.measureCurrency = config.measureCurrency;\n this.labelResolver = config.labelResolver;\n this.draftRowsResolver = config.draftRowsResolver;\n this.isRegisteredObject = config.isRegisteredObject;\n\n // Compile + register pre-defined datasets (ADR-0021).\n if (config.datasets) {\n for (const ds of config.datasets) {\n try {\n this.registerDataset(ds);\n } catch (e) {\n this.logger?.warn?.(`[Analytics] Failed to register dataset \"${ds?.name}\": ${String((e as Error)?.message ?? e)}`);\n }\n }\n }\n\n // Build the context-independent strategy context. `getReadScope` is bound\n // per query in `callCtx(context)` so it can resolve the active tenant.\n this.baseCtx = {\n getCube: (name) => this.cubeRegistry.get(name),\n queryCapabilities: config.queryCapabilities || (() => DEFAULT_CAPABILITIES),\n executeRawSql: config.executeRawSql,\n executeAggregate: config.executeAggregate,\n fallbackService: config.fallbackService,\n // Prefer a compiled dataset's declared relationships (D-C join allowlist);\n // fall back to any explicitly-configured provider for legacy cubes.\n getAllowedRelationships: (cubeName: string) =>\n this.datasetRegistry.get(cubeName)?.allowedRelationships\n ?? config.getAllowedRelationships?.(cubeName),\n coerceTemporalFilterValue: config.coerceTemporalFilterValue,\n isExternalObject: config.isExternalObject,\n };\n\n // Build strategy chain (built-in + custom, sorted by priority)\n // InMemoryStrategy is NOT built-in — it lives in @objectstack/driver-memory\n // and should be passed via config.strategies when needed.\n // When fallbackService is configured, an internal delegate is added at P3.\n const builtIn: AnalyticsStrategy[] = [\n new NativeSQLStrategy(),\n new ObjectQLStrategy(),\n ];\n\n // Auto-add fallback delegate when fallbackService is provided\n if (config.fallbackService) {\n builtIn.push(new FallbackDelegateStrategy());\n }\n\n const custom = config.strategies || [];\n this.strategies = [...builtIn, ...custom].sort((a, b) => a.priority - b.priority);\n\n this.logger.info(\n `[Analytics] Initialized with ${this.cubeRegistry.size} cubes, ` +\n `${this.strategies.length} strategies: ${this.strategies.map(s => s.name).join(' → ')}`,\n );\n }\n\n /**\n * Build a per-call StrategyContext that binds the read-scope provider to the\n * current request's ExecutionContext (ADR-0021 D-C). The strategy then sees a\n * `getReadScope(objectName)` that already knows the active tenant.\n */\n private async callCtx(\n query: AnalyticsQuery,\n context?: ExecutionContext,\n ): Promise<StrategyContext> {\n // #3602 — `context` rides along unconditionally. It is the ENGINE-side belt\n // (forwarded to `engine.aggregate`, where the middleware chain applies its\n // own RLS), so it must not be gated on the analytics-side belt being wired:\n // a deployment with no `getReadScope` provider is exactly the one that most\n // needs the engine to scope for it.\n if (!this.readScopeProvider) return { ...this.baseCtx, context };\n // Pre-resolve the read scope for every object the strategy will scan (base\n // + all declared joins) BEFORE the synchronous SQL builder runs, since the\n // provider may be async (the production `security.getReadFilter` bridge).\n // The strategy then reads each object's filter synchronously from the map.\n const scopes = await this.resolveReadScopes(query, context);\n return {\n ...this.baseCtx,\n context,\n getReadScope: (objectName: string) => scopes.get(objectName) ?? null,\n };\n }\n\n /**\n * Resolve the read scope (tenant + RLS `FilterCondition`) for the base object\n * AND every joined object of the query's cube, keyed by object name. This is\n * the async pre-pass that lets the synchronous strategy enforce scoping even\n * when the provider (security `getReadFilter`) resolves asynchronously.\n *\n * The object set is `cube.sql` (base) plus every `cube.joins[*].name` — a\n * SUPERSET of what the strategy actually scans (the strategy only joins along\n * declared relationships), so no scanned object is ever left unscoped.\n *\n * Fail-closed: if the provider throws for an object, the whole query is\n * rejected rather than emitting SQL with that object unscoped.\n */\n private async resolveReadScopes(\n query: AnalyticsQuery,\n context?: ExecutionContext,\n ): Promise<Map<string, FilterCondition>> {\n const map = new Map<string, FilterCondition>();\n const provider = this.readScopeProvider;\n if (!provider || !query.cube) return map;\n const cube = this.cubeRegistry.get(query.cube);\n if (!cube) return map;\n\n const objects = new Set<string>();\n if (typeof cube.sql === 'string' && cube.sql.trim()) {\n objects.add(cube.sql.trim());\n }\n const joins = (cube as { joins?: Record<string, { name?: string }> }).joins;\n if (joins) {\n for (const [alias, j] of Object.entries(joins)) {\n objects.add(j?.name ?? alias);\n }\n }\n\n for (const object of objects) {\n let filter: FilterCondition | null | undefined;\n try {\n filter = await provider(object, context);\n } catch (e) {\n // Deny the entire query — never fall through to unscoped SQL.\n this.logger.error?.(\n `[Analytics] read-scope resolution failed for object \"${object}\" — ` +\n `rejecting query (fail-closed, ADR-0021 D-C)`,\n e instanceof Error ? e : new Error(String(e)),\n );\n throw new Error(\n `[Analytics] read-scope resolution failed for \"${object}\"; query denied (fail-closed).`,\n );\n }\n if (filter != null) map.set(object, filter);\n }\n return map;\n }\n\n /**\n * Execute an analytical query by delegating to the first capable strategy.\n *\n * A strategy can discover only AT EXECUTION TIME that the underlying driver\n * cannot serve it — the canonical case is NativeSQLStrategy on an in-memory\n * driver, whose `execute()` returns null for raw SQL (the auto-bridge throws\n * `RAW_SQL_UNSUPPORTED`). That is a capability miss, not a query error: fall\n * back to the next capable strategy (e.g. ObjectQLStrategy over the\n * aggregate bridge) instead of failing — or worse, fabricating empty rows.\n * Any other error propagates untouched.\n */\n async query(query: AnalyticsQuery, context?: ExecutionContext): Promise<AnalyticsResult> {\n if (!query.cube) {\n throw new Error('Cube name is required in analytics query');\n }\n\n this.ensureCube(query);\n const ctx = await this.callCtx(query, context);\n let skip: Set<AnalyticsStrategy> | undefined;\n for (;;) {\n const strategy = this.resolveStrategy(query, ctx, skip);\n this.logger.debug(`[Analytics] Query on cube \"${query.cube}\" → ${strategy.name}`);\n try {\n return await strategy.execute(query, ctx);\n } catch (e) {\n if ((e as { code?: string })?.code === 'RAW_SQL_UNSUPPORTED') {\n this.logger.warn(\n `[Analytics] ${strategy.name} cannot run on this driver (raw SQL unsupported) — falling back to the next strategy.`,\n );\n (skip ??= new Set()).add(strategy);\n continue;\n }\n throw e;\n }\n }\n }\n\n /**\n * Compile a `dataset` (ADR-0021) and register its Cube + join allowlist so it\n * can be queried by name. Idempotent (re-registering overwrites). Returns the\n * compiled dataset.\n */\n registerDataset(dataset: Dataset): CompiledDataset {\n const compiled = compileDataset(dataset, this.relationshipResolver);\n this.cubeRegistry.register(compiled.cube);\n this.datasetRegistry.set(dataset.name, compiled);\n return compiled;\n }\n\n /**\n * Execute a semantic-layer dataset (ADR-0021). Compiles the dataset (saved or\n * inline draft — Studio preview), registers its Cube + join allowlist, then\n * runs the selection through the `DatasetExecutor` with the request context so\n * tenant/RLS scoping (D-C) is applied. See {@link IAnalyticsService.queryDataset}.\n */\n async queryDataset(\n dataset: Dataset,\n selection: DatasetSelection,\n context?: ExecutionContext,\n options?: { previewDrafts?: boolean },\n ): Promise<AnalyticsResult> {\n const compiled = this.registerDataset(dataset);\n this.logger.debug(`[Analytics] queryDataset \"${dataset.name}\" (object=${dataset.object}, include=${(dataset.include ?? []).join(',') || '—'})`);\n\n // ── ADR-0037 P3 — draft data preview ────────────────────────────────────\n // When the request renders the as-if-published world AND the base object\n // has a PENDING seed draft, evaluate the selection over the seed's rows in\n // memory (a query-evaluating proxy feeds the unchanged DatasetExecutor, so\n // measure filters / compareTo / derived measures all behave identically).\n // No pending seed → fall through to the real engine: published objects\n // keep charting live data even inside a preview.\n if (options?.previewDrafts && this.draftRowsResolver) {\n let seedRows: Record<string, unknown>[] | null = null;\n try {\n seedRows = await this.draftRowsResolver(dataset.object, context);\n } catch (e) {\n this.logger.warn(`[Analytics] draft preview resolver failed for \"${dataset.object}\" — falling back to live data: ${String((e as Error)?.message ?? e)}`);\n }\n if (seedRows) {\n this.logger.debug(`[Analytics] queryDataset \"${dataset.name}\" → preview over ${seedRows.length} drafted seed row(s)`);\n const previewService = {\n query: async (q: AnalyticsQuery) => evaluateAnalyticsQueryOverRows(q, compiled.cube, seedRows!),\n } as IAnalyticsService;\n const previewResult = await new DatasetExecutor(previewService).execute(compiled, selection, context);\n // Label resolution is skipped on purpose: drafted seed rows reference\n // lookups by NAME (the seed convention), which already reads well.\n return previewResult;\n }\n }\n\n // #3602 — every label lookup in this request (sort keys below, display\n // labels further down) reads the REFERENCED object, so bind that object's\n // own read scope to this request once, up front.\n const provider = this.readScopeProvider;\n const resolveScope = provider\n ? (targetObject: string) => provider(targetObject, context)\n : undefined;\n // #3680 — per-request label-fetch cache. A selection that sorts by a\n // lookup dimension resolves labels twice (pre-window sort keys, then\n // post-window display); the cache makes the display pass reuse the ids the\n // sort already fetched, so label-ordering costs ONE id→name read total.\n const labelDeps = this.labelResolver ? withLabelFetchCache(this.labelResolver) : undefined;\n // #3680 — hand the executor the sort-key label hook so an `order` on a\n // select/lookup dimension sorts by the label the user reads. Built over\n // the SAME capabilities (and read scope) as the display resolution below.\n const orderLabels = labelDeps && dataset.dimensions?.length\n ? createOrderLabelResolver(\n dataset.object,\n dataset.dimensions\n .filter((d) => !!d.field)\n .map((d) => ({ name: d.name, field: d.field as string })),\n labelDeps,\n resolveScope,\n context,\n )\n : undefined;\n\n // Graceful degradation: a dashboard/report widget whose backing object or\n // table is not present in this kernel (e.g. a platform dashboard like\n // System Overview that charts `sys_audit_log`, opened in an environment\n // that never mounted the audit object) must render as \"no data\" — NOT\n // crash the widget with a 500. Datasets were the one read surface that\n // hard-failed on a missing source.\n let result: AnalyticsResult;\n try {\n result = await new DatasetExecutor(this, orderLabels).execute(compiled, selection, context);\n } catch (err) {\n if (isMissingSourceError(err)) {\n this.logger.warn(\n `[Analytics] dataset \"${dataset.name}\" backing object \"${dataset.object}\" is unavailable ` +\n `(${String((err as Error)?.message ?? err)}); returning an empty result instead of failing the widget`,\n );\n return { rows: [], fields: [], totals: [] };\n }\n throw err;\n }\n\n // Selected dimensions resolved against the dataset definition — shared by\n // drill metadata, label resolution, and dimension field-label enrichment.\n const selectedDims = (selection.dimensions ?? [])\n .map((name) => dataset.dimensions?.find((d) => d.name === name))\n .filter((d): d is NonNullable<typeof d> => !!d);\n\n // ADR-0021 D2 — drill-through metadata. A host (dashboard/report) drills a\n // clicked bucket back to the underlying records, but it only knows the\n // dimension NAMES, and the label resolution below OVERWRITES the raw grouped\n // value in each row with its display label. So before that happens, snapshot\n // the raw grouped values into a PARALLEL array (aligned to `rows` by index —\n // the result rows are NOT mutated) and expose the dataset's `object` +\n // dimension→field mapping so the renderer can build an exact-match filter.\n // Date buckets are excluded — a humanized bucket (\"2026-06\") can't be\n // exact-matched against the stored timestamp, so they are not drillable.\n const drillDims = selectedDims.filter((d) => !!d.field && d.type !== 'date');\n if (drillDims.length && result.rows.length) {\n (result as AnalyticsResultWithDrill).object = dataset.object;\n (result as AnalyticsResultWithDrill).dimensionFields = Object.fromEntries(\n drillDims.map((d) => [d.name, d.field as string]),\n );\n (result as AnalyticsResultWithDrill).drillRawRows = result.rows.map((row) => {\n const raw: Record<string, unknown> = {};\n for (const d of drillDims) raw[d.name] = row[d.name];\n return raw;\n });\n // #3214 — the totals/subtotal rows (#1753) carry dimension values too and\n // go through the SAME label resolution below, so snapshot their raw\n // grouped values here in the same pre-label pass. Aligned to `result.totals`\n // by index; each grouping is restricted to the drillable dims it actually\n // groups by (the grand-total grouping `[]` keeps empty maps, so a subtotal\n // drill filters by the stored value while the grand total drills unfiltered).\n if (result.totals?.length) {\n (result as AnalyticsResultWithDrill).drillRawTotals = result.totals.map((total) => {\n const groupingDims = drillDims.filter((d) => total.dimensions.includes(d.name));\n return total.rows.map((row) => {\n const raw: Record<string, unknown> = {};\n for (const d of groupingDims) raw[d.name] = row[d.name];\n return raw;\n });\n });\n }\n }\n\n // #1752 — date-range drill scope. A `dateGranularity` dimension groups a\n // SPAN of records into one bucket, so drilling it needs a half-open range\n // `[gte, lt)`, which the equality `drillRawRows` sidecar can't express\n // (that's exactly why date dims are excluded from `drillDims` above). Emit\n // a parallel range sidecar computed — via the shared inverse util so server\n // and client agree on boundaries — from the canonical bucket KEY, which is\n // still in `rows[i][dim]` here (this runs BEFORE label resolution rewrites\n // it to a display label).\n const rangeTz = selection.timezone ?? context?.timezone ?? 'UTC';\n // Per drillable date+granularity dim, decide how to serialize its bounds\n // (ADR-0053 temporal semantics):\n // - `datetime` → the reference tz's MIDNIGHT INSTANT (ISO), because the\n // bucket is defined on that tz's calendar (works under any tz, incl. DST);\n // - `date` → `YYYY-MM-DD` calendar bounds, a tz-naive calendar day that is\n // exact under ANY reference tz;\n // - unknown field type → safe only under UTC (where the calendar day and\n // its instant coincide); under a non-UTC tz we can't tell whether to\n // shift, so the dim is omitted and the host drills a superset.\n // The bucket size to invert MUST be the one the query actually grouped by —\n // `selection.dateGranularity` overrides the dataset dimension's default\n // (#3588). Reading `d.dateGranularity` here meant a widget that bucketed by\n // quarter or year got its ranges computed from the dataset's month (or,\n // when the dataset declared none, dropped entirely) — so drilling a bucket\n // opened the wrong span, or the chart lost drill-through altogether.\n const rangeDims: Array<{ d: (typeof selectedDims)[number]; granularity: DateGranularityValue; instant: boolean }> = [];\n for (const d of selectedDims) {\n if (!d.field || d.type !== 'date') continue;\n const granularity = resolveDimensionGranularity(selection, d.name, d.dateGranularity);\n if (!granularity) continue;\n const ftype = this.measureCurrency?.(dataset.object, d.field as string)?.type;\n if (ftype === 'datetime') rangeDims.push({ d, granularity, instant: true });\n else if (ftype === 'date') rangeDims.push({ d, granularity, instant: false });\n else if (rangeTz === 'UTC') rangeDims.push({ d, granularity, instant: false });\n // else: unknown field type under a non-UTC reference tz → omit (superset).\n }\n if (rangeDims.length && result.rows.length) {\n const bound = (ymd: string, instant: boolean): string =>\n instant ? new Date(zonedDateStartToUtcMs(ymd, rangeTz)).toISOString() : ymd;\n (result as AnalyticsResultWithDrill).drillRanges = result.rows.map((row) => {\n const ranges: Record<string, { field: string; gte: string; lt: string }> = {};\n for (const { d, granularity, instant } of rangeDims) {\n // A row in the empty bucket carries `null` here (#3839) and yields no\n // range, so that row simply gets no drill bound — the superset.\n const cal = bucketKeyToCalendarRange(row[d.name] as string | null, granularity);\n if (cal) {\n ranges[d.name] = { field: d.field as string, gte: bound(cal.start, instant), lt: bound(cal.end, instant) };\n }\n }\n return ranges;\n });\n // The equality drill block sets `object` only when a NON-date drill dim\n // exists; a report grouped ONLY by time still needs the base object so the\n // host can open its list. Safe to (re)set to the same dataset object.\n (result as AnalyticsResultWithDrill).object = dataset.object;\n }\n\n // ADR-0021 — resolve grouped dimension values to human display labels\n // (select option label, lookup related-record name). Charts render the\n // dimension key verbatim, so this is the single place that turns a stored\n // value / FK id into the text a user expects to read.\n if (labelDeps && selectedDims.length) {\n // Same single-source rule as the drill ranges above: a date bucket must be\n // LABELLED with the granularity it was actually grouped by (#3588).\n // Formatting a `year` bucket with the dataset's `month` default rendered\n // it as \"1970-01\" — the year key re-parsed as an epoch millisecond count.\n const dims = selectedDims\n .filter((d) => !!d.field)\n .map((d) => ({\n name: d.name,\n field: d.field,\n type: d.type,\n dateGranularity: resolveDimensionGranularity(selection, d.name, d.dateGranularity),\n }));\n if (dims.length) {\n // `resolveScope` (hoisted above) binds the referenced object's read\n // scope to THIS request so the label lookup (a per-record read of the\n // related object) cannot surface a record the referenced object's RLS\n // would hide (#3602). `labelDeps` is the per-request cache over the\n // configured resolver, so ids the sort-key pass (#3680) already fetched\n // are not fetched again here.\n try {\n // `context` rides alongside `resolveScope` — the label lookup's SECOND\n // belt. `resolveScope` is this layer's own predicate; the context lets\n // the engine's middleware scope the same per-record read itself.\n await resolveDimensionLabels(dataset.object, dims, result.rows, labelDeps, resolveScope, context);\n // Totals rows (#1753) carry dimension values too (a row subtotal is\n // keyed by its row bucket) — resolve each grouping's own subset.\n for (const total of result.totals ?? []) {\n const subset = dims.filter((d) => total.dimensions.includes(d.name));\n if (subset.length) {\n await resolveDimensionLabels(dataset.object, subset, total.rows, labelDeps, resolveScope, context);\n }\n }\n } catch (e) {\n this.logger?.warn?.(`[Analytics] dimension label resolution failed for \"${dataset.name}\": ${String((e as Error)?.message ?? e)}`);\n }\n }\n }\n\n // ADR-0021 — enrich measure columns with their display `label` + `format`\n // so presentations show \"Tasks\" / \"$616,000\" instead of the raw measure\n // name \"task_count\" / \"616000\". Carried on the result fields; the renderer\n // applies the format (it can't be baked into the numeric row value).\n if (result.fields?.length && dataset.measures?.length) {\n const measureByName = new Map(dataset.measures.map((m) => [m.name, m]));\n for (const f of result.fields) {\n const m = measureByName.get(f.name) ?? measureByName.get(f.name.replace(/__compare$/, ''));\n if (!m) continue;\n if (f.label == null && typeof m.label === 'string') f.label = m.label;\n if (f.format == null && m.format) f.format = m.format;\n // ADR-0053 currency chain. A MONETARY measure resolves its display\n // currency from: explicit measure `currency` → source-field\n // `currencyConfig.defaultCurrency` → tenant default (`ctx.currency`). A\n // measure is monetary if it declares a currency OR aggregates a\n // `currency`-type field; non-monetary measures (count, avg of a plain\n // number) never receive a currency code.\n const fc = f as { currency?: string };\n const mc = m as { currency?: string };\n if (fc.currency == null) {\n const meta = m.field ? this.measureCurrency?.(dataset.object, m.field) : undefined;\n const monetary = !!mc.currency || meta?.type === 'currency';\n if (monetary) {\n const resolved = mc.currency ?? meta?.defaultCurrency ?? context?.currency;\n if (resolved) fc.currency = resolved;\n }\n }\n }\n }\n\n // Enrich DIMENSION columns with their display `label` too, so a grouped\n // table header reads \"Status\" instead of the raw field name \"status\". The\n // measure-only enrichment above left dimension headers bare (the renderer\n // then fell back to the raw dimension name).\n if (result.fields?.length && selectedDims.length) {\n const dimByName = new Map(selectedDims.map((d) => [d.name, d]));\n const dimByField = new Map(selectedDims.filter((d) => !!d.field).map((d) => [d.field as string, d]));\n for (const f of result.fields) {\n if (f.label != null) continue;\n // Result fields may be keyed by the dataset dimension NAME or the\n // underlying cube FIELD depending on strategy — match either.\n const d = dimByName.get(f.name) ?? dimByField.get(f.name);\n if (d && typeof d.label === 'string') f.label = d.label;\n }\n }\n return result;\n }\n\n /**\n * Get cube metadata for discovery.\n */\n async getMeta(cubeName?: string): Promise<CubeMeta[]> {\n // If a fallback service is configured, merge its metadata with the registry\n const cubes = cubeName\n ? [this.cubeRegistry.get(cubeName)].filter(Boolean) as Cube[]\n : this.cubeRegistry.getAll();\n\n return cubes.map(cube => ({\n name: cube.name,\n title: cube.title,\n measures: Object.entries(cube.measures).map(([key, measure]) => ({\n name: `${cube.name}.${key}`,\n type: measure.type,\n title: measure.label,\n })),\n dimensions: Object.entries(cube.dimensions).map(([key, dimension]) => ({\n name: `${cube.name}.${key}`,\n type: dimension.type,\n title: dimension.label,\n })),\n }));\n }\n\n /**\n * Generate SQL for a query without executing it (dry-run).\n */\n async generateSql(query: AnalyticsQuery, context?: ExecutionContext): Promise<{ sql: string; params: unknown[] }> {\n if (!query.cube) {\n throw new Error('Cube name is required for SQL generation');\n }\n\n this.ensureCube(query);\n const ctx = await this.callCtx(query, context);\n const strategy = this.resolveStrategy(query, ctx);\n this.logger.debug(`[Analytics] generateSql on cube \"${query.cube}\" → ${strategy.name}`);\n\n return strategy.generateSql(query, ctx);\n }\n\n // ── Internal ─────────────────────────────────────────────────────\n\n /**\n * Ensure a cube exists for the given query and that it knows about every\n * measure referenced by the query.\n *\n * - If no cube is registered for `query.cube`, infer a minimal cube from\n * the query so downstream strategies (which assume `cube.sql` exists)\n * don't crash.\n * - If a cube exists but the query references measures that aren't in\n * `cube.measures` (e.g. `amount_sum`, `amount_avg` emitted by dashboard\n * widget translators), inject suffix-inferred Metric entries so the\n * strategies pick the right aggregation function and field.\n */\n private ensureCube(query: AnalyticsQuery): void {\n const name = query.cube!;\n let cube = this.cubeRegistry.get(name);\n\n if (!cube) {\n // [#3867] Auto-inference below sets `cube.sql = name`, so from here on\n // the queried string IS a physical table name. Verify it names a\n // registered object BEFORE that happens — otherwise `/analytics/query`\n // is a way to aggregate over any table the connection can see, exactly\n // the hole #3770 closed on the data path. A registered Cube needs no\n // such check: it was authored, and its `sql` is whatever it declares.\n this.assertInferableCube(name);\n cube = this.inferCubeFromQuery(query);\n this.cubeRegistry.register(cube);\n // A scalar query — only measures, no grouping (no `dimensions`/\n // `timeDimensions`) — is the first-class \"metric over an object\" path\n // (e.g. the `object-metric` KPI widget). Auto-inferring a count/sum cube\n // is the intended behaviour there, so log at debug. A query that groups\n // by an explicit dimension or time bucket almost certainly meant to hit a\n // registered cube; keep that at warn so a forgotten registration is loud.\n const isScalarMetric =\n (query.dimensions?.length ?? 0) === 0 && (query.timeDimensions?.length ?? 0) === 0;\n const message =\n `[Analytics] No cube registered for \"${name}\"; auto-inferred a minimal cube ` +\n `(sql=\"${name}\", measures=${Object.keys(cube.measures).join(',') || '(none)'}, ` +\n `dimensions=${Object.keys(cube.dimensions).join(',') || '(none)'}). ` +\n `Define an explicit Cube in your stack for full control.`;\n if (isScalarMetric) this.logger.debug(message);\n else this.logger.warn(message);\n return;\n }\n\n // Cube exists — check for unknown measures referenced by the query and\n // augment the cube with suffix-inferred Metric definitions so callers\n // that pass `<field>_sum` / `<field>_avg` etc. get the right aggregation.\n const stripPrefix = (m: string) => (m.includes('.') ? m.split('.').slice(1).join('.') : m);\n const extraMeasures: Record<string, any> = {};\n for (const m of query.measures || []) {\n const key = stripPrefix(m);\n if (cube.measures[key] || extraMeasures[key]) continue;\n extraMeasures[key] = inferMeasure(key);\n }\n if (Object.keys(extraMeasures).length > 0) {\n const augmented: Cube = {\n ...cube,\n measures: { ...cube.measures, ...extraMeasures },\n };\n this.cubeRegistry.register(augmented);\n this.logger.debug(\n `[Analytics] Augmented cube \"${name}\" with inferred measures: ${Object.keys(extraMeasures).join(',')}`,\n );\n }\n }\n\n /**\n * [#3867] Gate on the cube auto-inference path: a name with no registered\n * Cube may only be inferred into one if it is a registered object.\n *\n * Rejects with `status: 404` / `code: 'CUBE_NOT_FOUND'` so the HTTP boundary\n * answers \"no such cube\" instead of letting the name reach the driver as a\n * table and surfacing whatever the driver says about it. The message names\n * both ways the request could be made valid, because from here the two are\n * genuinely indistinguishable: register a Cube, or register the object.\n *\n * Skips when `isRegisteredObject` was not supplied — see the config field's\n * doc for why that tier is a deliberate stand-down and not a hole.\n */\n private assertInferableCube(name: string): void {\n const isRegisteredObject = this.isRegisteredObject;\n if (!isRegisteredObject) {\n if (!this.warnedNoObjectRegistry) {\n this.warnedNoObjectRegistry = true;\n this.logger.warn(\n '[Analytics] no object-registry hook configured — the cube-inference existence gate ' +\n '(#3867) is INACTIVE for this service; an unregistered cube name reaches the driver ' +\n 'as a raw table name.',\n );\n }\n return;\n }\n if (isRegisteredObject(name)) return;\n const err = new Error(\n `Cube '${name}' not found: no cube is registered under that name, and it is not a ` +\n `registered object either (a cube can only be auto-inferred from a registered object). ` +\n `Define a Cube in your stack, or check the object name.`,\n ) as Error & { code?: string; status?: number; cube?: string };\n err.code = 'CUBE_NOT_FOUND';\n err.status = 404;\n err.cube = name;\n throw err;\n }\n\n /** Build a minimal Cube from the fields referenced by an AnalyticsQuery. */\n private inferCubeFromQuery(query: AnalyticsQuery): Cube {\n const cubeName = query.cube!;\n const measures: Record<string, any> = {};\n const dimensions: Record<string, any> = {};\n\n const stripPrefix = (m: string) => (m.includes('.') ? m.split('.').slice(1).join('.') : m);\n\n // Always provide a default `count` measure\n measures.count = { name: 'count', label: 'Count', type: 'count', sql: '*' };\n\n for (const m of query.measures || []) {\n const key = stripPrefix(m);\n if (measures[key]) continue;\n const inferred = inferMeasure(key);\n measures[key] = inferred;\n }\n\n for (const d of query.dimensions || []) {\n const key = stripPrefix(d);\n if (dimensions[key]) continue;\n dimensions[key] = { name: key, label: key, type: 'string', sql: key };\n }\n\n if (query.where && typeof query.where === 'object' && !Array.isArray(query.where)) {\n // Canonical FilterCondition: top-level keys (excluding logical\n // combinators) are field names. We only need them to seed an\n // ad-hoc cube definition for free-form queries.\n for (const key of Object.keys(query.where as Record<string, unknown>)) {\n if (key.startsWith('$')) continue;\n const stripped = stripPrefix(key);\n if (dimensions[stripped] || measures[stripped]) continue;\n dimensions[stripped] = { name: stripped, label: stripped, type: 'string', sql: stripped };\n }\n }\n\n for (const td of query.timeDimensions || []) {\n const key = stripPrefix(td.dimension);\n if (dimensions[key]) continue;\n dimensions[key] = {\n name: key, label: key, type: 'time', sql: key,\n granularities: ['day', 'week', 'month', 'quarter', 'year'],\n };\n }\n\n return {\n name: cubeName,\n title: cubeName,\n sql: cubeName,\n measures,\n dimensions,\n public: false,\n };\n }\n\n /**\n * Walk the strategy chain and return the first strategy that can handle the\n * query. `skip` excludes strategies that already proved incapable at\n * execution time (see {@link query}'s RAW_SQL_UNSUPPORTED fallback).\n */\n private resolveStrategy(\n query: AnalyticsQuery,\n ctx: StrategyContext,\n skip?: Set<AnalyticsStrategy>,\n ): AnalyticsStrategy {\n for (const strategy of this.strategies) {\n if (skip?.has(strategy)) continue;\n if (strategy.canHandle(query, ctx)) {\n return strategy;\n }\n }\n throw new Error(\n `[Analytics] No strategy can handle query for cube \"${query.cube}\". ` +\n `Checked: ${this.strategies.map(s => s.name).join(', ')}${skip?.size ? ` (skipped at runtime: ${[...skip].map((s) => s.name).join(', ')})` : ''}. ` +\n 'Ensure a compatible driver is configured or a fallback service is registered.',\n );\n }\n}\n\n/**\n * Infer a Metric definition from a measure key name.\n *\n * Recognised suffix conventions (matches dashboard widget translators that\n * emit measures like `<field>_sum`, `<field>_avg`):\n *\n * | Suffix | Aggregation |\n * |:-------------------|:----------------|\n * | `count` | `count(*)` |\n * | `_sum` | `sum(field)` |\n * | `_avg` / `_average`| `avg(field)` |\n * | `_min` | `min(field)` |\n * | `_max` | `max(field)` |\n * | `_count_distinct` | `count(distinct field)` |\n *\n * Anything else is treated as a `sum(<key>)` — best-effort default for an\n * unknown numeric measure.\n */\nexport function inferMeasure(key: string): { name: string; label: string; type: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'; sql: string } {\n if (key === 'count') {\n return { name: 'count', label: 'Count', type: 'count', sql: '*' };\n }\n const suffixes: Array<[string, 'sum' | 'avg' | 'min' | 'max' | 'count_distinct']> = [\n ['_count_distinct', 'count_distinct'],\n ['_sum', 'sum'],\n ['_avg', 'avg'],\n ['_average', 'avg'],\n ['_min', 'min'],\n ['_max', 'max'],\n ];\n for (const [suffix, type] of suffixes) {\n if (key.endsWith(suffix)) {\n const field = key.slice(0, -suffix.length) || '*';\n return { name: key, label: key, type, sql: field };\n }\n }\n return { name: key, label: key, type: 'sum', sql: key };\n}\n\n/**\n * FallbackDelegateStrategy — Internal strategy for fallback service delegation.\n *\n * Automatically added to the strategy chain when `fallbackService` is configured.\n * Not exported — consumers who need explicit in-memory support should use\n * `InMemoryStrategy` from `@objectstack/driver-memory`.\n */\nclass FallbackDelegateStrategy implements AnalyticsStrategy {\n readonly name = 'FallbackDelegateStrategy';\n readonly priority = 30;\n\n canHandle(query: AnalyticsQuery, ctx: StrategyContext): boolean {\n if (!query.cube) return false;\n return !!ctx.fallbackService;\n }\n\n async execute(query: AnalyticsQuery, ctx: StrategyContext): Promise<AnalyticsResult> {\n return ctx.fallbackService!.query(query);\n }\n\n async generateSql(query: AnalyticsQuery, ctx: StrategyContext): Promise<{ sql: string; params: unknown[] }> {\n if (ctx.fallbackService?.generateSql) {\n return ctx.fallbackService.generateSql(query);\n }\n return {\n sql: `-- FallbackDelegateStrategy: SQL generation not supported for cube \"${query.cube}\"`,\n params: [],\n };\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Cube } from '@objectstack/spec/data';\n\n/**\n * CubeRegistry — Central registry for analytics cube definitions.\n *\n * Cubes can be registered from two sources:\n * 1. **Manifest definitions** — Explicit cube definitions in `objectstack.config.ts`.\n * 2. **Object schema inference** — Auto-generated cubes from ObjectQL object schemas.\n *\n * The registry is the single source of truth for cube metadata discovery\n * (used by `getMeta()` and the strategy chain).\n */\nexport class CubeRegistry {\n private cubes = new Map<string, Cube>();\n\n /** Register a single cube definition. Overwrites if name already exists. */\n register(cube: Cube): void {\n this.cubes.set(cube.name, cube);\n }\n\n /** Register multiple cube definitions at once. */\n registerAll(cubes: Cube[]): void {\n for (const cube of cubes) {\n this.register(cube);\n }\n }\n\n /** Get a cube definition by name. */\n get(name: string): Cube | undefined {\n return this.cubes.get(name);\n }\n\n /** Check if a cube is registered. */\n has(name: string): boolean {\n return this.cubes.has(name);\n }\n\n /** Return all registered cubes. */\n getAll(): Cube[] {\n return Array.from(this.cubes.values());\n }\n\n /** Return all cube names. */\n names(): string[] {\n return Array.from(this.cubes.keys());\n }\n\n /** Number of registered cubes. */\n get size(): number {\n return this.cubes.size;\n }\n\n /** Remove all cubes. */\n clear(): void {\n this.cubes.clear();\n }\n\n /**\n * Auto-generate a cube definition from an object schema.\n *\n * Heuristic rules:\n * - `number` fields → `sum`, `avg`, `min`, `max` measures\n * - `boolean` fields → `count` measure (count where true)\n * - All non-computed fields → dimensions\n * - `date`/`datetime` fields → time dimensions with standard granularities\n * - A default `count` measure is always added\n *\n * @param objectName - The snake_case object name (used as table/cube name)\n * @param fields - Array of field descriptors `{ name, type, label? }`\n */\n inferFromObject(\n objectName: string,\n fields: Array<{ name: string; type: string; label?: string }>,\n ): Cube {\n const measures: Record<string, any> = {\n count: {\n name: 'count',\n label: 'Count',\n type: 'count',\n sql: '*',\n },\n };\n const dimensions: Record<string, any> = {};\n\n for (const field of fields) {\n const label = field.label || field.name;\n\n // All fields become dimensions\n const dimType = this.fieldTypeToDimensionType(field.type);\n dimensions[field.name] = {\n name: field.name,\n label,\n type: dimType,\n sql: field.name,\n ...(dimType === 'time'\n ? { granularities: ['day', 'week', 'month', 'quarter', 'year'] }\n : {}),\n };\n\n // Numeric fields also become aggregation measures\n if (field.type === 'number' || field.type === 'currency' || field.type === 'percent') {\n measures[`${field.name}_sum`] = {\n name: `${field.name}_sum`,\n label: `${label} (Sum)`,\n type: 'sum',\n sql: field.name,\n };\n measures[`${field.name}_avg`] = {\n name: `${field.name}_avg`,\n label: `${label} (Avg)`,\n type: 'avg',\n sql: field.name,\n };\n }\n }\n\n const cube: Cube = {\n name: objectName,\n title: objectName,\n sql: objectName,\n measures,\n dimensions,\n public: false,\n };\n\n this.register(cube);\n return cube;\n }\n\n private fieldTypeToDimensionType(fieldType: string): string {\n switch (fieldType) {\n case 'number':\n case 'currency':\n case 'percent':\n return 'number';\n case 'boolean':\n return 'boolean';\n case 'date':\n case 'datetime':\n return 'time';\n default:\n return 'string';\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Filter Normalization for the Analytics Layer\n *\n * The analytics endpoint accepts filters via the canonical `where`\n * field per the unified Query DSL (`spec/data/query.zod.ts`):\n *\n * - MongoDB-style FilterCondition: `{ field: value }` /\n * `{ field: { $op: value } }` / `{ $and: [...] }` — defined in\n * `spec/data/filter.zod.ts` and used by `find()`, dashboard\n * widget `filter`, RLS, etc.\n *\n * `normalizeAnalyticsFilters` flattens the FilterCondition tree into\n * the internal array form used by the SQL/Mongo pipeline strategies.\n * Strategies stay simple — they only need to know one shape — and the\n * spec is honoured: dashboard metadata is authored once in the\n * canonical MongoDB form and the server normalizes at the boundary.\n */\n\nexport interface NormalizedAnalyticsFilter {\n member: string;\n operator: string;\n values: string[];\n}\n\nconst MONGO_TO_CUBE_OP: Record<string, string> = {\n $eq: 'equals',\n $ne: 'notEquals',\n $gt: 'gt',\n $gte: 'gte',\n $lt: 'lt',\n $lte: 'lte',\n $in: 'in',\n $nin: 'notIn',\n $contains: 'contains',\n $notContains: 'notContains',\n $exists: 'set',\n};\n\n/**\n * Stringify a filter value as the internal pipeline requires `values: string[]`.\n *\n * Booleans serialize as the tokens `'true'`/`'false'` (NOT `'1'`/`'0'`) so the\n * boolean identity survives the string roundtrip: the consuming strategies can\n * recover a real boolean for the ObjectQL engine (which compares against the\n * stored boolean type) while still binding `1`/`0` for SQL. Stringifying to\n * `'1'`/`'0'` was indistinguishable from a numeric 1/0 and made every boolean\n * equality filter / boolean group-by compare a number against a boolean — and\n * never match.\n */\nfunction stringifyForCube(v: unknown): string {\n if (v == null) return '';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (v instanceof Date) return v.toISOString();\n if (typeof v === 'object') return JSON.stringify(v);\n return String(v);\n}\n\nfunction flattenCondition(cond: Record<string, unknown>, out: NormalizedAnalyticsFilter[]): void {\n for (const [key, raw] of Object.entries(cond)) {\n if (raw === undefined) continue;\n\n if (key === '$and' && Array.isArray(raw)) {\n for (const sub of raw) {\n if (sub && typeof sub === 'object') {\n flattenCondition(sub as Record<string, unknown>, out);\n }\n }\n continue;\n }\n // Logical $or / $not require recursive WHERE building which the\n // current strategies don't yet support; ignore so partial queries\n // still run.\n if (key === '$or' || key === '$not') continue;\n\n if (raw === null) {\n out.push({ member: key, operator: 'notSet', values: [] });\n continue;\n }\n\n if (typeof raw === 'object' && !Array.isArray(raw) && !(raw instanceof Date)) {\n const wrapper = raw as Record<string, unknown>;\n const opKeys = Object.keys(wrapper).filter(k => k.startsWith('$'));\n if (opKeys.length > 0) {\n for (const opKey of opKeys) {\n const cubeOp = MONGO_TO_CUBE_OP[opKey];\n if (!cubeOp) continue;\n const v = wrapper[opKey];\n const values = Array.isArray(v)\n ? v.map(stringifyForCube)\n : [stringifyForCube(v)];\n out.push({ member: key, operator: cubeOp, values });\n }\n continue;\n }\n // Nested relation (e.g. {profile: {verified: true}}). Flatten with\n // dot-prefixed keys so cube field path resolution still works.\n for (const [nestedKey, nestedVal] of Object.entries(wrapper)) {\n flattenCondition({ [`${key}.${nestedKey}`]: nestedVal }, out);\n }\n continue;\n }\n\n // Implicit equality / array → in\n if (Array.isArray(raw)) {\n out.push({ member: key, operator: 'in', values: raw.map(stringifyForCube) });\n } else {\n out.push({ member: key, operator: 'equals', values: [stringifyForCube(raw)] });\n }\n }\n}\n\n/**\n * Normalize an analytics query's `where` (FilterCondition) into the\n * internal array form used by all strategies.\n */\nexport function normalizeAnalyticsFilters(query: { where?: unknown } | unknown): NormalizedAnalyticsFilter[] {\n if (!query || typeof query !== 'object') return [];\n\n const out: NormalizedAnalyticsFilter[] = [];\n const where = (query as { where?: unknown }).where;\n\n if (where && typeof where === 'object' && !Array.isArray(where)) {\n flattenCondition(where as Record<string, unknown>, out);\n }\n\n return out;\n}\n\n/** Recover a finite number from a purely-numeric token, else undefined. */\nfunction recoverNumber(s: string): number | undefined {\n if (/^-?\\d+(\\.\\d+)?$/.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) return n;\n }\n return undefined;\n}\n\n/**\n * Coerce a stringified filter value back into a runtime type for SQL\n * parameter binding. Better-sqlite3 (and most drivers) cannot bind a JS\n * boolean, so booleans are recovered as `1`/`0` integers; numbers are\n * recovered as numbers — avoiding string-vs-number mismatches against typed\n * columns.\n */\nexport function coerceFilterValueForSql(s: string): unknown {\n if (s === 'true') return 1;\n if (s === 'false') return 0;\n if (s === 'null') return null;\n return recoverNumber(s) ?? s;\n}\n\n/**\n * Coerce a stringified filter value back into a runtime type for the ObjectQL\n * aggregate engine. Unlike the SQL path, the engine compares against the\n * *stored* runtime type, so a boolean field holds a real `true`/`false` — bind\n * the boolean itself, NOT `1`/`0`, or the equality never matches.\n */\nexport function coerceFilterValueForObjectQL(s: string): unknown {\n if (s === 'true') return true;\n if (s === 'false') return false;\n if (s === 'null') return null;\n return recoverNumber(s) ?? s;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { FilterCondition } from '@objectstack/spec/data';\n\n/**\n * Compile an RLS / tenant read-scope `FilterCondition` into a parameterized,\n * alias-qualified SQL predicate (ADR-0021 D-C).\n *\n * This is the single, security-critical translation point between the\n * canonical Mongo-style filter the `RLSCompiler` emits and the raw SQL the\n * analytics `NativeSQLStrategy` runs. It is deliberately:\n *\n * - **Fail-closed.** Any operator, value shape, or identifier it cannot\n * translate THROWS. A read-scope predicate must never be silently dropped —\n * dropping it would run the query unscoped and leak cross-tenant data.\n * - **Injection-safe.** Field/alias identifiers are validated against a strict\n * snake_case pattern and every value is bound as a `?` placeholder (the\n * strategy renumbers `?` → `$N`). No value is ever interpolated into SQL.\n * - **Alias-qualified.** Bare fields become `\"alias\".\"field\"` so the same\n * predicate applies to the base table or any joined table.\n *\n * Supports the operators the RLS layer and common policies emit: implicit\n * equality, `$eq/$ne/$gt/$gte/$lt/$lte/$in/$nin/$between/$contains/$notContains/\n * $startsWith/$endsWith/$null/$exists`, and `$and/$or/$not` combinators.\n */\n\nconst IDENT = /^[a-z_][a-z0-9_]*$/i;\n\nfunction quoteIdent(name: string, kind: string): string {\n if (typeof name !== 'string' || !IDENT.test(name)) {\n throw new Error(`[read-scope-sql] unsafe ${kind} identifier \"${String(name)}\" — refusing to build read scope (fail-closed).`);\n }\n return `\"${name}\"`;\n}\n\nexport function compileScopedFilterToSql(\n filter: FilterCondition,\n alias: string,\n): { sql: string; params: unknown[] } {\n const quotedAlias = quoteIdent(alias, 'alias');\n const params: unknown[] = [];\n const sql = compileNode(filter, quotedAlias, params);\n return { sql, params };\n}\n\n/** Compile a filter node into a boolean SQL expression ('' = empty/no constraint). */\nfunction compileNode(node: unknown, qAlias: string, params: unknown[]): string {\n if (node === null || typeof node !== 'object' || Array.isArray(node)) {\n throw new Error('[read-scope-sql] read scope must be a filter object (fail-closed).');\n }\n const clauses: string[] = [];\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n if (key === '$and' || key === '$or') {\n if (!Array.isArray(value) || value.length === 0) {\n throw new Error(`[read-scope-sql] \"${key}\" requires a non-empty array (fail-closed).`);\n }\n const parts = (value as unknown[])\n .map((child) => compileNode(child, qAlias, params))\n .filter((s) => s.length > 0);\n if (parts.length === 0) continue;\n const joiner = key === '$and' ? ' AND ' : ' OR ';\n clauses.push(`(${parts.join(joiner)})`);\n } else if (key === '$not') {\n const inner = compileNode(value, qAlias, params);\n if (inner) clauses.push(`NOT (${inner})`);\n } else if (key.startsWith('$')) {\n throw new Error(`[read-scope-sql] unsupported top-level operator \"${key}\" (fail-closed).`);\n } else {\n clauses.push(compileField(key, value, qAlias, params));\n }\n }\n return clauses.join(' AND ');\n}\n\n/** Compile a single `field: value | { $op: ... }` entry. */\nfunction compileField(field: string, value: unknown, qAlias: string, params: unknown[]): string {\n const col = `${qAlias}.${quoteIdent(field, 'field')}`;\n\n // Scalar / null → implicit equality.\n if (value === null) return `${col} IS NULL`;\n if (typeof value !== 'object' || value instanceof Date) {\n params.push(value);\n return `${col} = ?`;\n }\n if (Array.isArray(value)) {\n throw new Error(`[read-scope-sql] bare array value for \"${field}\" — use { $in: [...] } (fail-closed).`);\n }\n\n const ops = value as Record<string, unknown>;\n const keys = Object.keys(ops);\n // A value object must be ALL operators; a non-$ key means a nested relation,\n // which a flat read scope cannot join — fail closed.\n if (keys.length === 0 || keys.some((k) => !k.startsWith('$'))) {\n throw new Error(`[read-scope-sql] \"${field}\" has a nested/relation value which is not supported in a read scope (fail-closed).`);\n }\n\n const parts: string[] = [];\n for (const op of keys) {\n parts.push(compileOperator(col, op, ops[op], field, params));\n }\n return parts.length === 1 ? parts[0] : `(${parts.join(' AND ')})`;\n}\n\nfunction bind(params: unknown[], v: unknown): string {\n params.push(v);\n return '?';\n}\n\nfunction compileOperator(col: string, op: string, val: unknown, field: string, params: unknown[]): string {\n switch (op) {\n case '$eq': return val === null ? `${col} IS NULL` : `${col} = ${bind(params, val)}`;\n case '$ne': return val === null ? `${col} IS NOT NULL` : `${col} <> ${bind(params, val)}`;\n case '$gt': return `${col} > ${bind(params, val)}`;\n case '$gte': return `${col} >= ${bind(params, val)}`;\n case '$lt': return `${col} < ${bind(params, val)}`;\n case '$lte': return `${col} <= ${bind(params, val)}`;\n case '$in': {\n if (!Array.isArray(val)) throw new Error(`[read-scope-sql] $in for \"${field}\" needs an array (fail-closed).`);\n if (val.length === 0) return '1 = 0'; // IN () matches nothing — safe\n return `${col} IN (${val.map((v) => bind(params, v)).join(', ')})`;\n }\n case '$nin': {\n if (!Array.isArray(val)) throw new Error(`[read-scope-sql] $nin for \"${field}\" needs an array (fail-closed).`);\n if (val.length === 0) return '1 = 1'; // NOT IN () excludes nothing\n return `${col} NOT IN (${val.map((v) => bind(params, v)).join(', ')})`;\n }\n case '$between': {\n if (!Array.isArray(val) || val.length !== 2) throw new Error(`[read-scope-sql] $between for \"${field}\" needs [min,max] (fail-closed).`);\n return `${col} BETWEEN ${bind(params, val[0])} AND ${bind(params, val[1])}`;\n }\n case '$contains': return `${col} LIKE ${bind(params, `%${String(val)}%`)}`;\n case '$notContains': return `${col} NOT LIKE ${bind(params, `%${String(val)}%`)}`;\n case '$startsWith': return `${col} LIKE ${bind(params, `${String(val)}%`)}`;\n case '$endsWith': return `${col} LIKE ${bind(params, `%${String(val)}`)}`;\n case '$null': return val ? `${col} IS NULL` : `${col} IS NOT NULL`;\n case '$exists': return val ? `${col} IS NOT NULL` : `${col} IS NULL`;\n default:\n throw new Error(`[read-scope-sql] unsupported operator \"${op}\" on \"${field}\" (fail-closed).`);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { AnalyticsQuery, AnalyticsResult } from '@objectstack/spec/contracts';\nimport type { Cube } from '@objectstack/spec/data';\nimport type { AnalyticsStrategy, StrategyContext } from './types.js';\nimport { normalizeAnalyticsFilters, coerceFilterValueForSql } from './filter-normalizer.js';\nimport { compileScopedFilterToSql } from '../read-scope-sql.js';\n\n/**\n * NativeSQLStrategy — Priority 1\n *\n * Pushes the analytics query down to the database as a native SQL statement.\n * This is the most efficient path and is preferred whenever the backing driver\n * supports raw SQL execution (e.g. Postgres, MySQL, SQLite).\n */\nexport class NativeSQLStrategy implements AnalyticsStrategy {\n readonly name = 'NativeSQLStrategy';\n readonly priority = 10;\n\n canHandle(query: AnalyticsQuery, ctx: StrategyContext): boolean {\n if (!query.cube) return false;\n // This strategy groups by the raw column expression (`GROUP BY <col>`) and\n // emits no `date_trunc` — it cannot bucket a date dimension to a coarser\n // granularity, nor resolve buckets on a non-UTC calendar. When the query\n // asks for granularity bucketing we therefore DECLINE so the lower-priority\n // ObjectQLStrategy handles it via `engine.aggregate` (native date_trunc when\n // UTC-safe, else uniform in-memory bucketing). Without this, a date-bucketed\n // query silently grouped by the raw timestamp — one bucket per row — and a\n // non-UTC reference timezone was ignored entirely (ADR-0053 Phase 2, #1982).\n if (query.timeDimensions?.some((td) => !!td.granularity)) return false;\n // ADR-0062 D6 — DECLINE federated (external-datasource) objects. This\n // strategy hand-compiles `FROM \"<object>\"` and bare column references, which\n // bypass the driver's physical-table resolution (`external.remoteName` /\n // `remoteSchema` / `columnMap`) and would query the WRONG table. Routing the\n // query to the lower-priority ObjectQL aggregate path keeps it correct —\n // that path goes through the driver's `getBuilder` (#2138/#2149). Applies to\n // the base object AND any joined object (a join would also hit the wrong\n // table). Until native-SQL learns the driver's resolution, \"disabled\" beats\n // \"silently wrong\".\n if (typeof ctx.isExternalObject === 'function') {\n const cube = ctx.getCube(query.cube);\n if (cube) {\n if (ctx.isExternalObject(this.extractObjectName(cube))) return false;\n const joinTargets = cube.joins ? Object.values(cube.joins) : [];\n for (const j of joinTargets) {\n const joinedObject = (j as { name?: string })?.name;\n if (joinedObject && ctx.isExternalObject(joinedObject)) return false;\n }\n }\n }\n const caps = ctx.queryCapabilities(query.cube);\n return caps.nativeSql && typeof ctx.executeRawSql === 'function';\n }\n\n async execute(query: AnalyticsQuery, ctx: StrategyContext): Promise<AnalyticsResult> {\n const { sql, params } = await this.generateSql(query, ctx);\n const cube = ctx.getCube(query.cube!)!;\n const objectName = this.extractObjectName(cube);\n\n const rows = await ctx.executeRawSql!(objectName, sql, params);\n\n // Build field metadata\n const fields = this.buildFieldMeta(query, cube);\n\n return { rows, fields, sql };\n }\n\n async generateSql(query: AnalyticsQuery, ctx: StrategyContext): Promise<{ sql: string; params: unknown[] }> {\n const cube = ctx.getCube(query.cube!);\n if (!cube) {\n throw new Error(`Cube not found: ${query.cube}`);\n }\n\n const params: unknown[] = [];\n const selectClauses: string[] = [];\n const groupByClauses: string[] = [];\n const tableName = this.extractObjectName(cube);\n // Map of relation alias → JOIN clause. Populated lazily as dotted\n // dimensions/measures/filters are resolved.\n const joins = new Map<string, string>();\n\n // Build SELECT for dimensions\n if (query.dimensions && query.dimensions.length > 0) {\n for (const dim of query.dimensions) {\n const colExpr = this.resolveDimensionSql(cube, dim, tableName, joins);\n selectClauses.push(`${colExpr} AS \"${dim}\"`);\n groupByClauses.push(colExpr);\n }\n }\n\n // Build SELECT for measures\n if (query.measures && query.measures.length > 0) {\n for (const measure of query.measures) {\n const aggExpr = this.resolveMeasureSql(cube, measure, tableName, joins);\n selectClauses.push(`${aggExpr} AS \"${measure}\"`);\n }\n }\n\n // Build WHERE clause\n const whereClauses: string[] = [];\n const normalizedFilters = normalizeAnalyticsFilters(query);\n if (normalizedFilters.length > 0) {\n for (const filter of normalizedFilters) {\n const colExpr = this.resolveFieldSql(cube, filter.member, tableName, joins);\n // Resolve the (object, column) this member binds against so the value\n // can be coerced to the column's storage form (see buildFilterClause).\n const target = this.resolveStorageTarget(cube, filter.member, tableName);\n const clause = this.buildFilterClause(colExpr, filter.operator, filter.values, params, ctx, target);\n if (clause) whereClauses.push(clause);\n }\n }\n\n // Build time dimension filters\n if (query.timeDimensions && query.timeDimensions.length > 0) {\n for (const td of query.timeDimensions) {\n const colExpr = this.resolveFieldSql(cube, td.dimension, tableName, joins);\n if (td.dateRange) {\n const range = Array.isArray(td.dateRange) ? td.dateRange : [td.dateRange, td.dateRange];\n if (range.length === 2) {\n // Same epoch-vs-text root cause as buildFilterClause: a dateRange on a\n // SQLite `Field.datetime` column compares ISO TEXT against an INTEGER\n // epoch and matches nothing. Coerce both bounds to the storage form.\n const td2 = this.resolveStorageTarget(cube, td.dimension, tableName);\n params.push(\n this.coerceTemporal(ctx, td2, range[0]),\n this.coerceTemporal(ctx, td2, range[1]),\n );\n whereClauses.push(`${colExpr} BETWEEN $${params.length - 1} AND $${params.length}`);\n }\n }\n }\n }\n\n // ── ADR-0021 D-C — enforce the join allowlist + inject per-object RLS ──\n // 1. Reject any join not backed by a relationship the dataset declared.\n const allowed = ctx.getAllowedRelationships?.(query.cube!);\n if (allowed) {\n for (const alias of joins.keys()) {\n if (!allowed.has(alias)) {\n throw new Error(\n `[NativeSQLStrategy] join \"${alias}\" is not backed by a declared relationship on ` +\n `cube \"${query.cube}\". v1 only joins along relationships listed in the dataset's \\`include\\`.`,\n );\n }\n }\n }\n // 2. Inject the tenant/RLS read scope for the base table AND every joined\n // object — this is the predicate the raw-SQL path would otherwise skip.\n this.applyReadScope(this.extractObjectName(cube), tableName, ctx, whereClauses, params);\n for (const alias of joins.keys()) {\n // The joined OBJECT (for the RLS lookup) is the target table from the\n // cube's join map; the ALIAS is how it's referenced in SQL. These differ\n // for namespaced objects (alias `account` → object `crm_account`).\n const joinedObject = cube.joins?.[alias]?.name ?? alias;\n this.applyReadScope(joinedObject, alias, ctx, whereClauses, params);\n }\n\n let sql = `SELECT ${selectClauses.join(', ')} FROM \"${tableName}\"`;\n if (joins.size > 0) {\n sql += ' ' + Array.from(joins.values()).join(' ');\n }\n if (whereClauses.length > 0) {\n sql += ` WHERE ${whereClauses.join(' AND ')}`;\n }\n if (groupByClauses.length > 0) {\n sql += ` GROUP BY ${groupByClauses.join(', ')}`;\n }\n if (query.order && Object.keys(query.order).length > 0) {\n const orderClauses = Object.entries(query.order).map(([f, d]) => `\"${f}\" ${d.toUpperCase()}`);\n sql += ` ORDER BY ${orderClauses.join(', ')}`;\n }\n if (query.limit != null) {\n sql += ` LIMIT ${query.limit}`;\n }\n if (query.offset != null) {\n sql += ` OFFSET ${query.offset}`;\n }\n\n return { sql, params };\n }\n\n // ── Helpers ──────────────────────────────────────────────────────\n\n /**\n * ADR-0021 D-C — inject an object's read scope (tenant + RLS predicate) into\n * the WHERE clause. The scope is a canonical `FilterCondition` (what the\n * RLSCompiler emits); `compileScopedFilterToSql` turns it into alias-qualified,\n * parameterized SQL (fail-closed — it throws rather than drop a predicate).\n * The `?` placeholders are then renumbered into the strategy's `$N` scheme.\n * No-op when the runtime provides no scope hook (the caller is then\n * responsible for isolation — see contract note).\n */\n private applyReadScope(\n objectName: string,\n alias: string,\n ctx: StrategyContext,\n whereClauses: string[],\n params: unknown[],\n ): void {\n if (typeof ctx.getReadScope !== 'function') return;\n const filter = ctx.getReadScope(objectName);\n if (filter === undefined || filter === null) return;\n const { sql, params: scopeParams } = compileScopedFilterToSql(filter, alias);\n if (!sql) return;\n let i = 0;\n const rendered = sql.replace(/\\?/g, () => {\n params.push(scopeParams[i++]);\n return `$${params.length}`;\n });\n whereClauses.push(`(${rendered})`);\n }\n\n /** SQL-safe join alias for a relationship path (dots → `__`); single-segment\n * paths are unchanged. Mirrors the dataset compiler's `cube.joins` keying so\n * alias, allowlist, and per-hop RLS all agree on one valid identifier. */\n private joinAlias(path: string): string {\n return path.replace(/\\./g, '__');\n }\n\n /**\n * Resolve a dimension/measure/filter SQL expression that may reference a\n * related table via dot notation (e.g. `account.industry`).\n *\n * A dotted `sql` is a relationship PATH (ADR-0071 multi-hop): every segment\n * but the last is a to-one relationship hop, the last is the column. Each hop\n * synthesises a `LEFT JOIN` aliased by its full path prefix, chained\n * parent→child. The convention (matching the auto-cube generator and\n * ObjectStack object schemas) for a single hop is:\n *\n * <parentTable>.<lookupField> = <lookupField>.id\n *\n * i.e. the lookup field name on the parent table equals the related\n * table name. This holds for all `Field.lookup({ object: '...' })`\n * declarations where the field is named after its target object.\n *\n * Returns the qualified SQL reference (e.g. `\"account\".\"industry\"`).\n * Pure column references (no dot) are returned as-is.\n */\n private qualifyAndRegisterJoin(\n rawSql: string,\n parentTable: string,\n joins: Map<string, string>,\n cube?: Cube,\n ): string {\n if (!rawSql.includes('.')) {\n // Base-table column. When the cube can join other tables, a bare column\n // that also exists on a joined table (e.g. base `status` vs joined\n // `account.status`) makes the SQL engine raise \"ambiguous column name\".\n // Qualify plain identifiers with the base table; leave SQL expressions\n // and `*` untouched. Single-object cubes (no joins) keep bare columns so\n // their generated SQL is byte-for-byte unchanged.\n const canJoin = !!cube?.joins && Object.keys(cube.joins).length > 0;\n if (canJoin && /^[A-Za-z_][A-Za-z0-9_]*$/.test(rawSql)) {\n return `\"${parentTable}\".\"${rawSql}\"`;\n }\n return rawSql;\n }\n // Multi-hop (ADR-0071): the dotted path IS the join chain. Every segment but\n // the last is a relationship hop; the last is the column. The join ALIAS at\n // each hop is the full path PREFIX (`account`, then `account.owner`), which\n // encodes its own parent (the prefix minus its last segment) and FK column\n // (that segment). Register one LEFT JOIN per prefix, chaining parent→child.\n const segments = rawSql.split('.');\n const column = segments[segments.length - 1];\n const hops = segments.slice(0, -1);\n if (hops.length === 0 || !column) return rawSql;\n let parentAlias = parentTable;\n let prefix = '';\n for (const seg of hops) {\n prefix = prefix ? `${prefix}.${seg}` : seg;\n const alias = this.joinAlias(prefix);\n if (!joins.has(alias)) {\n // The joined TABLE is resolved from the Cube's `joins` map (emitted by\n // the dataset compiler, keyed by the same alias); fall back to the alias\n // as the table for legacy/same-name cubes.\n const joinTable = cube?.joins?.[alias]?.name ?? alias;\n // Only emit an explicit alias when the table differs from it; when they\n // match, `LEFT JOIN \"account\" ON …` is cleaner (and back-compat).\n const tableRef = joinTable === alias ? `\"${alias}\"` : `\"${joinTable}\" \"${alias}\"`;\n joins.set(\n alias,\n `LEFT JOIN ${tableRef} ON \"${parentAlias}\".\"${seg}\" = \"${alias}\".\"id\"`,\n );\n }\n parentAlias = alias;\n }\n return `\"${parentAlias}\".\"${column}\"`;\n }\n\n /**\n * Resolve a member reference (dimension, measure, or filter field) to its\n * cube definition.\n *\n * Accepts three naming conventions:\n * 1. `<cube>.<field>` — the canonical analytics qualifier (stripped to `<field>`).\n * 2. `<lookup>.<field>` — a relation traversal (e.g. `account.industry`).\n * First tried as the literal key, then as the underscore-flattened\n * key (`account_industry`), and finally returned as a synthetic\n * definition whose `sql` is the dotted reference so the JOIN\n * machinery can pick it up.\n * 3. `<field>` — a bare field name on the cube's table.\n */\n private lookupMember(\n cube: Cube,\n member: string,\n kind: 'dimension' | 'measure',\n ): { sql: string; type?: string } | undefined {\n const bag = kind === 'dimension' ? cube.dimensions : cube.measures;\n // Direct hit on the registered key (handles `cube.field` and exact dotted keys).\n if (bag[member]) return bag[member];\n if (member.includes('.')) {\n const [first, ...rest] = member.split('.');\n const tail = rest.join('.');\n // `<cube>.<field>` style.\n if (first === cube.name && bag[tail]) return bag[tail];\n // Plain second-segment lookup (legacy behaviour).\n if (bag[tail]) return bag[tail];\n // Underscore-flattened relation lookup (e.g. `account_industry`).\n const flat = member.replace(/\\./g, '_');\n if (bag[flat]) return bag[flat];\n // Synthetic relation traversal — let qualifyAndRegisterJoin handle it.\n if (kind === 'dimension') {\n return { sql: member, type: 'string' };\n }\n } else if (bag[member]) {\n return bag[member];\n }\n return undefined;\n }\n\n private resolveDimensionSql(\n cube: Cube,\n member: string,\n parentTable: string,\n joins: Map<string, string>,\n ): string {\n const dim = this.lookupMember(cube, member, 'dimension');\n const raw = dim ? dim.sql : (member.includes('.') ? member.split('.')[1] : member);\n return this.qualifyAndRegisterJoin(raw, parentTable, joins, cube);\n }\n\n private resolveMeasureSql(\n cube: Cube,\n member: string,\n parentTable: string,\n joins: Map<string, string>,\n ): string {\n const measure = this.lookupMember(cube, member, 'measure') as\n | { sql: string; type: string }\n | undefined;\n if (!measure) return `COUNT(*)`;\n\n const col = measure.sql === '*'\n ? '*'\n : this.qualifyAndRegisterJoin(measure.sql, parentTable, joins, cube);\n switch (measure.type) {\n case 'count': return 'COUNT(*)';\n case 'sum': return `SUM(${col})`;\n case 'avg': return `AVG(${col})`;\n case 'min': return `MIN(${col})`;\n case 'max': return `MAX(${col})`;\n case 'count_distinct': return `COUNT(DISTINCT ${col})`;\n default: return `COUNT(*)`;\n }\n }\n\n private resolveFieldSql(\n cube: Cube,\n member: string,\n parentTable: string,\n joins: Map<string, string>,\n ): string {\n const dim = this.lookupMember(cube, member, 'dimension');\n if (dim) return this.qualifyAndRegisterJoin(dim.sql, parentTable, joins, cube);\n const measure = this.lookupMember(cube, member, 'measure');\n if (measure) return this.qualifyAndRegisterJoin(measure.sql, parentTable, joins, cube);\n const fieldName = member.includes('.') ? member.split('.')[1] : member;\n return fieldName;\n }\n\n /**\n * Resolve the (object, column) a filter member binds against, so its\n * comparand can be coerced to that column's on-disk storage form.\n *\n * Mirrors `resolveFieldSql`'s `sql` resolution but yields the *logical*\n * target rather than the qualified SQL:\n * - A dotted column (`account.region`, emitted for a relation traversal)\n * belongs to the JOINED object — resolve the alias → target table via the\n * cube's `joins` map (alias `account` → object `crm_account` when\n * namespaced) and take the tail as the column.\n * - Otherwise the column lives on the cube's BASE table. Use the dimension's\n * resolved `sql` (the real column, which may differ from the member name,\n * e.g. dimension `assessed` → column `assessed_at`) rather than the member.\n */\n private resolveStorageTarget(\n cube: Cube,\n member: string,\n baseTable: string,\n ): { object: string; field: string } {\n const dim = this.lookupMember(cube, member, 'dimension');\n const measure = dim ? undefined : this.lookupMember(cube, member, 'measure');\n const rawSql = dim?.sql ?? measure?.sql ?? (member.includes('.') ? member.split('.').slice(1).join('.') : member);\n\n if (rawSql.includes('.')) {\n // Multi-hop (ADR-0071): the column's owning object is the join at the\n // relationship PATH (all segments but the last); the column is the last.\n const segments = rawSql.split('.');\n const field = segments[segments.length - 1];\n const relPath = segments.slice(0, -1).join('.');\n const object = cube.joins?.[this.joinAlias(relPath)]?.name ?? relPath;\n return { object, field };\n }\n return { object: baseTable, field: rawSql };\n }\n\n /**\n * Apply the storage-form coercion for a single comparand. Prefers the\n * driver-backed `coerceTemporalFilterValue` hook (single source of truth for\n * the date/datetime storage convention — see StrategyContext); when the hook\n * is absent, or returns the value unchanged (the field is not a temporal\n * column, or the dialect stores it as a native timestamp), falls back to the\n * generic boolean/number recovery so non-temporal typed columns still bind\n * correctly.\n */\n private coerceTemporal(\n ctx: StrategyContext,\n target: { object: string; field: string },\n value: string,\n ): unknown {\n if (typeof ctx.coerceTemporalFilterValue === 'function') {\n const coerced = ctx.coerceTemporalFilterValue(target.object, target.field, value);\n // Hook returns the value untouched for non-temporal / native-timestamp\n // columns; only short-circuit when it actually changed the value.\n if (coerced !== value) return coerced;\n }\n return coerceFilterValueForSql(value);\n }\n\n private buildFilterClause(\n col: string,\n operator: string,\n values: string[] | undefined,\n params: unknown[],\n ctx: StrategyContext,\n target: { object: string; field: string },\n ): string | null {\n const opMap: Record<string, string> = {\n equals: '=', notEquals: '!=', gt: '>', gte: '>=', lt: '<', lte: '<=',\n contains: 'LIKE', notContains: 'NOT LIKE',\n };\n\n if (operator === 'set') return `${col} IS NOT NULL`;\n if (operator === 'notSet') return `${col} IS NULL`;\n\n if (operator === 'in' || operator === 'notIn') {\n if (!values || values.length === 0) return null;\n // Dates can legitimately appear in an `in`/`notIn` set (e.g. a multi-day\n // KPI), so coerce each element to the column's storage form too — same\n // SQLite epoch-vs-text root cause as the scalar operators below.\n const placeholders = values.map(v => { params.push(this.coerceTemporal(ctx, target, v)); return `$${params.length}`; }).join(', ');\n return `${col} ${operator === 'in' ? 'IN' : 'NOT IN'} (${placeholders})`;\n }\n\n const sqlOp = opMap[operator];\n if (!sqlOp || !values || values.length === 0) return null;\n\n if (operator === 'contains' || operator === 'notContains') {\n params.push(`%${values[0]}%`);\n } else {\n // Coerce so booleans/numbers bind as their native SQL types AND so a\n // relative-date / ISO-string comparand on a SQLite `Field.datetime`\n // column is converted to its INTEGER epoch storage form. Without this a\n // dashboard filter like `assessed_at >= '2025-06-18'` compiles to a\n // TEXT-vs-INTEGER affinity compare that is always false → \"No rows\",\n // even though the rows exist (the confirmed time-series chart bug).\n params.push(this.coerceTemporal(ctx, target, values[0]));\n }\n return `${col} ${sqlOp} $${params.length}`;\n }\n\n private extractObjectName(cube: Cube): string {\n return cube.sql.trim();\n }\n\n private buildFieldMeta(query: AnalyticsQuery, cube: Cube): Array<{ name: string; type: string }> {\n const fields: Array<{ name: string; type: string }> = [];\n if (query.dimensions) {\n for (const dim of query.dimensions) {\n const d = this.lookupMember(cube, dim, 'dimension');\n fields.push({ name: dim, type: d?.type || 'string' });\n }\n }\n if (query.measures) {\n for (const m of query.measures) {\n fields.push({ name: m, type: 'number' });\n }\n }\n return fields;\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Cross-object dimension re-bucketing (#3654 capability).\n *\n * `engine.aggregate()` cannot join, so the ObjectQL path cannot group directly\n * by a related object's attribute (`account.region`). Instead the strategy:\n * 1. groups the base aggregate by the LOOKUP FK column (`account`), which the\n * engine CAN do (it is a plain base column), and\n * 2. resolves each FK id to the related attribute (`region`) with a SCOPED\n * read of the referenced object, then\n * 3. re-buckets the base aggregate by that attribute here, in memory,\n * recombining the measures.\n *\n * This module is the pure, deterministic step (3): given the base rows, the\n * FK→attribute maps, and the measures' aggregation methods, produce the rows a\n * direct cross-object grouping would have. It is unit-tested in isolation\n * because a wrong re-combination silently corrupts totals — exactly the class of\n * bug #3654 exists to kill.\n *\n * A base row whose FK does not resolve (the referenced record is hidden by the\n * referenced object's own RLS) buckets under {@link RESTRICTED_BUCKET}: its\n * measure still counts, so grand totals are preserved, but the hidden record's\n * attribute value never appears (no leak — ADR-0021 D-C, the #3602 class).\n */\n\n/** Only aggregation methods that re-combine across sub-buckets are supported. */\nexport type RecombinableMethod = 'sum' | 'count' | 'min' | 'max';\n\nexport const RECOMBINABLE_METHODS: ReadonlySet<string> = new Set<RecombinableMethod>([\n 'sum',\n 'count',\n 'min',\n 'max',\n]);\n\n/** Sentinel bucket for base rows whose referenced record the caller cannot read. */\nexport const RESTRICTED_BUCKET = '(restricted)';\n\nexport interface CrossObjectDim {\n /** Output key for the resolved attribute (the original dimension name), e.g. `region`. */\n outputName: string;\n /** The base FK column the base aggregate was grouped by, e.g. `account`. */\n fkField: string;\n /** `fkValue → attributeValue`. An FK absent from the map buckets as RESTRICTED. */\n fkToAttr: Map<unknown, unknown>;\n}\n\nexport interface MeasureRecombine {\n /** The measure's output key in each base row. */\n alias: string;\n method: RecombinableMethod;\n}\n\n/**\n * Order two measure values. A number orders as itself; a `Date` or an ISO\n * timestamp orders as its instant, so a `min`/`max` over a temporal measure\n * compares correctly instead of collapsing to `NaN` (#3797). `NaN` means \"not\n * orderable\" and the caller keeps the other side.\n */\nfunction orderableValue(v: unknown): number {\n if (v == null) return NaN;\n if (typeof v === 'number') return v;\n if (v instanceof Date) return v.getTime();\n const n = Number(v);\n if (Number.isFinite(n)) return n;\n return Date.parse(String(v));\n}\n\n/**\n * Combine two measure values under an aggregation method (either may be\n * undefined).\n *\n * `sum`/`count` are numeric by construction and stay so. `min`/`max` return the\n * winning ORIGINAL value rather than a number: the value they pick is a value\n * OF the column, so a temporal measure has to come back out in the same shape\n * the driver presented it (#3797) — coercing it to a number here would put the\n * epoch leak back one layer up, and on any dialect whose driver returns an ISO\n * string it would produce `NaN` outright.\n */\nfunction recombine(method: RecombinableMethod, acc: unknown, next: unknown): unknown {\n if (method === 'min' || method === 'max') {\n if (acc === undefined) return next ?? 0;\n const a = orderableValue(acc);\n const n = orderableValue(next);\n if (Number.isNaN(n)) return acc;\n if (Number.isNaN(a)) return next;\n const nextWins = method === 'min' ? n < a : n > a;\n return nextWins ? next : acc;\n }\n const n = Number(next ?? 0);\n return acc === undefined ? n : Number(acc) + n;\n}\n\n/**\n * Re-bucket base aggregate rows by resolved cross-object attributes.\n *\n * @param baseRows rows grouped by `baseDimFields` + every `crossDims[*].fkField`.\n * @param baseDimFields the NON-cross-object group keys carried through unchanged\n * (base columns and date buckets), keyed as in `baseRows`.\n * @param crossDims one entry per cross-object dimension (its FK→attr map).\n * @param measures measure keys + their (recombinable) aggregation method.\n * @returns rows keyed by `baseDimFields` + each `crossDims[*].outputName` + measures.\n */\nexport function rebucketCrossObject(\n baseRows: Record<string, unknown>[],\n baseDimFields: string[],\n crossDims: CrossObjectDim[],\n measures: MeasureRecombine[],\n): Record<string, unknown>[] {\n const buckets = new Map<string, Record<string, unknown>>();\n\n for (const row of baseRows) {\n // Resolve each cross-object FK to its attribute (or RESTRICTED).\n const resolved: Record<string, unknown> = {};\n for (const cd of crossDims) {\n const fk = row[cd.fkField];\n resolved[cd.outputName] = cd.fkToAttr.has(fk) ? cd.fkToAttr.get(fk) : RESTRICTED_BUCKET;\n }\n\n // Bucket key = base dims (unchanged) + resolved attributes. `\u0001` is a\n // separator no group value contains, matching the engine's own convention.\n const keyParts: string[] = [];\n // JSON-encoded, so the empty bucket (`null` on both aggregation paths since\n // #3839) stays distinct from a row whose value is the literal string\n // `\"null\"` — plain interpolation renders both as `null` and would merge two\n // real groups into one. Only this composite id is affected; the emitted\n // bucket keeps the row's own value verbatim below.\n for (const f of baseDimFields) keyParts.push(`${f}=${JSON.stringify(row[f] ?? null)}`);\n for (const cd of crossDims) keyParts.push(`${cd.outputName}=${String(resolved[cd.outputName])}`);\n const key = keyParts.join('\u0001');\n\n let bucket = buckets.get(key);\n if (!bucket) {\n bucket = {};\n for (const f of baseDimFields) bucket[f] = row[f];\n for (const cd of crossDims) bucket[cd.outputName] = resolved[cd.outputName];\n buckets.set(key, bucket);\n }\n for (const m of measures) {\n bucket[m.alias] = recombine(m.method, bucket[m.alias], row[m.alias]);\n }\n }\n\n return [...buckets.values()];\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { AnalyticsQuery, AnalyticsResult } from '@objectstack/spec/contracts';\nimport type { Cube } from '@objectstack/spec/data';\nimport type { AnalyticsStrategy, StrategyContext } from './types.js';\nimport { normalizeAnalyticsFilters, coerceFilterValueForObjectQL } from './filter-normalizer.js';\nimport { compileScopedFilterToSql } from '../read-scope-sql.js';\nimport {\n rebucketCrossObject,\n RECOMBINABLE_METHODS,\n type CrossObjectDim,\n type MeasureRecombine,\n type RecombinableMethod,\n} from './cross-object-rebucket.js';\n\n/** Scalar analytics operators → their SQL spelling (display SQL only). */\nconst SCALAR_SQL_OPS: Record<string, string> = {\n equals: '=', notEquals: '!=', gt: '>', gte: '>=', lt: '<', lte: '<=',\n};\n\n/** One cross-object grouping dimension planned for FK-expand (#3654). */\ninterface CrossObjectPlanDim {\n /** The caller's dimension name (output key), e.g. `region`. */\n outputName: string;\n /** The base lookup FK column to group the base aggregate by, e.g. `account`. */\n fkField: string;\n /** The related object's attribute to resolve the FK to, e.g. `region`. */\n attr: string;\n /** The related object name (join target), e.g. `crm_account`. */\n refObject: string;\n}\n\ninterface CrossObjectPlan {\n crossDims: CrossObjectPlanDim[];\n}\n\n/**\n * ObjectQLStrategy — Priority 2\n *\n * Translates an analytics query into an ObjectQL `engine.aggregate()` call.\n * This path works with any driver that supports the ObjectQL aggregate AST\n * (Postgres, Mongo, SQLite, etc.) without requiring raw SQL access.\n */\nexport class ObjectQLStrategy implements AnalyticsStrategy {\n readonly name = 'ObjectQLStrategy';\n readonly priority = 20;\n\n canHandle(query: AnalyticsQuery, ctx: StrategyContext): boolean {\n if (!query.cube) return false;\n const caps = ctx.queryCapabilities(query.cube);\n return caps.objectqlAggregate && typeof ctx.executeAggregate === 'function';\n }\n\n async execute(query: AnalyticsQuery, ctx: StrategyContext): Promise<AnalyticsResult> {\n const cube = ctx.getCube(query.cube!)!;\n const objectName = this.extractObjectName(cube);\n\n // Build groupBy from dimensions, honouring `timeDimensions` granularity.\n // A date dimension with a granularity becomes a STRUCTURED groupBy item\n // `{ field, dateGranularity }` — which `engine.aggregate()` buckets (driver\n // date_trunc or in-memory). Without this the ObjectQL path grouped raw\n // timestamps (one bucket per row) and date-bucketed dataset widgets never\n // matched their legacy `categoryGranularity` counterpart.\n type GroupByItem = string | { field: string; dateGranularity: string };\n const granByDim = new Map<string, string>();\n for (const td of query.timeDimensions ?? []) {\n if (td.granularity) granByDim.set(td.dimension, td.granularity);\n }\n const groupBy: GroupByItem[] = [];\n if (query.dimensions && query.dimensions.length > 0) {\n for (const dim of query.dimensions) {\n const field = this.resolveFieldName(cube, dim, 'dimension');\n const gran = granByDim.get(dim);\n groupBy.push(gran ? { field, dateGranularity: gran } : field);\n granByDim.delete(dim);\n }\n }\n // Time dimensions not also listed in `dimensions` still bucket + group.\n for (const [dim, gran] of granByDim) {\n groupBy.push({ field: this.resolveFieldName(cube, dim, 'dimension'), dateGranularity: gran });\n }\n\n // Build aggregations from measures\n const aggregations: Array<{ field: string; method: string; alias: string }> = [];\n if (query.measures && query.measures.length > 0) {\n for (const measure of query.measures) {\n const { field, method } = this.resolveMeasureAggregation(cube, measure);\n aggregations.push({ field, method, alias: measure });\n }\n }\n\n // Build the engine filter. Every predicate — the caller's `where` and the\n // time-dimension windows alike — is contributed through\n // `mergeFilterOperand`, because one field routinely carries MULTIPLE\n // operators (a range `{$gte, $lte}` on `close_date`) and a plain assignment\n // would keep only the last.\n const filter: Record<string, unknown> = {};\n // Operands that cannot merge into their field's entry without one silently\n // replacing the other; ANDed in below so the engine intersects them.\n const conjuncts: Record<string, unknown>[] = [];\n for (const f of normalizeAnalyticsFilters(query)) {\n const fieldName = this.resolveFieldName(cube, f.member, 'any');\n const extra = this.mergeFilterOperand(filter, fieldName, this.convertFilter(f.operator, f.values));\n if (extra) conjuncts.push(extra);\n }\n // #3650 — and the time-dimension WINDOWS, through the SAME merge, so a\n // `dateRange` and a caller `where` bound on one field compose instead of\n // clobbering each other.\n for (const { field, bounds } of this.dateRangeBounds(cube, query)) {\n const extra = this.mergeFilterOperand(filter, field, bounds);\n if (extra) conjuncts.push(extra);\n }\n if (conjuncts.length > 0) {\n filter.$and = [...(Array.isArray(filter.$and) ? filter.$and : []), ...conjuncts];\n }\n\n // #3654 — classify cross-object references. A cross-object DIMENSION within\n // the supported envelope is served by an FK-expand (`executeCrossObject`);\n // everything the engine cannot serve (cross-object measures/filters,\n // multi-hop, non-recombinable measures) is REJECTED by `planCrossObject` —\n // the engine has no join, and a silent mis-bucket is worse than a loud\n // error. `null` ⇒ the query is base-only and takes the direct path below.\n const plan = this.planCrossObject(cube, query, filter);\n if (plan) {\n return this.executeCrossObject(cube, query, aggregations, filter, plan, ctx);\n }\n\n // ADR-0021 D-C — the base object's read scope (tenant + RLS) MUST be ANDed\n // in before the query leaves the strategy (#3597). A base-only query has a\n // single object in play, so one base-object scope is sufficient here.\n const rows = await ctx.executeAggregate!(objectName, {\n // Structured groupBy items ({field, dateGranularity}) pass through the\n // executeAggregate bridge to engine.aggregate, which buckets them. The\n // contract types groupBy as string[]; the cast carries the richer shape.\n groupBy: groupBy.length > 0 ? (groupBy as unknown as string[]) : undefined,\n aggregations: aggregations.length > 0 ? aggregations : undefined,\n filter: this.withReadScope(objectName, filter, ctx),\n // ADR-0053 Phase 2 (D2): forward the reference tz so date buckets resolve\n // on that zone's calendar days. A non-UTC zone makes the engine bucket\n // in-memory (uniform across drivers); UTC/unset keeps the DB fast path.\n timezone: query.timezone,\n // ADR-0021 D-C (#3602): the second belt. `withReadScope` above is this\n // layer's own scoping; handing the engine the context makes ITS middleware\n // inject RLS too, so a future strategy that forgets `withReadScope` still\n // cannot read across tenants. Without it the operation reaches the engine\n // principal-less and plugin-security falls open — the #3597 shape.\n context: ctx.context,\n });\n\n // Remap short field names back to cube-qualified names\n const mappedRows = rows.map(row => {\n const mapped: Record<string, unknown> = {};\n if (query.dimensions) {\n for (const dim of query.dimensions) {\n const shortName = this.resolveFieldName(cube, dim, 'dimension');\n if (shortName in row) mapped[dim] = row[shortName];\n }\n }\n if (query.measures) {\n for (const m of query.measures) {\n // Alias was set to the full measure name\n if (m in row) mapped[m] = row[m];\n }\n }\n return mapped;\n });\n\n const fields = this.buildFieldMeta(query, cube);\n // Echo a representative SQL alongside the rows (#3588). `NativeSQLStrategy`\n // returns the statement it actually ran, and dataset responses surface that\n // string — it is how an author checks what their widget compiled to. This\n // path builds an AST, so it had nothing to echo, and the `sql` field simply\n // vanished from the response whenever a query was date-bucketed (native SQL\n // declines granularity, handing those queries here). An author reading the\n // response then couldn't tell \"bucketing is not implemented\" from \"this\n // strategy doesn't report\". Best-effort: rendering is a debugging aid and\n // must never fail a query that already ran.\n let sql: string | undefined;\n try {\n sql = (await this.generateSql(query, ctx)).sql;\n } catch {\n sql = undefined;\n }\n return sql ? { rows: mappedRows, fields, sql } : { rows: mappedRows, fields };\n }\n\n /**\n * Render a REPRESENTATIVE SQL string for an ObjectQL aggregate query.\n *\n * This path executes through `engine.aggregate()`, not raw SQL, so the string\n * is documentation rather than the literal statement — but it must be an\n * honest account of what the query does, because dataset responses echo it\n * and authors read it to verify their widget options landed (#3588). It\n * therefore renders date bucketing (`date_trunc`), the WHERE predicate,\n * ordering, and the row window.\n *\n * Filter VALUES are rendered as `$n` placeholders and returned in `params`,\n * never inlined: the echoed statement travels to the browser, and a filter\n * comparand can carry tenant data.\n */\n async generateSql(query: AnalyticsQuery, ctx: StrategyContext): Promise<{ sql: string; params: unknown[] }> {\n const cube = ctx.getCube(query.cube!);\n if (!cube) {\n throw new Error(`Cube not found: ${query.cube}`);\n }\n\n const selectParts: string[] = [];\n const groupByParts: string[] = [];\n const params: unknown[] = [];\n\n // Date-bucketed dimensions render as `date_trunc('<granularity>', col)` —\n // the SQL shape the driver's own bucketing implements — so a `month` trend\n // no longer reads as if it grouped by the raw column.\n const granByDim = new Map<string, string>();\n for (const td of query.timeDimensions ?? []) {\n if (td.granularity) granByDim.set(td.dimension, td.granularity);\n }\n const tableName = this.extractObjectName(cube);\n // #3654 — plan cross-object dims (throws for out-of-envelope, so\n // `/analytics/sql` and `execute()` accept/reject the SAME set). An in-envelope\n // cross-object dim renders as a LEFT JOIN — its logical shape; `execute()`\n // serves it via FK-expand.\n const plan = this.planCrossObject(cube, query, Object.fromEntries(\n normalizeAnalyticsFilters(query).map((f) => [this.resolveFieldName(cube, f.member, 'any'), true]),\n ));\n const crossByDim = new Map((plan?.crossDims ?? []).map((cd) => [cd.outputName, cd]));\n const joinClauses: string[] = [];\n const dimExpr = (dim: string): string => {\n const cd = crossByDim.get(dim);\n if (cd) {\n joinClauses.push(\n `LEFT JOIN \"${cd.refObject}\" ON \"${tableName}\".\"${cd.fkField}\" = \"${cd.refObject}\".\"id\"`,\n );\n return `\"${cd.refObject}\".\"${cd.attr}\"`;\n }\n const col = this.resolveFieldName(cube, dim, 'dimension');\n const gran = granByDim.get(dim);\n return gran ? `date_trunc('${gran}', ${col})` : col;\n };\n\n if (query.dimensions) {\n for (const dim of query.dimensions) {\n const expr = dimExpr(dim);\n selectParts.push(`${expr} AS \"${dim}\"`);\n groupByParts.push(expr);\n }\n }\n // A time dimension that is bucketed but not also listed in `dimensions`\n // still groups (see `execute`), so it belongs in the rendered GROUP BY too.\n for (const [dim] of granByDim) {\n if (query.dimensions?.includes(dim)) continue;\n const expr = dimExpr(dim);\n selectParts.push(`${expr} AS \"${dim}\"`);\n groupByParts.push(expr);\n }\n if (query.measures) {\n for (const m of query.measures) {\n const { field, method } = this.resolveMeasureAggregation(cube, m);\n const aggSql = method === 'count'\n ? 'COUNT(*)'\n : method === 'count_distinct'\n ? `COUNT(DISTINCT ${field})`\n : `${method.toUpperCase()}(${field})`;\n selectParts.push(`${aggSql} AS \"${m}\"`);\n }\n }\n\n // ADR-0021 D-C (#3602) — render the READ SCOPE too, not just the caller's\n // own filters (#3652 added those). Without it this string still reads as an\n // unscoped table scan while the real aggregate is scoped (#3601), so anyone\n // debugging a \"why is this row missing\" gets SQL that cannot reproduce the\n // result. Nothing leaks — the string is never executed, and scope VALUES\n // stay in `params`, which `execute()`'s echo discards — but a rendering\n // that contradicts execution is worse than no rendering.\n //\n // The cross-object guard runs here for the same reason: this must not\n // render SQL for a query `execute()` would reject outright (#3654).\n //\n // Faithfulness cuts both ways: the time-dimension WINDOWS render too, from\n // the same `dateRangeBounds` lowering `execute()` sends to the engine\n // (#3650). This comment used to explain why a BETWEEN was deliberately\n // absent — because `execute()` dropped the window and rendering one would\n // have invented a predicate. Now that it applies the window, omitting it\n // here would be the lie in the other direction.\n // (The cross-object envelope was already enforced by `planCrossObject` above,\n // so `/analytics/sql` rejects the same out-of-envelope set `execute()` does.)\n\n const whereParts: string[] = [];\n for (const f of normalizeAnalyticsFilters(query)) {\n const clause = this.buildFilterClauseSql(\n this.resolveFieldName(cube, f.member, 'any'),\n f.operator,\n f.values,\n params,\n );\n if (clause) whereParts.push(clause);\n }\n // Bounds bind as `$n` placeholders like every other comparand: this string\n // travels to the browser, and a window can carry tenant-derived dates.\n for (const { field, bounds } of this.dateRangeBounds(cube, query)) {\n params.push(bounds.$gte, bounds.$lte);\n whereParts.push(`${field} BETWEEN $${params.length - 1} AND $${params.length}`);\n }\n // Read scope last, so it reads as the outermost constraint. Compiled by the\n // same fail-closed compiler `NativeSQLStrategy` uses — it throws rather than\n // drop a predicate, which is the correct posture even for a display string:\n // silently omitting the scope is exactly the misleading output being fixed.\n const scope = ctx.getReadScope?.(tableName);\n if (scope != null) {\n const { sql: scopeSql, params: scopeParams } = compileScopedFilterToSql(scope, tableName);\n if (scopeSql) {\n let i = 0;\n // `compileScopedFilterToSql` emits `?`; renumber into this builder's $N.\n const rendered = scopeSql.replace(/\\?/g, () => {\n params.push(scopeParams[i++]);\n return `$${params.length}`;\n });\n whereParts.push(`(${rendered})`);\n }\n }\n\n let sql = `SELECT ${selectParts.join(', ')} FROM \"${tableName}\"`;\n if (joinClauses.length > 0) sql += ' ' + joinClauses.join(' ');\n if (whereParts.length > 0) {\n sql += ` WHERE ${whereParts.join(' AND ')}`;\n }\n if (groupByParts.length > 0) {\n sql += ` GROUP BY ${groupByParts.join(', ')}`;\n }\n if (query.order && Object.keys(query.order).length > 0) {\n const orderClauses = Object.entries(query.order).map(([f, d]) => `\"${f}\" ${d.toUpperCase()}`);\n sql += ` ORDER BY ${orderClauses.join(', ')}`;\n }\n if (query.limit != null) sql += ` LIMIT ${query.limit}`;\n if (query.offset != null) sql += ` OFFSET ${query.offset}`;\n\n return { sql, params };\n }\n\n // ── Helpers ──────────────────────────────────────────────────────\n\n /**\n * ADR-0021 D-C (#3597) — AND the object's read scope (tenant + RLS) into the\n * filter handed to `engine.aggregate`.\n *\n * This path used to drop the scope entirely, and the engine could not make up\n * for it: the aggregate bridge passes no `ExecutionContext`, so the security\n * middleware's principal-less fall-open skipped its own RLS injection. Both\n * belts were off at once — an authenticated caller received aggregates\n * computed over EVERY tenant's rows.\n *\n * Composed with `$and`, never by key merge: the query's own filter and the\n * scope can name the SAME field (e.g. a dashboard filtering `organization_id`),\n * and a spread would let caller input silently overwrite the security\n * predicate. `$and` makes that structurally impossible.\n */\n private withReadScope(\n objectName: string,\n filter: Record<string, unknown>,\n ctx: StrategyContext,\n ): Record<string, unknown> | undefined {\n const userFilter = Object.keys(filter).length > 0 ? filter : undefined;\n if (typeof ctx.getReadScope !== 'function') return userFilter;\n const scope = ctx.getReadScope(objectName);\n if (scope === undefined || scope === null) return userFilter;\n const scopeFilter = scope as Record<string, unknown>;\n if (!userFilter) return scopeFilter;\n return { $and: [userFilter, scopeFilter] };\n }\n\n /** Is `field` a resolved cross-object (relationship-traversal) reference? */\n private isCrossObjectField(cube: Cube, field: string, baseObject: string): boolean {\n if (!field.includes('.')) return false;\n const alias = field.split('.')[0];\n const joinedObject = cube.joins?.[alias]?.name ?? alias;\n return joinedObject !== baseObject;\n }\n\n /**\n * Plan how to serve cross-object references on this join-less path (#3654).\n *\n * `engine.aggregate()` cannot join. A cross-object DIMENSION within a\n * supported envelope is served by an FK-expand (`executeCrossObject`): group\n * the base aggregate on the lookup FK, resolve the FK to the related attribute\n * with a SCOPED read, re-bucket in memory. Returns `null` for a base-only\n * query (direct path), a plan for an in-envelope cross-object query.\n *\n * THROWS for anything outside the envelope — a cross-object MEASURE or FILTER\n * (needs a real join to evaluate), a MULTI-HOP dimension (`a.b.c`), or a\n * non-recombinable measure (`avg`/`count_distinct`, whose sub-bucket values\n * cannot be merged). A loud error beats the silent mis-bucket #3654 kills.\n * `generateSql()` calls this too, so the preview accepts/rejects the same set.\n *\n * Detection is on RESOLVED field names, so a dotted dimension the cube\n * flattens to a real column is treated as base, not cross-object.\n */\n private planCrossObject(\n cube: Cube,\n query: AnalyticsQuery,\n filter: Record<string, unknown>,\n ): CrossObjectPlan | null {\n const baseObject = this.extractObjectName(cube);\n\n // A date bucket over a related object's field is not supported. Checked\n // FIRST: since #3650 a `dateRange` also lands in `filter`, so a cross-object\n // time dimension would otherwise be reported as a \"cross-object filter\" —\n // true of the lowered predicate, but not what the author wrote.\n for (const td of query.timeDimensions ?? []) {\n const field = this.resolveFieldName(cube, td.dimension, 'dimension');\n if (this.isCrossObjectField(cube, field, baseObject)) {\n throw new Error(\n `[Analytics] ObjectQLStrategy cannot bucket a cross-object time dimension (\"${field}\").`,\n );\n }\n }\n\n // A cross-object MEASURE or FILTER can only be evaluated with a real join.\n const nonDim = [\n ...(query.measures ?? []).map((m) => ({ where: 'measure', field: this.resolveMeasureAggregation(cube, m).field })),\n ...Object.keys(filter).map((f) => ({ where: 'filter', field: f })),\n ].filter((r) => this.isCrossObjectField(cube, r.field, baseObject));\n if (nonDim.length > 0) {\n throw new Error(\n `[Analytics] ObjectQLStrategy cannot evaluate a cross-object ${nonDim[0].where} ` +\n `(\"${nonDim[0].field}\") — the engine cannot join in an aggregate. Run this ` +\n `query on a native-SQL driver, or remove the cross-object ${nonDim[0].where}.`,\n );\n }\n\n // Collect cross-object DIMENSIONS (single-hop only).\n const crossDims: CrossObjectPlanDim[] = [];\n for (const dim of query.dimensions ?? []) {\n const field = this.resolveFieldName(cube, dim, 'dimension');\n if (!this.isCrossObjectField(cube, field, baseObject)) continue;\n const [alias, ...rest] = field.split('.');\n const attr = rest.join('.');\n if (attr.includes('.')) {\n throw new Error(\n `[Analytics] ObjectQLStrategy supports only single-hop cross-object ` +\n `dimensions; \"${field}\" traverses more than one relationship.`,\n );\n }\n crossDims.push({ outputName: dim, fkField: alias, attr, refObject: cube.joins?.[alias]?.name ?? alias });\n }\n\n if (crossDims.length === 0) return null;\n\n // Every measure must re-combine across the intermediate FK sub-buckets.\n for (const m of query.measures ?? []) {\n const { method } = this.resolveMeasureAggregation(cube, m);\n if (!RECOMBINABLE_METHODS.has(method)) {\n throw new Error(\n `[Analytics] ObjectQLStrategy cannot group by a cross-object dimension ` +\n `with a \"${method}\" measure (\"${m}\") — its value cannot be recombined ` +\n `across the intermediate FK grouping. Use sum/count/min/max, or run on ` +\n `a native-SQL driver.`,\n );\n }\n }\n\n return { crossDims };\n }\n\n /**\n * Serve a cross-object-dimension query by FK-expand (#3654). The pure\n * re-bucketing step lives in `cross-object-rebucket.ts`.\n */\n private async executeCrossObject(\n cube: Cube,\n query: AnalyticsQuery,\n aggregations: Array<{ field: string; method: string; alias: string }>,\n filter: Record<string, unknown>,\n plan: CrossObjectPlan,\n ctx: StrategyContext,\n ): Promise<AnalyticsResult> {\n const baseObject = this.extractObjectName(cube);\n const crossByDim = new Map(plan.crossDims.map((cd) => [cd.outputName, cd]));\n\n // Rewrite group-by: a cross-object dim becomes its base FK column; base and\n // time dims pass through. `baseDimFields` are the group keys carried into\n // the re-bucket verbatim (the FK columns are replaced by resolved attrs).\n type GroupByItem = string | { field: string; dateGranularity: string };\n const granByDim = new Map<string, string>();\n for (const td of query.timeDimensions ?? []) {\n if (td.granularity) granByDim.set(td.dimension, td.granularity);\n }\n const groupBy: GroupByItem[] = [];\n const baseDimFields: string[] = [];\n for (const dim of query.dimensions ?? []) {\n const cd = crossByDim.get(dim);\n if (cd) {\n groupBy.push(cd.fkField);\n continue;\n }\n const field = this.resolveFieldName(cube, dim, 'dimension');\n const gran = granByDim.get(dim);\n groupBy.push(gran ? { field, dateGranularity: gran } : field);\n baseDimFields.push(field);\n granByDim.delete(dim);\n }\n for (const [dim, gran] of granByDim) {\n const field = this.resolveFieldName(cube, dim, 'dimension');\n groupBy.push({ field, dateGranularity: gran });\n baseDimFields.push(field);\n }\n\n // Base aggregate, grouped by the FK, scoped to the base object. Threads the\n // ExecutionContext for the engine-side second belt too (#3602).\n const baseRows = await ctx.executeAggregate!(baseObject, {\n groupBy: groupBy.length > 0 ? (groupBy as unknown as string[]) : undefined,\n aggregations: aggregations.length > 0 ? aggregations : undefined,\n filter: this.withReadScope(baseObject, filter, ctx),\n timezone: query.timezone,\n context: ctx.context,\n });\n\n // Resolve each cross-object dim's FK → attribute, SCOPED to the referenced\n // object: a related record the caller cannot read never yields its\n // attribute, so it buckets as RESTRICTED (no leak; ADR-0021 D-C / #3602).\n const resolvedDims: CrossObjectDim[] = [];\n for (const cd of plan.crossDims) {\n const fkValues = [...new Set(baseRows.map((r) => r[cd.fkField]).filter((v) => v != null))];\n const fkToAttr = await this.resolveFkAttr(cd.refObject, cd.attr, fkValues, ctx);\n resolvedDims.push({ outputName: cd.outputName, fkField: cd.fkField, fkToAttr });\n }\n\n const measures: MeasureRecombine[] = (query.measures ?? []).map((m) => ({\n alias: m,\n // planCrossObject already asserted every measure is recombinable.\n method: this.resolveMeasureAggregation(cube, m).method as RecombinableMethod,\n }));\n\n const merged = rebucketCrossObject(baseRows, baseDimFields, resolvedDims, measures);\n\n // Map resolved group keys back to the caller's dimension names.\n const mappedRows = merged.map((row) => {\n const out: Record<string, unknown> = {};\n for (const dim of query.dimensions ?? []) {\n if (crossByDim.has(dim)) {\n if (dim in row) out[dim] = row[dim];\n } else {\n const field = this.resolveFieldName(cube, dim, 'dimension');\n if (field in row) out[dim] = row[field];\n }\n }\n for (const td of query.timeDimensions ?? []) {\n if (query.dimensions?.includes(td.dimension)) continue;\n const field = this.resolveFieldName(cube, td.dimension, 'dimension');\n if (field in row) out[td.dimension] = row[field];\n }\n for (const m of query.measures ?? []) {\n if (m in row) out[m] = row[m];\n }\n return out;\n });\n\n return { rows: mappedRows, fields: this.buildFieldMeta(query, cube) };\n }\n\n /**\n * Resolve `fkValues` (ids of `refObject`) to their `attr` values, applying the\n * referenced object's OWN read scope (#3654 / #3602). Reuses the aggregate\n * bridge — `group by (id, attr)` is one row per record. Ids the scope hides\n * are simply absent from the map (⇒ RESTRICTED bucket downstream).\n */\n private async resolveFkAttr(\n refObject: string,\n attr: string,\n fkValues: unknown[],\n ctx: StrategyContext,\n ): Promise<Map<unknown, unknown>> {\n const map = new Map<unknown, unknown>();\n if (fkValues.length === 0 || typeof ctx.executeAggregate !== 'function') return map;\n const idFilter: Record<string, unknown> = { id: { $in: fkValues } };\n const scope = typeof ctx.getReadScope === 'function' ? ctx.getReadScope(refObject) : null;\n const filter = scope != null ? { $and: [idFilter, scope] } : idFilter;\n const rows = await ctx.executeAggregate(refObject, {\n groupBy: ['id', attr],\n aggregations: [{ field: 'id', method: 'count', alias: '_c' }],\n filter,\n context: ctx.context,\n });\n for (const r of rows) {\n if (r.id != null) map.set(r.id, r[attr]);\n }\n return map;\n }\n\n /**\n * Render one normalized filter as a display SQL predicate for `generateSql`.\n *\n * Mirrors `NativeSQLStrategy.buildFilterClause`'s operator vocabulary so the\n * two previews read alike, but binds through `coerceFilterValueForObjectQL`:\n * the comparand shown is the one THIS path actually hands the engine (a real\n * boolean, not SQL's 1/0). Returns null for an operator/value combination\n * that carries no predicate, matching `execute()`, which drops it too.\n */\n private buildFilterClauseSql(\n col: string,\n operator: string,\n values: string[] | undefined,\n params: unknown[],\n ): string | null {\n if (operator === 'set') return `${col} IS NOT NULL`;\n if (operator === 'notSet') return `${col} IS NULL`;\n\n if (!values || values.length === 0) return null;\n\n if (operator === 'in' || operator === 'notIn') {\n const placeholders = values\n .map((v) => { params.push(coerceFilterValueForObjectQL(v)); return `$${params.length}`; })\n .join(', ');\n return `${col} ${operator === 'in' ? 'IN' : 'NOT IN'} (${placeholders})`;\n }\n\n if (operator === 'contains' || operator === 'notContains') {\n params.push(`%${values[0]}%`);\n return `${col} ${operator === 'contains' ? 'LIKE' : 'NOT LIKE'} $${params.length}`;\n }\n\n const op = SCALAR_SQL_OPS[operator];\n if (!op) return null;\n params.push(coerceFilterValueForObjectQL(values[0]));\n return `${col} ${op} $${params.length}`;\n }\n\n /**\n * Resolve a member ref to a `{ sql, type? }` definition.\n *\n * Mirrors `NativeSQLStrategy.lookupMember` so the two strategies\n * accept the same naming conventions:\n * 1. `<cube>.<field>` — canonical analytics qualifier.\n * 2. `<lookup>.<field>` — relation traversal (e.g. `account.industry`).\n * Tries literal key, then underscore-flattened key, then falls\n * back to a synthetic dim whose `sql` is the dotted path so the\n * ObjectQL aggregate engine can traverse it via the lookup field.\n * 3. `<field>` — bare column on the cube's table.\n */\n private lookupMember(\n cube: Cube,\n member: string,\n kind: 'dimension' | 'measure',\n ): { sql: string; type?: string } | undefined {\n const bag = kind === 'dimension' ? cube.dimensions : cube.measures;\n if (bag[member]) return bag[member];\n if (member.includes('.')) {\n const [first, ...rest] = member.split('.');\n const tail = rest.join('.');\n if (first === cube.name && bag[tail]) return bag[tail];\n if (bag[tail]) return bag[tail];\n const flat = member.replace(/\\./g, '_');\n if (bag[flat]) return bag[flat];\n if (kind === 'dimension') return { sql: member, type: 'string' };\n } else if (bag[member]) {\n return bag[member];\n }\n return undefined;\n }\n\n private resolveFieldName(cube: Cube, member: string, kind: 'dimension' | 'measure' | 'any'): string {\n if (kind === 'dimension' || kind === 'any') {\n const dim = this.lookupMember(cube, member, 'dimension');\n if (dim) return dim.sql.replace(/^\\$/, '');\n }\n if (kind === 'measure' || kind === 'any') {\n const measure = this.lookupMember(cube, member, 'measure');\n if (measure) return measure.sql.replace(/^\\$/, '');\n }\n return member.includes('.') ? member.split('.')[1] : member;\n }\n\n private resolveMeasureAggregation(cube: Cube, measureName: string): { field: string; method: string } {\n const direct = this.lookupMember(cube, measureName, 'measure') as\n | { sql: string; type: string }\n | undefined;\n if (direct) {\n return {\n field: direct.sql.replace(/^\\$/, ''),\n method: direct.type === 'count_distinct' ? 'count_distinct' : direct.type,\n };\n }\n // Accept `${field}_${type}` aliases (e.g. 'amount_sum') for measures whose\n // canonical name is just `${field}` (e.g. measure 'amount' of type 'sum').\n // This matches the convention used by clients that build measure names\n // from (field, function) pairs (e.g. the data-objectstack adapter).\n const fieldName = measureName.includes('.') ? measureName.split('.')[1] : measureName;\n const aggTypes = ['count', 'sum', 'avg', 'min', 'max', 'count_distinct'];\n for (const type of aggTypes) {\n const suffix = `_${type}`;\n if (fieldName.endsWith(suffix)) {\n const baseField = fieldName.slice(0, -suffix.length);\n const candidate = cube.measures[baseField];\n if (candidate && candidate.type === type) {\n return {\n field: candidate.sql.replace(/^\\$/, ''),\n method: candidate.type === 'count_distinct' ? 'count_distinct' : candidate.type,\n };\n }\n }\n }\n return { field: '*', method: 'count' };\n }\n\n /**\n * AND one more operand onto `filter[field]`, merging operator objects rather\n * than overwriting them. Returns a standalone conjunct when the two cannot\n * share one entry, or `null` when the merge absorbed the operand.\n *\n * Every predicate this strategy contributes goes through here — the caller's\n * `where` and the time-dimension `dateRange` alike. Two operands on one field\n * are the normal case (`{$gte}` from a `where` plus `{$gte,$lte}` from a\n * window on `close_date`), and a plain assignment would keep only the last:\n * that is how a range used to lose a bound.\n *\n * Spreading is sound only while the operands name DIFFERENT operators. Where\n * they collide — two `$gte` bounds on one field, which a window makes routine\n * and which a `where` can already produce on its own through `$and` — the\n * spread keeps whichever came last and WIDENS the query. Same for a bare\n * equality meeting an operator object: neither can absorb the other. Those\n * are handed back for the caller to AND in separately, so the engine\n * intersects them instead of the strategy picking a winner.\n */\n private mergeFilterOperand(\n filter: Record<string, unknown>,\n field: string,\n operand: unknown,\n ): Record<string, unknown> | null {\n const existing = filter[field];\n if (existing === undefined) {\n filter[field] = operand;\n return null;\n }\n const mergeable = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === 'object' && !Array.isArray(v);\n if (!mergeable(existing) || !mergeable(operand)) return { [field]: operand };\n if (Object.keys(operand).some((op) => op in existing)) return { [field]: operand };\n filter[field] = { ...existing, ...operand };\n return null;\n }\n\n /**\n * Lower `timeDimensions[].dateRange` into resolved-field bounds (#3650).\n *\n * `dateRange` states a WINDOW on a time dimension; it is a SIBLING of `where`,\n * never folded into it. `normalizeAnalyticsFilters` reads only `where`, so\n * this path used to drop the window on the floor — no error, just every row\n * ever recorded. Nor is that a corner case: `NativeSQLStrategy.canHandle`\n * declines any query carrying a `granularity`, so a date-bucketed trend lands\n * HERE on every driver — and \"bucketed trend\" is precisely the shape that also\n * carries a range (\"last 12 months\", \"this quarter\").\n *\n * Bounds are inclusive on both ends — the same `$gte`/`$lte` pair\n * `NativeSQLStrategy` binds as `BETWEEN` and the memory driver builds as a\n * `$match`, so one dashboard reads the same on every driver.\n *\n * Comparands are coerced by the SAME helper the `where` path uses, so an\n * epoch-ms bound recovers as a number and an ISO string stays a string. No\n * STORAGE coercion happens here, deliberately: `NativeSQLStrategy` needs\n * `coerceTemporal` because it binds into raw SQL and had to learn that a\n * SQLite `Field.datetime` is an INTEGER epoch (#2034); this path goes through\n * `engine.aggregate()`, where the driver's own CRUD filter coercion applies —\n * the very coercion that already makes a `where` bound on that same column\n * work today.\n *\n * A bare-string `dateRange` degenerates to the single point `[s, s]`, matching\n * `NativeSQLStrategy`. Relative phrases (\"Last 7 days\") are NOT resolved here;\n * neither SQL path resolves them, and inventing a second interpretation on the\n * driver-independent path is how the two would drift apart again.\n *\n * An oddly-sized array (the schema types `dateRange` as a plain `string[]`)\n * takes its first two entries, a one-entry array degenerating to a point.\n * `NativeSQLStrategy` drops such a window entirely — but \"drop the window\"\n * means \"plot all of history\", which is the very failure this fixes, so the\n * fallback here errs toward the narrower query instead.\n */\n private dateRangeBounds(\n cube: Cube,\n query: AnalyticsQuery,\n ): Array<{ field: string; bounds: Record<string, unknown> }> {\n const out: Array<{ field: string; bounds: Record<string, unknown> }> = [];\n for (const td of query.timeDimensions ?? []) {\n if (!td.dateRange) continue;\n const range = Array.isArray(td.dateRange) ? td.dateRange : [td.dateRange, td.dateRange];\n const [start, end = start] = range;\n if (start == null) continue;\n out.push({\n field: this.resolveFieldName(cube, td.dimension, 'dimension'),\n bounds: {\n $gte: coerceFilterValueForObjectQL(String(start)),\n $lte: coerceFilterValueForObjectQL(String(end)),\n },\n });\n }\n return out;\n }\n\n private convertFilter(operator: string, values?: string[]): unknown {\n if (operator === 'set') return { $ne: null };\n if (operator === 'notSet') return null;\n if (!values || values.length === 0) return undefined;\n\n const v0 = coerceFilterValueForObjectQL(values[0]);\n const all = values.map(coerceFilterValueForObjectQL);\n switch (operator) {\n case 'equals': return v0;\n case 'notEquals': return { $ne: v0 };\n case 'gt': return { $gt: v0 };\n case 'gte': return { $gte: v0 };\n case 'lt': return { $lt: v0 };\n case 'lte': return { $lte: v0 };\n case 'contains': return { $regex: values[0] };\n case 'in': return { $in: all };\n case 'notIn': return { $nin: all };\n default: return v0;\n }\n }\n\n private extractObjectName(cube: Cube): string {\n return cube.sql.trim();\n }\n\n private buildFieldMeta(query: AnalyticsQuery, cube: Cube): Array<{ name: string; type: string }> {\n const fields: Array<{ name: string; type: string }> = [];\n if (query.dimensions) {\n for (const dim of query.dimensions) {\n const d = this.lookupMember(cube, dim, 'dimension');\n fields.push({ name: dim, type: d?.type || 'string' });\n }\n }\n if (query.measures) {\n for (const m of query.measures) {\n fields.push({ name: m, type: 'number' });\n }\n }\n return fields;\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Cube, Metric, Dimension as CubeDimension, CubeJoin } from '@objectstack/spec/data';\nimport type { Dataset, DatasetMeasure, DatasetDimension } from '@objectstack/spec/ui';\nimport type { FilterCondition } from '@objectstack/spec/data';\n\n/**\n * Dataset → Cube compiler (ADR-0021 D-A=(c), WS2).\n *\n * Lowers a declarative `dataset` (base object + included relationships +\n * declared dimensions/measures + derived measures) into the existing Cube\n * analytics runtime model. The author never writes an `ON` clause: joins are\n * DERIVED from the `include` relationship names and the dotted `relationship.field`\n * references on dimensions/measures, matching the NativeSQLStrategy convention\n * `<parentTable>.<relationship> = <relationship>.id`.\n *\n * Safety (D-C): every dotted field reference must point at a relationship that\n * the dataset explicitly declared in `include`; otherwise the compile fails.\n * The returned `allowedRelationships` set is the join allowlist the strategy\n * enforces at SQL-build time.\n */\n\n/** Operators v1 does NOT compile to the Cube SQL switch — surfaced as a clear error. */\nconst UNSUPPORTED_AGGREGATES = new Set(['array_agg', 'string_agg']);\n\nexport interface DerivedMeasureSpec {\n name: string;\n op: 'ratio' | 'sum' | 'difference' | 'product';\n of: string[];\n}\n\nexport interface CompiledDataset {\n /** The Cube the dataset compiles to (consumed by the strategy chain). */\n cube: Cube;\n /**\n * Every join alias the dataset may use — each declared `include` path AND its\n * intermediate prefixes (ADR-0071). The join allowlist (D-C): the\n * NativeSQLStrategy rejects any join alias not in this set.\n */\n allowedRelationships: Set<string>;\n /** Derived measures, computed post-aggregation by the executor (Q1). */\n derived: DerivedMeasureSpec[];\n /** Definition-level filter (the dataset's intrinsic scope). */\n filter?: FilterCondition;\n /** Per-measure scoped filters, keyed by measure name (applied by executor). */\n measureFilters: Record<string, FilterCondition>;\n}\n\n/**\n * The related object reached by traversing a relationship: its logical object\n * name (used to resolve the NEXT hop in a multi-hop chain — ADR-0071) and its\n * physical table name (the join target).\n */\nexport interface RelationshipTarget {\n object: string;\n table: string;\n}\n\n/**\n * Resolves a relationship name on a base object to the related object/table,\n * using the runtime's object graph. Optional: when omitted the compiler trusts\n * the declared `include` names (the NativeSQLStrategy convention assumes the\n * relationship name equals the related table name).\n *\n * May return a bare table-name `string` (legacy single-hop: object name is\n * assumed equal to the table) or a {@link RelationshipTarget} (required to\n * traverse further along a multi-hop path, where object differs from table for\n * namespaced objects).\n */\nexport type RelationshipResolver = (\n baseObject: string,\n relationshipName: string,\n) => string | RelationshipTarget | undefined;\n\n/** Map a dataset measure's aggregate to the Cube metric `type`. */\nfunction aggregateToMetricType(m: DatasetMeasure): Metric['type'] {\n // Only reached for non-derived measures, where the spec refinement guarantees\n // an aggregate; guard defensively so the type narrows from `optional`.\n if (!m.aggregate) {\n throw new Error(`[dataset-compiler] non-derived measure \"${m.name}\" has no aggregate`);\n }\n if (UNSUPPORTED_AGGREGATES.has(m.aggregate)) {\n throw new Error(\n `[dataset-compiler] measure \"${m.name}\" uses aggregate \"${m.aggregate}\" which is ` +\n `not supported by the v1 dataset runtime (supported: count, sum, avg, min, max, count_distinct).`,\n );\n }\n return m.aggregate as Metric['type'];\n}\n\n/** Map a dataset dimension type to the Cube dimension `type`. */\nfunction dimensionType(d: DatasetDimension): CubeDimension['type'] {\n switch (d.type) {\n case 'date': return 'time';\n case 'number': return 'number';\n case 'boolean': return 'boolean';\n case 'lookup': return 'string';\n case 'string': return 'string';\n default: return 'string';\n }\n}\n\n/** The relationship PATH a dotted field traverses — all segments but the final\n * column — or null for a base-object field. E.g. `account.owner.region` →\n * `account.owner`; `account.region` → `account`; `region` → null. */\nfunction fieldRelationshipPath(field: string): string | null {\n const idx = field.lastIndexOf('.');\n return idx > 0 ? field.slice(0, idx) : null;\n}\n\n/** Max relationship hops in one `include` path — base → 3 hops = 4 objects\n * (ADR-0071; Salesforce-report-type parity). To-one chains never fan out, so\n * this is a performance/complexity guard, not a correctness limit. */\nconst MAX_JOIN_HOPS = 3;\n\n/** SQL-safe join alias for a relationship PATH. The dotted path is the author-\n * facing form; the alias replaces dots with `__` (Cube.js convention) so each\n * prefix is one valid identifier — quoted dotted identifiers are rejected by\n * the read-scope SQL guard (fail-closed). Single-segment paths are unchanged,\n * so single-hop joins stay byte-for-byte identical. */\nconst joinAlias = (path: string): string => path.replace(/\\./g, '__');\n\nexport function compileDataset(\n dataset: Dataset,\n resolver?: RelationshipResolver,\n): CompiledDataset {\n const include = dataset.include ?? [];\n\n // Resolve each declared relationship PATH into its ordered join chain, emitting\n // one Cube join per PATH PREFIX (ADR-0071 multi-hop, to-one only). The join\n // ALIAS is the full dotted path (`account.owner`), which self-describes the\n // chain: the parent alias is the path minus its last segment, the FK column is\n // that last segment. So declaring `account.owner` auto-adds the intermediate\n // `account` join, and the strategy can rebuild every `ON` from the alias alone.\n // Without a resolver, each segment's relationship name is assumed to equal both\n // the related object and its table (legacy convention / unit tests).\n const resolveHop = (fromObject: string, rel: string): RelationshipTarget => {\n if (!resolver) return { object: rel, table: rel };\n const resolved = resolver(fromObject, rel);\n if (!resolved) {\n throw new Error(\n `[dataset-compiler] dataset \"${dataset.name}\" includes relationship \"${rel}\" ` +\n `which does not exist on object \"${fromObject}\".`,\n );\n }\n return typeof resolved === 'string' ? { object: resolved, table: resolved } : resolved;\n };\n const joins: Record<string, CubeJoin> = {};\n for (const path of include) {\n const segments = path.split('.');\n if (segments.length > MAX_JOIN_HOPS) {\n throw new Error(\n `[dataset-compiler] dataset \"${dataset.name}\" include path \"${path}\" exceeds the ` +\n `${MAX_JOIN_HOPS}-hop limit (${segments.length} hops). Deeper traversal is not supported.`,\n );\n }\n let fromObject = dataset.object;\n let parentAlias = dataset.object;\n let prefix = '';\n for (const seg of segments) {\n prefix = prefix ? `${prefix}.${seg}` : seg;\n const target = resolveHop(fromObject, seg);\n const alias = joinAlias(prefix);\n if (!joins[alias]) {\n // KEY is the SQL-safe alias; `name` carries the join TABLE; the strategy\n // rebuilds the ON clause from the alias convention (`<parent>.<seg> = <alias>.id`).\n joins[alias] = {\n name: target.table,\n relationship: 'many_to_one',\n sql: `${parentAlias}.${seg} = ${prefix}.id`,\n };\n }\n fromObject = target.object;\n parentAlias = prefix;\n }\n }\n\n // The join allowlist (D-C) is every registered alias — each declared path AND\n // its intermediate prefixes — so a multi-hop field's intermediate joins pass.\n const allowedRelationships = new Set(Object.keys(joins));\n\n // Assert any dotted field only traverses a DECLARED relationship PATH (D-C).\n const assertDeclared = (field: string, ownerKind: string, ownerName: string) => {\n const relPath = fieldRelationshipPath(field);\n if (relPath && !joins[joinAlias(relPath)]) {\n throw new Error(\n `[dataset-compiler] ${ownerKind} \"${ownerName}\" references relationship path \"${relPath}\" ` +\n `via \"${field}\", but \"${relPath}\" is not declared in the dataset's \\`include\\`. ` +\n `Only fields along a declared relationship path are joinable.`,\n );\n }\n };\n\n // Compile dimensions.\n const dimensions: Record<string, CubeDimension> = {};\n for (const d of dataset.dimensions) {\n assertDeclared(d.field, 'dimension', d.name);\n const dim: CubeDimension = {\n name: d.name,\n label: typeof d.label === 'string' ? d.label : d.name,\n type: dimensionType(d),\n sql: d.field,\n };\n if (dim.type === 'time') {\n dim.granularities = d.dateGranularity\n ? [d.dateGranularity]\n : ['day', 'week', 'month', 'quarter', 'year'];\n }\n dimensions[d.name] = dim;\n }\n\n // Compile measures (non-derived → Cube metrics; derived → sidecar).\n const measures: Record<string, Metric> = {};\n const derived: DerivedMeasureSpec[] = [];\n const measureFilters: Record<string, FilterCondition> = {};\n\n for (const m of dataset.measures) {\n if (m.derived) {\n derived.push({ name: m.name, op: m.derived.op, of: m.derived.of });\n continue;\n }\n if (m.field) assertDeclared(m.field, 'measure', m.name);\n const metric: Metric = {\n name: m.name,\n label: typeof m.label === 'string' ? m.label : m.name,\n type: aggregateToMetricType(m),\n // `count` with no field aggregates over rows (*).\n sql: m.field ?? '*',\n };\n if (typeof m.format === 'string') metric.format = m.format;\n measures[m.name] = metric;\n if (m.filter) measureFilters[m.name] = m.filter;\n }\n\n const cube: Cube = {\n name: dataset.name,\n title: typeof dataset.label === 'string' ? dataset.label : dataset.name,\n sql: dataset.object,\n measures,\n dimensions,\n public: false,\n };\n if (Object.keys(joins).length > 0) cube.joins = joins;\n\n return {\n cube,\n allowedRelationships,\n derived,\n filter: dataset.filter,\n measureFilters,\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IAnalyticsService,\n AnalyticsQuery,\n AnalyticsResult,\n DatasetSelection,\n DatasetCompareTo,\n} from '@objectstack/spec/contracts';\nimport type { FilterCondition } from '@objectstack/spec/data';\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\nimport { filterTokenContextFrom, resolveFilterTokens } from '@objectstack/core';\nimport type { CompiledDataset, DerivedMeasureSpec } from './dataset-compiler.js';\nimport type { OrderLabelResolver } from './dimension-labels.js';\n\n// Re-export the shared protocol shapes so existing importers keep working.\nexport type { DatasetSelection } from '@objectstack/spec/contracts';\n/** @deprecated use DatasetCompareTo from @objectstack/spec/contracts */\nexport type CompareTo = DatasetCompareTo;\n\n/**\n * Dataset executor (ADR-0021 WS2).\n *\n * Turns a compiled dataset + a presentation's selection (dimensions, measures,\n * runtime filter, compareTo) into one or more `AnalyticsQuery`s against the Cube\n * runtime, then post-processes the results:\n * - resolves the base measures a selection needs (including derived deps),\n * - applies measure-scoped filters via supplementary grouped queries,\n * - evaluates derived measures (ratio/sum/difference/product) row-by-row (Q1),\n * - shifts the query for `compareTo` (previousPeriod / previousYear) and\n * attaches `<measure>__compare` columns,\n * - computes server-side totals (`selection.totals.groupings`, #1753) by\n * re-running the selection per dimension subset, so matrix subtotals and\n * the grand total use each measure's true aggregate,\n * - orders and windows the final grid (`order` / `limit` / `offset`, #3588).\n *\n * **Where ordering happens, and why here.** `order`/`limit`/`offset` are applied\n * to the ASSEMBLED grid — after measure-scoped sub-queries are merged in, after\n * `compareTo` columns are attached, and after derived measures are computed —\n * never by forwarding them blindly to every sub-query. Two reasons:\n *\n * 1. **Correctness.** A supplementary measure-scoped query selects ONE measure;\n * forwarding `ORDER BY <other_measure>` to it emits SQL referencing a column\n * that query never selects, and forwarding `LIMIT` truncates it before the\n * merge, so rows silently vanish from the grid. A derived measure has no SQL\n * column at all, yet is a perfectly reasonable sort key.\n * 2. **Coverage.** Only `NativeSQLStrategy` honours `order`/`limit`; the\n * ObjectQL aggregate path has nowhere to put them (`EngineAggregateOptions`\n * has no ordering grammar), and date-bucketed queries are *forced* down that\n * path because native SQL declines granularity. Sorting here makes ordering\n * work identically on every driver and strategy.\n *\n * The single-query case still pushes `order`/`limit`/`offset` DOWN into the SQL\n * (see `canPushDownWindow`) so the database does the work and the echoed `sql`\n * shows it; the post-pass is then a no-op re-sort of already-sorted rows.\n *\n * **What the sort key IS for a label-bearing dimension (#3680).** An order key\n * naming a `select` or `lookup`/`master_detail` dimension sorts by the DISPLAY\n * label the response will carry (option label / related record name), not the\n * stored value — a \"sort by Account\" ordered by opaque FK ids presents as\n * arbitrary once the labels render. The mapping comes through an injected\n * {@link OrderLabelResolver} (built by `queryDataset` over the same\n * label-resolution capabilities the display pass uses); rows keep their raw\n * values — only the COMPARISON substitutes the label — so drill metadata still\n * snapshots stored values downstream. Such keys are never pushed into SQL (the\n * label is not a column there), and the label fetch happens BEFORE `applyWindow`\n * so a \"top 10 by account name\" truncates the right ten.\n *\n * RLS/tenant scoping is NOT handled here — it is enforced inside the strategy\n * via the StrategyContext read-scope hook (D-C). This layer is pure query\n * shaping + arithmetic; the order-label hook is an injected interface, not an\n * engine dependency.\n */\n\n/**\n * Expand `{filter-placeholder}` values across everything a dataset query\n * compares on (framework#3582): the dataset's intrinsic `filter`, the\n * presentation's `runtimeFilter` (a dashboard widget's own scope), every\n * measure-scoped filter, and the `dateRange` bounds of the selection's time\n * dimensions.\n *\n * The dashboard path needs its own call rather than inheriting the ObjectQL\n * engine's: `NativeSQLStrategy` compiles a raw `SELECT … WHERE` and binds\n * comparands directly, so a widget filtered on `{current_year_start}` never\n * passes through `engine.find()` at all — which is exactly why the token\n * reached SQLite as the literal text and every such widget rendered zero.\n *\n * Inputs are treated as immutable: a `CompiledDataset` lives in the service's\n * registry across requests, so resolving in place would bake one request's\n * user id (and one day's dates) into every later render. New objects are\n * allocated only when the tree actually held a placeholder.\n */\nfunction resolveSelectionTokens(\n compiled: CompiledDataset,\n selection: DatasetSelection,\n context?: ExecutionContext,\n): { compiled: CompiledDataset; selection: DatasetSelection } {\n // One instant for the whole call: the intrinsic filter, the runtime filter\n // and each measure filter are resolved in separate passes, and a query whose\n // pieces disagreed about \"now\" could straddle a period boundary — the primary\n // grid scoped to this month while a measure-scoped sub-query saw the next.\n const tokenCtx = filterTokenContextFrom(context, new Date());\n const resolve = <T>(v: T): T => resolveFilterTokens(v, tokenCtx);\n\n const filter = resolve(compiled.filter);\n const measureFilters = resolve(compiled.measureFilters);\n const runtimeFilter = resolve(selection.runtimeFilter);\n const timeDimensions = selection.timeDimensions?.map((td) =>\n td.dateRange == null ? td : { ...td, dateRange: resolve(td.dateRange) },\n );\n\n const compiledChanged =\n filter !== compiled.filter || measureFilters !== compiled.measureFilters;\n const selectionChanged =\n runtimeFilter !== selection.runtimeFilter ||\n (timeDimensions !== undefined &&\n timeDimensions.some((td, i) => td !== selection.timeDimensions![i]));\n\n return {\n compiled: compiledChanged ? { ...compiled, filter, measureFilters } : compiled,\n selection: selectionChanged ? { ...selection, runtimeFilter, timeDimensions } : selection,\n };\n}\n\n/** AND two optional FilterConditions into one (MongoDB-style). */\nexport function combineFilters(\n a?: FilterCondition,\n b?: FilterCondition,\n): FilterCondition | undefined {\n if (a && b) return { $and: [a, b] } as FilterCondition;\n return a ?? b;\n}\n\n/**\n * Evaluate derived measures on each aggregated row, mutating a shallow copy.\n * Division by zero (and missing operands) yields `null` rather than Infinity/NaN.\n */\nexport function evaluateDerivedMeasures(\n rows: Record<string, unknown>[],\n derived: DerivedMeasureSpec[],\n): Record<string, unknown>[] {\n if (derived.length === 0) return rows;\n return rows.map((row) => {\n const out = { ...row };\n for (const d of derived) {\n out[d.name] = computeDerived(d, out);\n }\n return out;\n });\n}\n\nfunction num(v: unknown): number | null {\n if (v == null) return null;\n const n = typeof v === 'number' ? v : Number(v);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction computeDerived(d: DerivedMeasureSpec, row: Record<string, unknown>): number | null {\n const vals = d.of.map((name) => num(row[name]));\n if (vals.some((v) => v === null)) return null;\n const nums = vals as number[];\n switch (d.op) {\n case 'ratio': {\n if (nums.length < 2 || nums[1] === 0) return null;\n return nums[0] / nums[1];\n }\n case 'difference':\n return nums.slice(1).reduce((acc, v) => acc - v, nums[0]);\n case 'sum':\n return nums.reduce((acc, v) => acc + v, 0);\n case 'product':\n return nums.reduce((acc, v) => acc * v, 1);\n default:\n return null;\n }\n}\n\n// ── date bucketing (#3588) ───────────────────────────────────────────────────\n\n/** The date-bucket vocabulary shared by the dataset, the selection, and the\n * bucketing utilities in `@objectstack/core`. */\nexport type DateGranularityValue = NonNullable<DatasetSelection['dateGranularity']>;\n\n/**\n * The EFFECTIVE bucket size for one date dimension of a selection — the single\n * source of truth for granularity precedence.\n *\n * Precedence, per dimension:\n * 1. a `granularity` already stated on that dimension's `timeDimensions`\n * entry — never overridden;\n * 2. `selection.dateGranularity` — the presentation's choice, so a widget can\n * bucket by month without the dataset committing every consumer to it;\n * 3. `datasetDefault` — the dataset dimension's own `dateGranularity`.\n *\n * The unit of precedence is the GRANULARITY, not the entry: a `timeDimensions`\n * entry carrying only a `dateRange` (what `compareTo` needs) states a WINDOW,\n * not a bucket size, and must not suppress bucketing.\n *\n * **Why this is exported.** The bucket size chosen here decides three things\n * that MUST agree: the `GROUP BY` the query compiles to, the humanized label\n * each bucket key is rendered as, and the half-open `[gte, lt)` range a bucket\n * drills into. When the query layer resolved granularity and the post-processing\n * in `analytics-service` read the dataset default instead, they silently\n * disagreed for every selection that overrode it — a `year` query came back\n * labelled `1970-01` (a year bucket re-formatted as a month), a `day` query\n * collapsed to duplicate month labels, and `quarter`/`year` lost their drill\n * ranges entirely. One function, called from all three sites, is what stops\n * that drift recurring.\n */\nexport function resolveDimensionGranularity(\n selection: Pick<DatasetSelection, 'timeDimensions' | 'dateGranularity'>,\n dimension: string,\n datasetDefault?: string,\n): DateGranularityValue | undefined {\n // `timeDimensions[].granularity` and the compiled cube's `granularities` are\n // both typed as bare strings by their own layers (Cube.js heritage), but the\n // only values that reach here come from the dataset/selection granularity\n // vocabulary — the same five the bucketing utilities accept.\n const stated = (selection.timeDimensions ?? []).find((t) => t.dimension === dimension)?.granularity;\n if (stated) return stated as DateGranularityValue;\n return selection.dateGranularity ?? (datasetDefault as DateGranularityValue | undefined);\n}\n\n// ── ordering + windowing (#3588) ─────────────────────────────────────────────\n\n/**\n * Compare two grouped-cell values for ORDER BY, ascending.\n *\n * Nulls sort LAST regardless of direction (the SQL `NULLS LAST` convention, and\n * the one users expect: an empty bucket shouldn't win a \"top 10 by revenue\").\n * The caller negates the result for `desc`, so the null branch deliberately\n * returns its verdict BEFORE that negation can flip it — see `compareRows`.\n *\n * Numbers (and numeric strings, which is how some drivers return SUM results)\n * compare numerically so 9 sorts below 10; everything else compares as a string.\n * Dates arrive here already bucketed to sort-stable keys (\"2026-04\", \"2026-Q2\"),\n * so lexicographic ordering is chronological for them too.\n */\nfunction compareValues(a: unknown, b: unknown): number {\n const aNull = a == null || a === '';\n const bNull = b == null || b === '';\n if (aNull || bNull) return aNull && bNull ? 0 : aNull ? 1 : -1;\n if (a instanceof Date || b instanceof Date) {\n return Number(a instanceof Date ? a.getTime() : a) - Number(b instanceof Date ? b.getTime() : b);\n }\n if (typeof a === 'boolean' || typeof b === 'boolean') {\n return Number(a) - Number(b);\n }\n const an = typeof a === 'number' ? a : Number(a);\n const bn = typeof b === 'number' ? b : Number(b);\n if (Number.isFinite(an) && Number.isFinite(bn)) return an - bn;\n return String(a).localeCompare(String(b));\n}\n\n/**\n * Order rows by each key in `order`, in the object's own key order (first key is\n * the primary sort). Returns a NEW array; the input is not mutated. Null/empty\n * cells stay last in both directions (see {@link compareValues}).\n *\n * `sortKeys` substitutes the COMPARED value per key (#3680): when it holds a map\n * for an order key, each cell compares by its mapped value — the display label a\n * label-bearing dimension will render as — falling back to the raw cell where\n * unmapped (an orphaned id or RLS-hidden record renders raw too, so sort and\n * display stay consistent). The rows themselves are never rewritten here.\n */\nexport function applyOrdering(\n rows: Record<string, unknown>[],\n order: Record<string, 'asc' | 'desc'> | undefined,\n sortKeys?: Record<string, Map<unknown, unknown>>,\n): Record<string, unknown>[] {\n const keys = Object.entries(order ?? {});\n if (keys.length === 0 || rows.length < 2) return rows;\n // Array.prototype.sort is stable (ES2019+), so equal rows keep the order the\n // grouping produced — an important property for reproducible LIMITs.\n return [...rows].sort((ra, rb) => {\n for (const [key, dir] of keys) {\n const map = sortKeys?.[key];\n const av = map?.get(ra[key]) ?? ra[key];\n const bv = map?.get(rb[key]) ?? rb[key];\n const aNull = av == null || av === '';\n const bNull = bv == null || bv === '';\n // Nulls last in BOTH directions — decided before `desc` negation.\n if (aNull || bNull) {\n if (aNull && bNull) continue;\n return aNull ? 1 : -1;\n }\n const c = compareValues(av, bv);\n if (c !== 0) return dir === 'desc' ? -c : c;\n }\n return 0;\n });\n}\n\n/** Apply `offset`/`limit` to an already-ordered grid. */\nexport function applyWindow(\n rows: Record<string, unknown>[],\n limit?: number,\n offset?: number,\n): Record<string, unknown>[] {\n const start = offset != null && offset > 0 ? offset : 0;\n if (start === 0 && limit == null) return rows;\n return rows.slice(start, limit != null ? start + limit : undefined);\n}\n\n/**\n * Validate `order` keys and resolve the EFFECTIVE ordering for a selection.\n *\n * A key must name something the caller actually selected — a dimension, a\n * measure, or a `<measure>__compare` column. An unknown key throws rather than\n * being dropped: silently ignoring `sortBy` is precisely the failure mode this\n * change exists to remove (#3588), and a mistyped sort key that quietly returns\n * arbitrarily-ordered rows is worse than a loud 400.\n *\n * When `limit`/`offset` is requested WITHOUT an order, the selected dimensions\n * ascending become the implicit ordering, so the truncated window is\n * reproducible instead of \"whatever the group-by happened to emit\".\n */\nexport function resolveOrdering(\n selection: DatasetSelection,\n dimensions: string[],\n): Record<string, 'asc' | 'desc'> | undefined {\n const order = selection.order;\n if (order && Object.keys(order).length > 0) {\n const selectable = new Set<string>([\n ...dimensions,\n ...selection.measures,\n ...selection.measures.map((m) => `${m}__compare`),\n ]);\n const unknown = Object.keys(order).filter((k) => !selectable.has(k));\n if (unknown.length) {\n throw new Error(\n `[dataset-executor] order key(s) ${unknown.map((k) => `\"${k}\"`).join(', ')} — ` +\n `not a selected dimension or measure. Selectable here: ` +\n `${[...selectable].join(', ') || '(none)'}.`,\n );\n }\n return order;\n }\n // Implicit, deterministic ordering so a bare `limit` is reproducible.\n if ((selection.limit != null || selection.offset != null) && dimensions.length > 0) {\n return Object.fromEntries(dimensions.map((d) => [d, 'asc' as const]));\n }\n return undefined;\n}\n\n// ── compareTo date math (deterministic — no Date.now) ────────────────────────\n\nfunction parseUTC(date: string): number {\n // Accepts 'YYYY-MM-DD' (and ISO datetimes); interpreted as UTC.\n const ms = Date.parse(date.length === 10 ? `${date}T00:00:00Z` : date);\n if (Number.isNaN(ms)) throw new Error(`[dataset-executor] invalid date in dateRange: \"${date}\"`);\n return ms;\n}\n\nconst DAY_MS = 86_400_000;\n\nfunction toISODate(ms: number): string {\n return new Date(ms).toISOString().slice(0, 10);\n}\n\nfunction shiftYear(date: string, years: number): string {\n const d = new Date(parseUTC(date));\n d.setUTCFullYear(d.getUTCFullYear() + years);\n return toISODate(d.getTime());\n}\n\n/** Compute the comparison window for a [start,end] range. */\nexport function shiftRange(range: [string, string], kind: CompareTo['kind']): [string, string] {\n const [start, end] = range;\n if (kind === 'previousYear') {\n return [shiftYear(start, -1), shiftYear(end, -1)];\n }\n // previousPeriod — the equal-length window ending the day before `start`.\n const startMs = parseUTC(start);\n const endMs = parseUTC(end);\n const lengthDays = Math.round((endMs - startMs) / DAY_MS) + 1;\n const prevEndMs = startMs - DAY_MS;\n const prevStartMs = prevEndMs - (lengthDays - 1) * DAY_MS;\n return [toISODate(prevStartMs), toISODate(prevEndMs)];\n}\n\nexport class DatasetExecutor {\n /**\n * @param service - The analytics service the executor issues its queries to.\n * @param orderLabels - Optional sort-key label hook (#3680). When provided,\n * an order key naming a label-bearing (`select`/`lookup`) dimension sorts\n * by its display label instead of the stored value. Omit to sort by stored\n * values everywhere (e.g. the draft-preview path, whose seed rows already\n * carry display names).\n */\n constructor(\n private readonly service: IAnalyticsService,\n private readonly orderLabels?: OrderLabelResolver,\n ) {}\n\n /**\n * Execute a dataset selection and return the shaped rows (+ field metadata).\n *\n * @param context - The request's ExecutionContext, threaded into every\n * underlying `IAnalyticsService.query` so the tenant/RLS read scope is\n * applied per request (ADR-0021 D-C).\n */\n async execute(\n compiledInput: CompiledDataset,\n selectionInput: DatasetSelection,\n context?: ExecutionContext,\n ): Promise<AnalyticsResult> {\n // framework#3582 — expand `{current_quarter_start}` / `{current_user_id}`\n // placeholders BEFORE any query is shaped, once for the whole call so every\n // sub-query (measure-scoped, totals, compareTo) shares one instant.\n const { compiled, selection } = resolveSelectionTokens(compiledInput, selectionInput, context);\n\n const result = await this.executeSelection(compiled, selection, context);\n\n // Server-side totals (#1753) — re-run the selection grouped by each\n // requested dimension subset, so a subtotal/grand total is the measure's\n // TRUE aggregate over the underlying rows (an avg total is the average of\n // all rows, not of bucket averages). Re-running the full pipeline keeps\n // measure-scoped filters, derived measures, and compareTo consistent with\n // the primary grid. order/limit/offset are dropped: totals cover the whole\n // selection, and an order key may reference a dimension the grouping drops.\n const groupings = selection.totals?.groupings;\n if (groupings?.length) {\n const selected = new Set(selection.dimensions ?? []);\n const totals: NonNullable<AnalyticsResult['totals']> = [];\n for (const grouping of groupings) {\n const unknown = grouping.filter((d) => !selected.has(d));\n if (unknown.length) {\n throw new Error(\n `[dataset-executor] totals grouping [${grouping.join(', ')}] is not a subset of the selected dimensions — unknown: ${unknown.join(', ')}.`,\n );\n }\n const sub = await this.executeSelection(compiled, {\n ...selection,\n dimensions: grouping,\n totals: undefined,\n order: undefined,\n limit: undefined,\n offset: undefined,\n }, context);\n totals.push({ dimensions: grouping, rows: sub.rows });\n }\n result.totals = totals;\n }\n\n return result;\n }\n\n private async executeSelection(\n compiled: CompiledDataset,\n selection: DatasetSelection,\n context?: ExecutionContext,\n ): Promise<AnalyticsResult> {\n const derivedByName = new Map(compiled.derived.map((d) => [d.name, d]));\n const selectedDerived = selection.measures\n .map((m) => derivedByName.get(m))\n .filter((d): d is DerivedMeasureSpec => !!d);\n\n // Base measures = selected non-derived + dependencies of selected derived.\n const baseMeasures = new Set<string>();\n for (const m of selection.measures) {\n if (!derivedByName.has(m)) baseMeasures.add(m);\n }\n for (const d of selectedDerived) {\n for (const dep of d.of) baseMeasures.add(dep);\n }\n\n // Split measures into those with a scoped filter and those without.\n const unfiltered: string[] = [];\n const filtered: string[] = [];\n for (const m of baseMeasures) {\n (compiled.measureFilters[m] ? filtered : unfiltered).push(m);\n }\n\n const baseFilter = combineFilters(compiled.filter, selection.runtimeFilter);\n const dimensions = selection.dimensions ?? [];\n\n // Effective ordering — validated against what this selection projects, with\n // a deterministic dimension order synthesized for a bare `limit` (#3588).\n const order = resolveOrdering(selection, dimensions);\n\n // #3680 — order keys naming a select/lookup dimension sort by the DISPLAY\n // label the response will carry, not the stored value / FK id. Resolved\n // over the assembled grid below; identified up front because such a key\n // also disqualifies SQL pushdown (the label is not a column the database\n // could ORDER BY, and a SQL LIMIT would truncate the wrong window).\n const labelOrderKeys = this.orderLabels\n ? Object.keys(order ?? {}).filter(\n (k) => dimensions.includes(k) && this.orderLabels!.isLabelBearing(k),\n )\n : [];\n\n // Push `order`/`limit`/`offset` down into the SQL only when this selection\n // is ONE query whose columns can satisfy them. With supplementary\n // measure-scoped queries, a compareTo pass, or derived measures in play, the\n // grid is assembled from several results — a sub-query LIMIT would drop rows\n // before the merge and an ORDER BY would name a column that sub-query never\n // selects. Those cases order in memory below instead.\n const singleQuery = filtered.length === 0 && !selection.compareTo && selectedDerived.length === 0;\n const pushDownKeys = new Set<string>([...dimensions, ...unfiltered]);\n const canPushDownWindow =\n singleQuery && labelOrderKeys.length === 0 &&\n Object.keys(order ?? {}).every((k) => pushDownKeys.has(k));\n const windowQuery = canPushDownWindow\n ? { order, limit: selection.limit, offset: selection.offset }\n : undefined;\n\n // Primary query: all unfiltered base measures in one pass. When every base\n // measure is filter-scoped, the supplementary queries below build the grid.\n let result: AnalyticsResult;\n if (unfiltered.length > 0 || filtered.length === 0) {\n result = await this.service.query(this.buildQuery(compiled, {\n measures: unfiltered,\n dimensions,\n where: baseFilter,\n selection,\n contextTimezone: context?.timezone,\n window: windowQuery,\n }), context);\n } else {\n result = { rows: [], fields: [] };\n }\n\n // Supplementary queries: one per measure-scoped filter, merged by dimension key.\n for (const m of filtered) {\n const mFilter = combineFilters(baseFilter, compiled.measureFilters[m]);\n const sub = await this.service.query(this.buildQuery(compiled, {\n measures: [m], dimensions, where: mFilter, selection,\n contextTimezone: context?.timezone,\n }), context);\n result.rows = mergeByDimensions(result.rows, sub.rows, dimensions, [m]);\n result.fields.push({ name: m, type: 'number' });\n }\n\n // compareTo — run a shifted query over the same base measures and attach.\n if (selection.compareTo) {\n const compareRows = await this.runCompare(compiled, selection, [...baseMeasures], dimensions, baseFilter, context);\n result.rows = mergeByDimensions(\n result.rows,\n compareRows,\n dimensions,\n [...baseMeasures].map((m) => `${m}__compare`),\n );\n for (const m of baseMeasures) result.fields.push({ name: `${m}__compare`, type: 'number' });\n }\n\n // Derived measures (computed from base + compare columns already present).\n result.rows = evaluateDerivedMeasures(result.rows, selectedDerived);\n for (const d of selectedDerived) result.fields.push({ name: d.name, type: 'number' });\n\n // Order + window the assembled grid (#3588). Every column the caller may\n // sort by exists by now — merged measure-scoped values, `__compare`\n // columns, and derived measures included. When the window was already\n // pushed into SQL this re-sorts an already-sorted grid (a no-op) and\n // re-slices an already-sliced one; when it could not be (the ObjectQL\n // aggregate path has no ordering grammar, and date-bucketed queries are\n // forced down it), this is what makes `sortBy` work at all.\n //\n // #3680 — for label-bearing order keys, substitute the display label as\n // the SORT KEY, resolved over the grid's distinct values BEFORE the window\n // (a \"top 10 by account name\" must pick the ten by name). Rows keep their\n // raw values — display rewriting stays in `queryDataset`, after the drill\n // metadata snapshots the stored values. A select dimension resolves from\n // field metadata (no query); a lookup costs one batched id→name read.\n let sortKeys: Record<string, Map<unknown, unknown>> | undefined;\n for (const key of labelOrderKeys) {\n const values = [...new Set(result.rows.map((r) => r[key]).filter((v) => v != null))];\n if (values.length === 0) continue;\n const labels = await this.orderLabels!.resolveLabels(key, values);\n if (labels && labels.size > 0) (sortKeys ??= {})[key] = labels;\n }\n result.rows = applyOrdering(result.rows, order, sortKeys);\n result.rows = applyWindow(result.rows, selection.limit, selection.offset);\n\n return result;\n }\n\n private buildQuery(\n compiled: CompiledDataset,\n opts: {\n measures: string[];\n dimensions: string[];\n where?: FilterCondition;\n selection: DatasetSelection;\n contextTimezone?: string;\n /**\n * Ordering/window to push DOWN into this query. Set only for a selection\n * the caller proved is a single self-sufficient query (see\n * `canPushDownWindow`); omitted for supplementary/compare sub-queries,\n * which must return their full grid for the merge.\n */\n window?: { order?: Record<string, 'asc' | 'desc'>; limit?: number; offset?: number };\n },\n ): AnalyticsQuery {\n const q: AnalyticsQuery = {\n cube: compiled.cube.name,\n measures: opts.measures,\n dimensions: opts.dimensions,\n // Precedence: explicit selection tz → request's reference tz\n // (ExecutionContext.timezone, ADR-0053 Phase 2) → UTC.\n timezone: opts.selection.timezone ?? opts.contextTimezone ?? 'UTC',\n };\n if (opts.where) q.where = opts.where as Record<string, unknown>;\n // Bucket selected date dimensions. Without this a date dimension groups by\n // the raw timestamp — one bucket per ROW, which is why a \"new accounts by\n // month\" bar chart drew one bar per account instead of one per month\n // (#3588).\n //\n // Granularity precedence, per dimension:\n // 1. a `granularity` already stated on that dimension's\n // `selection.timeDimensions` entry — never overridden;\n // 2. `selection.dateGranularity` — the PRESENTATION's choice, so a widget\n // can bucket by month without the dataset committing every consumer to\n // that granularity;\n // 3. the dataset dimension's own default (the compiler lowers an explicit\n // `dateGranularity` to a single-entry `granularities`; the 5-entry\n // \"all granularities\" list means the dataset stated no default).\n //\n // Note the unit of precedence is the GRANULARITY, not the entry. A\n // `timeDimensions` entry that only carries a `dateRange` (which is exactly\n // what `compareTo` needs) states a WINDOW, not a bucket size — letting its\n // mere presence suppress bucketing left the compared pass grouping raw\n // timestamps while the primary pass grouped months, so the two grids shared\n // no dimension key and every `__compare` column came back empty.\n const selTimeDims = opts.selection.timeDimensions ?? [];\n const selDims = new Set(selTimeDims.map((t) => t.dimension));\n const granularityFor = (name: string): string | undefined => {\n const cd = compiled.cube.dimensions[name];\n if (cd?.type !== 'time') return undefined;\n const datasetDefault = cd.granularities?.length === 1 ? String(cd.granularities[0]) : undefined;\n return resolveDimensionGranularity(opts.selection, name, datasetDefault);\n };\n // Fill in a bucket size for caller-supplied entries that named none.\n const resolvedTimeDims = selTimeDims.map((t) => {\n if (t.granularity) return t;\n const granularity = granularityFor(t.dimension);\n return granularity ? { ...t, granularity } : t;\n });\n const explicitTimeDims: Array<{ dimension: string; granularity: string }> = [];\n for (const name of opts.dimensions) {\n if (selDims.has(name)) continue;\n const granularity = granularityFor(name);\n if (granularity) explicitTimeDims.push({ dimension: name, granularity });\n }\n const mergedTimeDims = [...resolvedTimeDims, ...explicitTimeDims];\n if (mergedTimeDims.length > 0) q.timeDimensions = mergedTimeDims as AnalyticsQuery['timeDimensions'];\n // Ordering/window: pushed down ONLY when the caller vouched for it. The\n // executor always re-applies both over the assembled grid, so omitting them\n // here costs correctness nothing — it only moves the work to memory.\n if (opts.window?.order && Object.keys(opts.window.order).length > 0) q.order = opts.window.order;\n if (opts.window?.limit != null) q.limit = opts.window.limit;\n if (opts.window?.offset != null) q.offset = opts.window.offset;\n return q;\n }\n\n private async runCompare(\n compiled: CompiledDataset,\n selection: DatasetSelection,\n measures: string[],\n dimensions: string[],\n baseFilter: FilterCondition | undefined,\n context?: ExecutionContext,\n ): Promise<Record<string, unknown>[]> {\n const cmp = selection.compareTo!;\n const td = (selection.timeDimensions ?? []).find((t) => t.dimension === cmp.dimension);\n if (!td || !td.dateRange) {\n throw new Error(\n `[dataset-executor] compareTo requires a timeDimension \"${cmp.dimension}\" with a dateRange.`,\n );\n }\n const range: [string, string] = Array.isArray(td.dateRange)\n ? [td.dateRange[0], td.dateRange[1] ?? td.dateRange[0]]\n : [td.dateRange, td.dateRange];\n const shifted = shiftRange(range, cmp.kind);\n const shiftedTd = (selection.timeDimensions ?? []).map((t) =>\n t.dimension === cmp.dimension ? { ...t, dateRange: shifted } : t,\n );\n // Built through `buildQuery` so the comparison pass buckets its date\n // dimensions EXACTLY like the primary pass. Hand-rolling the query here\n // skipped granularity resolution, so a bucketed primary grid (\"2026-04\")\n // was merged against raw-timestamp comparison rows and no dimension key\n // ever matched — every `__compare` column came back empty. The shifted\n // `timeDimensions` still win for their own dimension (rule 1 of the\n // precedence chain); `window` is deliberately omitted — the comparison grid\n // must stay whole for the merge.\n const sub = await this.service.query(this.buildQuery(compiled, {\n measures,\n dimensions,\n where: baseFilter,\n selection: { ...selection, timeDimensions: shiftedTd },\n contextTimezone: context?.timezone,\n }), context);\n // Rename measure columns to `<measure>__compare` so they merge alongside primary.\n return sub.rows.map((row) => {\n const out: Record<string, unknown> = {};\n for (const dim of dimensions) out[dim] = row[dim];\n for (const m of measures) out[`${m}__compare`] = row[m];\n return out;\n });\n }\n}\n\n/**\n * Left-merge `extra` rows onto `base` rows by their dimension-key tuple,\n * copying the listed value columns. Rows in `extra` with no base match are\n * appended (outer-ish merge so comparison-only buckets still surface).\n */\nexport function mergeByDimensions(\n base: Record<string, unknown>[],\n extra: Record<string, unknown>[],\n dimensions: string[],\n valueColumns: string[],\n): Record<string, unknown>[] {\n const keyOf = (row: Record<string, unknown>) => dimensions.map((d) => String(row[d] ?? '')).join('\u0001');\n const index = new Map<string, Record<string, unknown>>();\n for (const row of base) index.set(keyOf(row), row);\n\n for (const row of extra) {\n const key = keyOf(row);\n const target = index.get(key);\n if (target) {\n for (const c of valueColumns) target[c] = row[c];\n } else {\n const fresh: Record<string, unknown> = {};\n for (const d of dimensions) fresh[d] = row[d];\n for (const c of valueColumns) fresh[c] = row[c];\n index.set(key, fresh);\n base.push(fresh);\n }\n }\n return base;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Dimension display-label resolution (ADR-0021).\n *\n * Analytics groups by the raw stored value of a dimension field. For two field\n * kinds that value is NOT human-readable:\n *\n * - **select** — grouped by the stored option `value` (e.g. `backlog`), but the\n * user-facing text is the option `label` (e.g. `Backlog`).\n * - **lookup / master_detail** — grouped by the foreign-key `id` (e.g.\n * `8eqtuKI4G9IhUsPS`), but the user-facing text is the related record's\n * display field (its name/title).\n *\n * `resolveDimensionLabels` post-processes the result rows IN PLACE, replacing the\n * raw value at `row[dimension.name]` with its display label when one is found.\n * Unresolved values are left untouched so an orphaned id still renders as itself\n * rather than blanking out. Date / number / plain-string dimensions are no-ops.\n *\n * The resolution LOGIC lives here (and is unit-tested); the low-level capabilities\n * — reading an object's field map and fetching id→label pairs — are injected via\n * {@link DimensionLabelDeps} so this module stays free of any engine dependency.\n */\n\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\n\n/** The minimal field shape this resolver needs. */\nexport interface FieldMetaLite {\n type?: string;\n /** Lookup / master_detail target object name. */\n reference?: string;\n /** Select options — the value→label source. */\n options?: Array<{ value: unknown; label?: string }>;\n}\n\n/** Capabilities the resolver needs from the runtime (injected by the plugin). */\nexport interface DimensionLabelDeps {\n /** Return the field map for an object, or `undefined` if unknown. */\n getObjectFields(objectName: string): Record<string, FieldMetaLite> | undefined;\n /**\n * Fetch a map of `id → display label` for the given ids of a target object.\n * The implementation chooses the target's display field. Returning an empty\n * map (e.g. no display field, no data access) leaves the ids unresolved.\n *\n * `scope` (ADR-0021 D-C, #3602) is the TARGET object's own read scope — the\n * RLS/tenant `FilterCondition` the implementation must AND into the label\n * lookup so this never reveals a related record the target object's RLS would\n * hide. The label lookup is a per-record read (`group by id`) dressed as an\n * aggregate; without the scope it leaks display names whenever the referenced\n * object is more restricted than the base object whose rows carry the id.\n * `undefined` means \"no scope for this object\" (global table / unrestricted\n * caller) — the same contract as the read-scope provider.\n *\n * `context` is the request's ExecutionContext — the SECOND belt on the same\n * read (#3602). `scope` is the analytics layer's own predicate; forwarding the\n * context lets the ENGINE's middleware chain scope this per-record read\n * itself, so it stays scoped even if a caller ever reaches this hook without\n * a resolved `scope`. Implementations bridging to an ObjectQL engine MUST\n * forward it; a bridge with nowhere to put it may ignore it.\n */\n fetchRecordLabels(\n targetObject: string,\n ids: unknown[],\n scope?: Record<string, unknown>,\n context?: ExecutionContext,\n ): Promise<Map<unknown, string>>;\n}\n\n/**\n * Resolve the TARGET object's read scope for a label lookup (#3602). Returns the\n * object's RLS/tenant `FilterCondition`, `null`/`undefined` when the object is\n * unscoped, or a rejected promise when the scope cannot be resolved — in which\n * case the resolver fails CLOSED (skips that dimension's labels) rather than\n * fetching unscoped names.\n */\nexport type LabelScopeResolver = (\n targetObject: string,\n) => Promise<Record<string, unknown> | null | undefined> | Record<string, unknown> | null | undefined;\n\nconst LOOKUP_TYPES = new Set(['lookup', 'master_detail']);\n\n/**\n * Sort-key label resolution for `DatasetSelection.order` (#3680).\n *\n * The executor sorts the assembled grid BEFORE `queryDataset` rewrites stored\n * dimension values into display labels, so an order key naming a `select` or\n * `lookup`/`master_detail` dimension used to sort by the stored value / FK id —\n * an order that presents as arbitrary once the labels render. This hook hands\n * the executor JUST the value→label mapping for such a dimension so it can sort\n * by what the user will actually read, while the rows keep their raw values\n * (drill metadata depends on them) and ordering + windowing stay one adjacent\n * step. The executor stays engine-free: it sees this interface, never the\n * engine behind it.\n */\nexport interface OrderLabelResolver {\n /**\n * Whether the dimension's stored value differs from the label it renders as\n * (`select` options, `lookup`/`master_detail` FK ids). Synchronous — the\n * executor consults it when deciding whether the window may be pushed into\n * SQL, before any query runs.\n */\n isLabelBearing(dimension: string): boolean;\n /**\n * Map the given raw stored values of one dimension to display labels.\n * Values missing from the map sort by their raw form — the same thing the\n * user will see rendered for them.\n */\n resolveLabels(dimension: string, values: unknown[]): Promise<Map<unknown, string> | undefined>;\n}\n\n/**\n * Build the executor's {@link OrderLabelResolver} from the dataset's dimension\n * list and the injected label capabilities. Mirrors the classification in\n * {@link resolveDimensionLabels}: a dimension is label-bearing when its field\n * carries select `options` or is a lookup/master_detail with a `reference`.\n *\n * - `select` resolves from field metadata — no query at all.\n * - `lookup`/`master_detail` costs ONE batched id→name read over the distinct\n * grouped values, scoped to the REFERENCED object's own RLS (#3602). Fail\n * closed: an unresolvable scope degrades to sorting by the stored id rather\n * than fetching unscoped — consistent with the display pass, which renders\n * the raw id in that case too.\n */\nexport function createOrderLabelResolver(\n baseObject: string,\n dims: Array<{ name: string; field: string }>,\n deps: DimensionLabelDeps,\n resolveScope?: LabelScopeResolver,\n context?: ExecutionContext,\n): OrderLabelResolver {\n const dimByName = new Map(dims.map((d) => [d.name, d]));\n const metaFor = (dimension: string): FieldMetaLite | undefined => {\n const dim = dimByName.get(dimension);\n return dim ? deps.getObjectFields(baseObject)?.[dim.field] : undefined;\n };\n return {\n isLabelBearing(dimension) {\n const meta = metaFor(dimension);\n if (!meta) return false;\n if (Array.isArray(meta.options) && meta.options.length > 0) return true;\n return !!(meta.type && LOOKUP_TYPES.has(meta.type) && meta.reference);\n },\n async resolveLabels(dimension, values) {\n const meta = metaFor(dimension);\n if (!meta) return undefined;\n if (Array.isArray(meta.options) && meta.options.length > 0) {\n const labelByValue = new Map<unknown, string>();\n for (const opt of meta.options) {\n if (opt && opt.label != null) labelByValue.set(opt.value, String(opt.label));\n }\n return labelByValue;\n }\n if (meta.type && LOOKUP_TYPES.has(meta.type) && meta.reference) {\n let scope: Record<string, unknown> | null | undefined;\n if (resolveScope) {\n try {\n scope = await resolveScope(meta.reference);\n } catch {\n return undefined;\n }\n }\n return deps.fetchRecordLabels(meta.reference, values, scope ?? undefined, context);\n }\n return undefined;\n },\n };\n}\n\n/**\n * Wrap a {@link DimensionLabelDeps} so repeated `fetchRecordLabels` calls\n * within ONE request fetch each id at most once. A selection that sorts by a\n * lookup dimension resolves labels twice — once PRE-window for the sort keys\n * (#3680, over the full grid's ids), once post-window for display (a subset of\n * the same ids) — so with this cache the display pass costs no extra query.\n *\n * Per-request only: entries are keyed by target object alone, which is safe\n * because an object's read scope is constant within one request. Never share\n * an instance across requests.\n */\nexport function withLabelFetchCache(deps: DimensionLabelDeps): DimensionLabelDeps {\n // Per target object: id → label, with `null` marking \"fetched, no label\"\n // (RLS-hidden or orphaned) so unresolvable ids are not re-fetched every call.\n const cache = new Map<string, Map<unknown, string | null>>();\n return {\n getObjectFields: (objectName) => deps.getObjectFields(objectName),\n async fetchRecordLabels(targetObject, ids, scope, context) {\n let known = cache.get(targetObject);\n if (!known) {\n known = new Map();\n cache.set(targetObject, known);\n }\n const missing = ids.filter((id) => !known.has(id));\n if (missing.length > 0) {\n const fetched = await deps.fetchRecordLabels(targetObject, missing, scope, context);\n for (const id of missing) known.set(id, fetched.get(id) ?? null);\n }\n const out = new Map<unknown, string>();\n for (const id of ids) {\n const label = known.get(id);\n if (label != null) out.set(id, label);\n }\n return out;\n },\n };\n}\n\n/** Date-dimension granularity (mirrors the dataset `dateGranularity` enum). */\nexport type DateGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year';\n\nconst pad = (n: number) => String(n).padStart(2, '0');\n\n/**\n * Format a raw date value (epoch-ms number, numeric string, ISO string, or\n * Date) to a human, sort-stable bucket label per granularity. Returns the input\n * unchanged when it isn't a parseable date, so a non-date value never blanks.\n *\n * year → \"2026\"\n * quarter → \"2026-Q2\"\n * month → \"2026-04\"\n * week → \"2026-04-13\" (ISO date of the bucket)\n * day → \"2026-04-15\"\n *\n * Intentionally UTC-only (ADR-0053 Phase 2): timezone bucketing happens\n * upstream in `bucketDate` / `bucketDateValue`, so by the time a value reaches\n * here it is *already* the reference-zone bucket (often a label string like\n * \"2026-Q2\"). Re-applying a timezone here would shift an already-correct\n * `YYYY-MM-DD` day bucket by a day — this is a pure, idempotent re-labeler.\n */\nexport function formatDateBucket(value: unknown, granularity?: DateGranularity | string): unknown {\n if (value == null || value instanceof Date === false) {\n if (typeof value !== 'number' && typeof value !== 'string') return value;\n }\n // A YEAR bucket's canonical key IS the bare year (\"2026\" / 2026) — which the\n // epoch heuristic below would read as 2026 milliseconds and relabel \"1970\".\n // Being idempotent over already-formatted bucket keys is this function's whole\n // contract, and every other granularity's key already survives the round trip\n // (\"2026-Q2\", \"2026-07\", \"2026-07-15\" all fail the pure-digit test); only the\n // year key collides with it. Recognised before parsing, for both the string\n // and numeric forms drivers return.\n if (granularity === 'year') {\n const y = typeof value === 'number' ? value : Number(String(value).trim());\n if (Number.isInteger(y) && y >= 1000 && y <= 9999) return String(y);\n }\n let d: Date;\n if (value instanceof Date) d = value;\n else if (typeof value === 'number') d = new Date(value);\n else {\n const s = String(value).trim();\n // Pure-digit strings are epoch millis (or seconds); otherwise let Date parse ISO.\n d = /^\\d+$/.test(s) ? new Date(Number(s) < 1e12 ? Number(s) * 1000 : Number(s)) : new Date(s);\n }\n if (Number.isNaN(d.getTime())) return value;\n const y = d.getUTCFullYear();\n const m = d.getUTCMonth(); // 0-11\n switch (granularity) {\n case 'year': return String(y);\n case 'quarter': return `${y}-Q${Math.floor(m / 3) + 1}`;\n case 'month': return `${y}-${pad(m + 1)}`;\n case 'week':\n case 'day':\n default: return `${y}-${pad(m + 1)}-${pad(d.getUTCDate())}`;\n }\n}\n\n/**\n * Replace raw dimension values with display labels, in place.\n *\n * @param baseObject - the dataset's base object (where the dimension fields live)\n * @param dims - selected dimensions as `{ name, field, type?, dateGranularity? }`\n * (row key = `name`)\n * @param rows - result rows, mutated in place\n * @param deps - injected runtime capabilities\n * @param resolveScope - (ADR-0021 D-C, #3602) resolves the referenced object's\n * own read scope for a lookup/master_detail dimension's label fetch. When it\n * throws, that dimension's labels are SKIPPED (fail-closed — the raw id renders\n * instead) rather than fetched unscoped. Omit when no read-scope provider is\n * configured (labels then fetch unscoped, as before — no security in play).\n * @param context - the request's ExecutionContext, forwarded to\n * {@link DimensionLabelDeps.fetchRecordLabels} so the engine's own middleware\n * scopes the per-record label read too — the second belt beside `resolveScope`\n * (#3602)\n */\nexport async function resolveDimensionLabels(\n baseObject: string,\n dims: Array<{ name: string; field: string; type?: string; dateGranularity?: DateGranularity | string }>,\n rows: Record<string, unknown>[],\n deps: DimensionLabelDeps,\n resolveScope?: LabelScopeResolver,\n context?: ExecutionContext,\n): Promise<void> {\n if (!rows.length || !dims.length) return;\n const fields = deps.getObjectFields(baseObject);\n if (!fields) return;\n\n for (const dim of dims) {\n const meta = fields[dim.field];\n\n // ── date: epoch / ISO → human bucket label ────────────────────────\n // A date dimension's grouped value is a raw timestamp (or a bucket start);\n // either way it must render as a readable date, not epoch millis.\n if (dim.type === 'date' || (meta && meta.type === 'date')) {\n for (const row of rows) {\n const formatted = formatDateBucket(row[dim.name], dim.dateGranularity);\n if (formatted != null) row[dim.name] = formatted;\n }\n continue;\n }\n\n if (!meta) continue;\n\n // ── select: value → option label ──────────────────────────────────\n if (Array.isArray(meta.options) && meta.options.length > 0) {\n const labelByValue = new Map<unknown, string>();\n for (const opt of meta.options) {\n if (opt && opt.label != null) labelByValue.set(opt.value, String(opt.label));\n }\n if (labelByValue.size === 0) continue;\n for (const row of rows) {\n const raw = row[dim.name];\n const label = labelByValue.get(raw);\n if (label != null) row[dim.name] = label;\n }\n continue;\n }\n\n // ── lookup / master_detail: id → related record display name ───────\n if (meta.type && LOOKUP_TYPES.has(meta.type) && meta.reference) {\n const ids = Array.from(\n new Set(rows.map((r) => r[dim.name]).filter((v) => v != null)),\n );\n if (ids.length === 0) continue;\n // #3602 — the label lookup reads the REFERENCED object by id. Scope it to\n // that object's own RLS so it never surfaces a related record the target's\n // RLS would hide (leak fires when the referenced object is stricter than\n // the base). Fail closed: if the scope can't be resolved, skip this\n // dimension's labels (raw id renders) rather than fetch unscoped.\n let scope: Record<string, unknown> | null | undefined;\n if (resolveScope) {\n try {\n scope = await resolveScope(meta.reference);\n } catch {\n continue;\n }\n }\n const labelById = await deps.fetchRecordLabels(meta.reference, ids, scope ?? undefined, context);\n if (!labelById || labelById.size === 0) continue;\n for (const row of rows) {\n const label = labelById.get(row[dim.name]);\n if (label != null) row[dim.name] = label;\n }\n }\n }\n}\n\n/**\n * Pick the display field for an object from its field map, by convention:\n * an explicit `name`/`title`/`label` field, else the first text-like field.\n * Returns `undefined` when nothing suitable exists.\n */\nexport function pickDisplayField(\n fields: Record<string, FieldMetaLite> | undefined,\n): string | undefined {\n if (!fields) return undefined;\n for (const preferred of ['name', 'title', 'label']) {\n if (fields[preferred]) return preferred;\n }\n for (const [name, meta] of Object.entries(fields)) {\n if (meta.type === 'text' || meta.type === 'string') return name;\n }\n return undefined;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n//\n// ADR-0037 Phase 3 — draft data preview: evaluate an AnalyticsQuery over an\n// in-memory row set (the pending `seed` draft's records) instead of the real\n// data engine. This is what lets a Live Canvas dashboard chart REAL numbers\n// from the DRAFTED sample data before anything is published — and because\n// publish materializes the *same* seed, the numbers are continuous across\n// the publish boundary.\n//\n// Scope (deliberately the dataset-query subset, not a general engine):\n// • Mongo-style `where` filters ($eq implicit, $ne/$gt/$gte/$lt/$lte/\n// $in/$nin/$contains, $and/$or/$not)\n// • timeDimensions date-range filtering + granularity bucketing\n// (day/week/month/quarter/year)\n// • group-by dimensions; count / countDistinct / sum / avg / min / max\n// • order + limit/offset\n// Anything beyond (joins via `include`, raw SQL) falls back to the caller's\n// normal execution path — the preview simply doesn't claim it.\n\nimport { calendarPartsInTzOrUtc } from '@objectstack/core';\nimport type { AnalyticsQuery, AnalyticsResult } from '@objectstack/spec/contracts';\nimport type { Cube } from '@objectstack/spec/data';\n\ntype Row = Record<string, unknown>;\n\n// ── Filters (the unified Query DSL subset) ──────────────────────────────────\n\nfunction compare(a: unknown, b: unknown): number {\n if (typeof a === 'number' && typeof b === 'number') return a - b;\n return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;\n}\n\nfunction matchOp(value: unknown, op: string, expected: unknown): boolean {\n switch (op) {\n case '$eq': return value === expected || String(value) === String(expected);\n case '$ne': return !(value === expected || String(value) === String(expected));\n case '$gt': return value != null && compare(value, expected) > 0;\n case '$gte': return value != null && compare(value, expected) >= 0;\n case '$lt': return value != null && compare(value, expected) < 0;\n case '$lte': return value != null && compare(value, expected) <= 0;\n case '$in': return Array.isArray(expected) && expected.some((e) => value === e || String(value) === String(e));\n case '$nin': return Array.isArray(expected) && !expected.some((e) => value === e || String(value) === String(e));\n case '$contains': return String(value ?? '').toLowerCase().includes(String(expected ?? '').toLowerCase());\n default: return true; // unknown operator — permissive (preview, reads only)\n }\n}\n\nexport function matchesWhere(row: Row, where: Record<string, unknown> | undefined): boolean {\n if (!where) return true;\n for (const [key, cond] of Object.entries(where)) {\n if (key === '$and') {\n if (!(cond as Row[]).every((c) => matchesWhere(row, c as Row))) return false;\n } else if (key === '$or') {\n if (!(cond as Row[]).some((c) => matchesWhere(row, c as Row))) return false;\n } else if (key === '$not') {\n if (matchesWhere(row, cond as Row)) return false;\n } else if (cond !== null && typeof cond === 'object' && !Array.isArray(cond)) {\n for (const [op, expected] of Object.entries(cond as Row)) {\n if (!matchOp(row[key], op, expected)) return false;\n }\n } else if (!(row[key] === cond || String(row[key]) === String(cond))) {\n return false; // implicit equality\n }\n }\n return true;\n}\n\n// ── Time bucketing ──────────────────────────────────────────────────────────\n\nexport function bucketDate(value: unknown, granularity: string, timezone?: string): string | null {\n const d = new Date(String(value));\n if (Number.isNaN(d.getTime())) return null;\n // ADR-0053 Phase 2: resolve the calendar day in the reference zone so an\n // instant near a tz day-boundary buckets where a user in that zone expects.\n // Unset / 'UTC' / invalid keeps the historical UTC bucketing.\n const { year: y, month, day: dayNum } = calendarPartsInTzOrUtc(d, timezone);\n const m = `${month}`.padStart(2, '0');\n const day = `${dayNum}`.padStart(2, '0');\n switch (granularity) {\n case 'year': return `${y}`;\n case 'quarter': return `${y}-Q${Math.floor((month - 1) / 3) + 1}`;\n case 'month': return `${y}-${m}`;\n case 'week': {\n // Build a UTC date from the zone-shifted parts, then step back to Monday.\n const monday = new Date(Date.UTC(y, month - 1, dayNum));\n const dow = (monday.getUTCDay() + 6) % 7; // Monday=0\n monday.setUTCDate(monday.getUTCDate() - dow);\n return monday.toISOString().slice(0, 10);\n }\n case 'day':\n default:\n return `${y}-${m}-${day}`;\n }\n}\n\n// ── Aggregation ─────────────────────────────────────────────────────────────\n\nfunction aggregate(rows: Row[], metricType: string, field: string): number {\n if (metricType === 'count' || field === '*') {\n if (metricType === 'countDistinct') {\n return new Set(rows.map((r) => r[field]).filter((v) => v != null)).size;\n }\n return rows.length;\n }\n const nums = rows.map((r) => Number(r[field])).filter((n) => Number.isFinite(n));\n switch (metricType) {\n case 'countDistinct': return new Set(rows.map((r) => r[field]).filter((v) => v != null)).size;\n case 'sum': return nums.reduce((a, b) => a + b, 0);\n case 'avg': return nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : 0;\n case 'min': return nums.length ? Math.min(...nums) : 0;\n case 'max': return nums.length ? Math.max(...nums) : 0;\n default: return nums.length ? nums.reduce((a, b) => a + b, 0) : rows.length;\n }\n}\n\n/**\n * Evaluate `query` over `rows` using the cube's measure/dimension specs.\n * Mirrors the engine strategies' output contract: rows keyed by bare\n * measure/dimension names, `fields` describing each output column.\n */\nexport function evaluateAnalyticsQueryOverRows(\n query: AnalyticsQuery,\n cube: Cube,\n rows: Row[],\n): AnalyticsResult {\n // 1. Row-level filters: `where`, then timeDimension dateRanges.\n let filtered = rows.filter((r) => matchesWhere(r, query.where));\n const timeDims = query.timeDimensions ?? [];\n for (const td of timeDims) {\n const dim = cube.dimensions?.[td.dimension];\n const field = String(dim?.sql ?? td.dimension);\n if (!td.dateRange) continue;\n const [start, end] = Array.isArray(td.dateRange) ? td.dateRange : [td.dateRange, td.dateRange];\n filtered = filtered.filter((r) => {\n const v = String(r[field] ?? '');\n return v >= String(start) && v <= `${end}~`; // '~' > any date char: inclusive end-day\n });\n }\n\n // 2. Grouping keys: each selected dimension (time dims bucketed).\n const dimensions = query.dimensions ?? [];\n const timezone = query.timezone; // ADR-0053 Phase 2: reference tz for bucketing\n const granByDim = new Map(timeDims.filter((t) => t.granularity).map((t) => [t.dimension, t.granularity!]));\n const keyOf = (r: Row): { key: string; values: Row } => {\n const values: Row = {};\n for (const name of dimensions) {\n const dim = cube.dimensions?.[name];\n const field = String(dim?.sql ?? name);\n const raw = r[field];\n const gran = granByDim.get(name) ?? (dim?.type === 'time' && dim.granularities?.length === 1 ? String(dim.granularities[0]) : undefined);\n values[name] = gran ? bucketDate(raw, gran, timezone) : (raw ?? null);\n }\n return { key: JSON.stringify(values), values };\n };\n\n const groups = new Map<string, { values: Row; rows: Row[] }>();\n for (const r of filtered) {\n const { key, values } = keyOf(r);\n const g = groups.get(key) ?? { values, rows: [] };\n g.rows.push(r);\n groups.set(key, g);\n }\n // No dimensions → a single overall group (even over zero rows: count = 0).\n if (dimensions.length === 0 && groups.size === 0) {\n groups.set('{}', { values: {}, rows: [] });\n }\n\n // 3. Aggregate each measure per group.\n const out: Row[] = [];\n for (const g of groups.values()) {\n const row: Row = { ...g.values };\n for (const m of query.measures) {\n const metric = cube.measures?.[m];\n row[m] = aggregate(g.rows, String(metric?.type ?? 'count'), String(metric?.sql ?? '*'));\n }\n out.push(row);\n }\n\n // 4. Order + paging.\n for (const [col, dir] of Object.entries(query.order ?? {}).reverse()) {\n out.sort((a, b) => (dir === 'desc' ? -1 : 1) * compare(a[col], b[col]));\n }\n const offset = query.offset ?? 0;\n const limited = out.slice(offset, query.limit != null ? offset + query.limit : undefined);\n\n return {\n rows: limited,\n fields: [\n ...dimensions.map((d) => ({ name: d, type: 'string' })),\n ...query.measures.map((m) => ({ name: m, type: 'number' })),\n ],\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { Cube, FilterCondition } from '@objectstack/spec/data';\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\nimport type { IAnalyticsService } from '@objectstack/spec/contracts';\nimport { AnalyticsService } from './analytics-service.js';\nimport type { AnalyticsServiceConfig } from './analytics-service.js';\nimport type { DriverCapabilities } from './strategies/types.js';\nimport { pickDisplayField, type DimensionLabelDeps } from './dimension-labels.js';\n\n/**\n * Minimal IDataEngine surface required for the auto-bridge.\n * ObjectQL exposes:\n * - `aggregate(object, { where, groupBy, aggregations: [{ function, field, alias }] })`\n * - `execute(sql, options)` for raw SQL pass-through (enables NativeSQLStrategy\n * and lets the analytics layer emit JOINs for relation traversal).\n */\ninterface DataEngineLike {\n aggregate(object: string, options: {\n where?: Record<string, unknown>;\n groupBy?: string[];\n aggregations?: Array<{ function: string; field: string; alias: string }>;\n /** Reference timezone (IANA) for date bucketing — ADR-0053 Phase 2. */\n timezone?: string;\n /**\n * `BaseEngineOptions.context` — identity/tenant of the request. The engine\n * merges it into the operation context (`mergeReadContext`), which is what\n * lets its middleware chain inject RLS into `opCtx.ast.where` (#3602).\n */\n context?: ExecutionContext;\n }): Promise<unknown[]>;\n execute?(command: unknown, options?: Record<string, unknown>): Promise<unknown>;\n /** Return the registered object schema (relationship → target + display-label resolution). */\n getObject?(name: string): {\n fields?: Record<string, {\n type?: string;\n reference?: string;\n options?: Array<{ value: unknown; label?: string }>;\n }>;\n /** Federation marker (ADR-0015): set on objects bound to an external datasource. */\n external?: unknown;\n /** The datasource this object is bound to (ADR-0062 D6 external detection). */\n datasource?: string;\n } | undefined;\n /**\n * Resolve the storage driver backing an object (public ObjectQL accessor).\n * Used to delegate temporal filter-value coercion to the driver, which is the\n * single source of truth for how a `Field.date`/`Field.datetime` is stored on\n * the active dialect. The driver may expose `temporalFilterValue(object, field,\n * value)` (SqlDriver does); when absent we leave the value untouched.\n */\n getDriverForObject?(objectName: string): DriverLike | undefined;\n}\n\n/** Minimal driver surface the analytics layer probes for temporal coercion. */\ninterface DriverLike {\n /**\n * Coerce a filter comparand to the column's on-disk storage form\n * (SQLite `Field.datetime` → epoch ms; `Field.date` → YYYY-MM-DD; native\n * timestamp / non-temporal → unchanged). Optional — only SqlDriver implements it.\n */\n temporalFilterValue?(objectName: string, field: string, value: unknown): unknown;\n}\n\n/**\n * Configuration for AnalyticsServicePlugin.\n */\nexport interface AnalyticsServicePluginOptions {\n /** Pre-defined cube definitions (from manifest). */\n cubes?: Cube[];\n /**\n * Probe driver capabilities for a given cube.\n * When omitted, defaults to in-memory only.\n */\n queryCapabilities?: (cubeName: string) => DriverCapabilities;\n /**\n * Execute raw SQL on a driver. Enables NativeSQLStrategy.\n */\n executeRawSql?: (objectName: string, sql: string, params: unknown[]) => Promise<Record<string, unknown>[]>;\n /**\n * Execute ObjectQL aggregate. Enables ObjectQLStrategy.\n */\n executeAggregate?: (objectName: string, options: {\n groupBy?: string[];\n aggregations?: Array<{ field: string; method: string; alias: string }>;\n filter?: Record<string, unknown>;\n /** Reference timezone (IANA) for date bucketing — ADR-0053 Phase 2. */\n timezone?: string;\n /**\n * ADR-0021 D-C (#3602) — the request's ExecutionContext. A custom bridge\n * MUST forward it to its engine so engine-side RLS applies; dropping it is\n * what made the built-in bridge fall open in #3597.\n */\n context?: ExecutionContext;\n }) => Promise<Record<string, unknown>[]>;\n /**\n * ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). The\n * runtime supplies this from its sharing middleware so the analytics raw-SQL\n * path cannot bypass tenant isolation. Receives the request's ExecutionContext\n * and returns the RLS `FilterCondition` for the object (what `RLSCompiler`\n * emits). When omitted, the plugin auto-bridges to a registered `'security'`\n * service exposing `getReadFilter(object, context)` if one is present.\n */\n getReadScope?: (\n objectName: string,\n context?: ExecutionContext,\n ) =>\n | FilterCondition\n | null\n | undefined\n | Promise<FilterCondition | null | undefined>;\n /**\n * ADR-0021 D-C — join allowlist per cube (the dataset's declared `include`).\n * Typically wired from the dataset registry's compiled `allowedRelationships`.\n */\n getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;\n /** Enable debug logging. */\n debug?: boolean;\n}\n\n/**\n * AnalyticsServicePlugin — Kernel plugin for multi-driver analytics.\n *\n * Lifecycle:\n * 1. **init** — Creates `AnalyticsService`, registers as `'analytics'` service.\n * If an existing analytics service is already registered (e.g. MemoryAnalyticsService\n * from dev-plugin), it is captured as the `fallbackService`.\n * 2. **start** — Triggers `'analytics:ready'` hook so other plugins can\n * register cubes or extend the service.\n * 3. **destroy** — Cleans up references.\n *\n * @example\n * ```ts\n * import { LiteKernel } from '@objectstack/core';\n * import { AnalyticsServicePlugin } from '@objectstack/service-analytics';\n *\n * const kernel = new LiteKernel();\n * kernel.use(new AnalyticsServicePlugin({\n * cubes: [ordersCube],\n * queryCapabilities: (cube) => ({ nativeSql: true, objectqlAggregate: true, inMemory: false }),\n * executeRawSql: async (obj, sql, params) => pgPool.query(sql, params).then(r => r.rows),\n * }));\n * await kernel.bootstrap();\n *\n * const analytics = kernel.getService<IAnalyticsService>('analytics');\n * const result = await analytics.query({ cube: 'orders', measures: ['orders.count'] });\n * ```\n */\nexport class AnalyticsServicePlugin implements Plugin {\n name = 'com.objectstack.service-analytics';\n version = '1.0.0';\n type = 'standard' as const;\n dependencies: string[] = [];\n\n private service?: AnalyticsService;\n private readonly options: AnalyticsServicePluginOptions;\n\n constructor(options: AnalyticsServicePluginOptions = {}) {\n this.options = options;\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Check if there is an existing analytics service (e.g. from dev-plugin)\n let fallbackService: IAnalyticsService | undefined;\n try {\n const existing = ctx.getService<IAnalyticsService>('analytics');\n if (existing && typeof existing.query === 'function') {\n fallbackService = existing;\n ctx.logger.debug('[Analytics] Found existing analytics service, using as fallback');\n }\n } catch {\n // No existing service — that's fine\n }\n\n // Auto-bridge: when caller did not supply executeAggregate, look up the\n // kernel's IDataEngine (registered as 'data' by ObjectQLPlugin) lazily and\n // translate AnalyticsStrategy's `{method, filter}` shape into the engine's\n // `{function, where}` shape. This lets users write\n // `new AnalyticsServicePlugin({ cubes })`\n // without re-implementing the bridge in every app.\n let executeAggregate = this.options.executeAggregate;\n let autoBridged = false;\n if (!executeAggregate) {\n const tryGetDataEngine = (): DataEngineLike | undefined => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n return svc && typeof svc.aggregate === 'function' ? svc : undefined;\n } catch {\n return undefined;\n }\n };\n // Probe now (warn if missing) but resolve at call time so plugin order\n // does not matter as long as 'data' exists by the time a query runs.\n if (!tryGetDataEngine()) {\n ctx.logger.warn(\n '[Analytics] No \"data\" service registered yet at init; ' +\n 'will retry per-query. Register ObjectQLPlugin or pass executeAggregate.',\n );\n }\n executeAggregate = async (objectName, { groupBy, aggregations, filter, timezone, context }) => {\n const engine = tryGetDataEngine();\n if (!engine) {\n throw new Error(\n '[Analytics] Cannot execute aggregate: no IDataEngine (\"data\") service is registered. ' +\n 'Add ObjectQLPlugin to the kernel or supply AnalyticsServicePlugin({ executeAggregate }).',\n );\n }\n const rows = await engine.aggregate(objectName, {\n where: filter,\n groupBy,\n aggregations: aggregations?.map((a) => ({\n function: a.method,\n field: a.field,\n alias: a.alias,\n })),\n // ADR-0053 Phase 2: thread the reference tz so date buckets resolve on\n // that zone's calendar days (engine buckets in-memory when non-UTC).\n timezone,\n // ADR-0021 D-C (#3602): thread the caller's identity so the engine's\n // middleware chain scopes the read itself. `BaseEngineOptions.context`\n // is `.optional()`, so nothing ever forced this bridge to pass it —\n // and it did not, which is how an authenticated aggregate reached the\n // engine with no principal and plugin-security fell open (#3597).\n context,\n });\n return rows as Record<string, unknown>[];\n };\n autoBridged = true;\n }\n\n // Auto-bridge raw SQL when the data engine exposes `execute()` and the\n // caller did not supply their own `executeRawSql`. This unlocks\n // NativeSQLStrategy (priority 10) which can emit `LEFT JOIN`s for\n // dotted dimension/measure references like `account.industry`.\n let executeRawSql = this.options.executeRawSql;\n let autoBridgedRawSql = false;\n if (!executeRawSql) {\n const tryGetExecutor = (): DataEngineLike | undefined => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n return svc && typeof svc.execute === 'function' ? svc : undefined;\n } catch {\n return undefined;\n }\n };\n // Always wire the bridge — resolution happens at call time, mirroring\n // the executeAggregate auto-bridge above. This way plugin-init order\n // does not matter as long as `data` exists by the time a query runs.\n executeRawSql = async (_objectName, sql, params) => {\n const engine = tryGetExecutor();\n if (!engine || !engine.execute) {\n throw new Error(\n '[Analytics] Cannot execute raw SQL: no IDataEngine (\"data\") service with execute() is registered.',\n );\n }\n // NativeSQLStrategy emits `$1, $2, …` placeholders. Knex (used by\n // driver-sql) speaks `?` placeholders, so translate.\n const knexSql = sql.replace(/\\$(\\d+)/g, '?');\n const result = await engine.execute(knexSql, { args: params });\n // A driver that cannot run SQL (e.g. the in-memory driver) returns\n // null from execute(). Silently mapping that to [] made EVERY dataset\n // query on such environments report \"No rows\" while looking healthy\n // (HTTP 200, compiled SQL attached). Throw a TYPED error instead so\n // the orchestrator can fall back to an aggregate-based strategy —\n // never fabricate an empty result.\n if (result === null || result === undefined) {\n const err = new Error(\n '[Analytics] The \"data\" engine\\'s driver returned null for raw SQL — ' +\n 'this driver does not support SQL execution. The query will fall back ' +\n 'to an aggregate-based strategy when one is available.',\n ) as Error & { code: string };\n err.code = 'RAW_SQL_UNSUPPORTED';\n throw err;\n }\n if (Array.isArray(result)) return result as Record<string, unknown>[];\n if (typeof result === 'object' && 'rows' in (result as Record<string, unknown>)) {\n return (result as { rows: Record<string, unknown>[] }).rows;\n }\n return [];\n };\n autoBridgedRawSql = true;\n }\n\n // Default capabilities: when we have an aggregate bridge, advertise\n // ObjectQL support so ObjectQLStrategy is selected. Callers can still\n // override via options.queryCapabilities.\n const queryCapabilities = this.options.queryCapabilities\n ?? (() => ({\n nativeSql: !!executeRawSql,\n objectqlAggregate: !!executeAggregate,\n inMemory: false,\n }));\n\n // ADR-0021 D-C — wire the read-scope provider. Prefer an explicit option;\n // otherwise auto-bridge to a registered `'security'` service that exposes\n // `getReadFilter(object, context)` (resolved at call time so plugin-init\n // order does not matter). This keeps analytics decoupled from security.\n interface SecurityReadFilter {\n getReadFilter(\n object: string,\n context?: ExecutionContext,\n ):\n | FilterCondition\n | null\n | undefined\n | Promise<FilterCondition | null | undefined>;\n }\n let getReadScope = this.options.getReadScope;\n let autoBridgedReadScope = false;\n let securityPresentAtInit = false;\n if (!getReadScope) {\n const trySecurity = (): SecurityReadFilter | undefined => {\n try {\n const svc = ctx.getService<SecurityReadFilter>('security');\n return svc && typeof svc.getReadFilter === 'function' ? svc : undefined;\n } catch {\n return undefined;\n }\n };\n // ALWAYS wire the bridge — resolution happens at call time, mirroring the\n // executeAggregate / executeRawSql auto-bridges above. Gating the\n // ASSIGNMENT on an init-time probe (as this did) made analytics RLS\n // silently plugin-ORDER-DEPENDENT: a kernel that registers this plugin\n // before the security plugin got NO read-scope provider at all, so every\n // strategy ran unscoped and only a WARN marked it. The repo's own\n // `bootStack` harness registers in exactly that order, which is why no\n // dogfood test could ever observe analytics RLS.\n securityPresentAtInit = !!trySecurity();\n getReadScope = (object, context) => trySecurity()?.getReadFilter(object, context);\n autoBridgedReadScope = true;\n }\n\n // ADR-0021 — relationship → target-object resolver. A dataset's `include`\n // names lookup/master_detail FIELDS on the base object; the joined TABLE is\n // each field's `reference` target (which can differ from the field name,\n // e.g. lookup `account` → object `crm_account`). Resolve from the 'data'\n // engine's object schema at compile time so cross-object joins target the\n // right table. Resolved lazily so plugin-init order doesn't matter.\n const relationshipResolver = (baseObject: string, relationshipName: string): string | undefined => {\n const engine = (() => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n return svc && typeof svc.getObject === 'function' ? svc : undefined;\n } catch { return undefined; }\n })();\n const obj = engine?.getObject?.(baseObject);\n const field = obj?.fields?.[relationshipName];\n if (field && (field.type === 'lookup' || field.type === 'master_detail') && field.reference) {\n return field.reference;\n }\n // Unknown to the schema — fall back to the relationship name as the table\n // (legacy same-name convention). Returning undefined would make the\n // compiler reject the dataset; the name-as-table fallback is safer for\n // engines that don't expose getObject.\n return engine ? undefined : relationshipName;\n };\n\n // ADR-0021 — dimension display-label resolution. `queryDataset` groups by a\n // dimension's raw stored value; for `select` fields the user-facing text is\n // the option label, and for `lookup`/`master_detail` fields it's the related\n // record's display name. Wire the two low-level capabilities the resolver\n // needs from the 'data' engine (resolved lazily so plugin-init order is free):\n // - field metadata (select options + lookup target), via getObject\n // - id→name pairs, via the executeAggregate bridge (group by id + name)\n const dataEngine = (): DataEngineLike | undefined => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n return svc && typeof svc.getObject === 'function' ? svc : undefined;\n } catch { return undefined; }\n };\n const labelResolver: DimensionLabelDeps = {\n getObjectFields: (objectName) => dataEngine()?.getObject?.(objectName)?.fields,\n fetchRecordLabels: async (targetObject, ids, scope, context) => {\n const map = new Map<unknown, string>();\n const displayField = pickDisplayField(dataEngine()?.getObject?.(targetObject)?.fields);\n if (!displayField || !executeAggregate || ids.length === 0) return map;\n // #3680 — the sort-key pass hands over the PRE-window id set (every\n // grouped value, not just the displayed page), so a high-cardinality\n // lookup dimension can push thousands of ids through here. Chunk the\n // `$in` so the bound-parameter count stays under every driver's limit\n // (SQLite's historic floor is 999 variables).\n const CHUNK = 500;\n for (let i = 0; i < ids.length; i += CHUNK) {\n // #3602 — AND the referenced object's own read scope into the id filter,\n // with `$and` (never key-merge) so it cannot be displaced by the id\n // predicate — the same composition the strategy uses for the aggregate.\n // Without it this per-record read leaks display names the target's RLS\n // would hide (fires when the referenced object is stricter than the base).\n const idFilter: Record<string, unknown> = { id: { $in: ids.slice(i, i + CHUNK) } };\n const filter = scope ? { $and: [idFilter, scope] } : idFilter;\n // Group by (id, displayField) — one row per record — reusing the aggregate\n // bridge rather than adding a record-fetch capability. A count keeps engines\n // that require ≥1 aggregation happy; the count itself is unused.\n const rows = await executeAggregate(targetObject, {\n groupBy: ['id', displayField],\n aggregations: [{ field: 'id', method: 'count', alias: '_c' }],\n filter,\n // #3602 second belt — `scope` above is the analytics layer's own\n // predicate on this per-record read; the context makes the engine's\n // middleware scope it as well.\n context,\n });\n for (const r of rows) {\n if (r.id != null && r[displayField] != null) map.set(r.id, String(r[displayField]));\n }\n }\n return map;\n },\n };\n\n // ADR-0037 P3 — draft data preview: resolve the PENDING seed draft's rows\n // for an object via the kernel protocol (state:'draft' read — a published\n // seed's rows are already in the real table and must NOT overlay). Lazy\n // service lookup so plugin order doesn't matter; null ⇒ no pending seed ⇒\n // queryDataset falls through to live data.\n const draftRowsResolver = async (objectName: string): Promise<Record<string, unknown>[] | null> => {\n type ProtocolLike = {\n getMetaItems?(req: { type: string; previewDrafts?: boolean }): Promise<unknown>;\n getMetaItem?(req: { type: string; name: string; state?: string }): Promise<unknown>;\n };\n let protocol: ProtocolLike | undefined;\n try {\n protocol = ctx.getService<ProtocolLike>('protocol');\n } catch { return null; }\n if (!protocol?.getMetaItems || !protocol.getMetaItem) return null;\n const res = await protocol.getMetaItems({ type: 'seed', previewDrafts: true }).catch(() => null);\n const list = Array.isArray(res)\n ? res\n : (res && typeof res === 'object' && Array.isArray((res as { items?: unknown[] }).items)\n ? (res as { items: unknown[] }).items\n : []);\n const rows: Record<string, unknown>[] = [];\n let pending = false;\n for (const entry of list) {\n const body = ((entry as { item?: unknown })?.item ?? entry) as { name?: string; object?: string } | null;\n if (!body?.name || body.object !== objectName) continue;\n // Only a PENDING draft row qualifies; getMetaItem({state:'draft'})\n // throws no_draft when the seed is already published.\n const draft = await protocol.getMetaItem({ type: 'seed', name: body.name, state: 'draft' }).catch(() => null);\n const draftBody = (draft as { item?: { records?: unknown[] } } | null)?.item;\n if (!draftBody) continue;\n pending = true;\n for (const r of Array.isArray(draftBody.records) ? draftBody.records : []) {\n if (r && typeof r === 'object') rows.push(r as Record<string, unknown>);\n }\n }\n return pending ? rows : null;\n };\n\n // Temporal storage-form coercion (fixes the SQLite datetime \"No rows\" bug).\n // The raw-SQL strategy binds dashboard relative-date tokens (already expanded\n // to ISO strings) directly, bypassing the driver's CRUD coercion. Delegate to\n // the driver — the single source of truth for the on-disk storage convention —\n // so a `Field.datetime` ISO comparand becomes epoch ms on SQLite, while\n // `Field.date` text and native-timestamp (Postgres) columns pass through\n // unchanged. Resolved at call time so plugin-init order does not matter.\n const coerceTemporalFilterValue = (\n objectName: string,\n fieldName: string,\n value: unknown,\n ): unknown => {\n try {\n const svc = ctx.getService<DataEngineLike>('data');\n const driver = svc?.getDriverForObject?.(objectName);\n if (driver && typeof driver.temporalFilterValue === 'function') {\n return driver.temporalFilterValue(objectName, fieldName, value);\n }\n } catch {\n // No data engine / driver, or it doesn't support coercion — leave the\n // value as-is (today's behaviour; safe for text/native-timestamp paths).\n }\n return value;\n };\n\n const config: AnalyticsServiceConfig = {\n cubes: this.options.cubes,\n logger: ctx.logger,\n queryCapabilities,\n executeRawSql,\n executeAggregate,\n fallbackService,\n getReadScope,\n getAllowedRelationships: this.options.getAllowedRelationships,\n coerceTemporalFilterValue,\n relationshipResolver,\n labelResolver,\n // ADR-0053 — source-field currency metadata for the measure currency chain.\n measureCurrency: (object: string, field: string) => {\n const f = dataEngine()?.getObject?.(object)?.fields?.[field] as\n | { type?: string; currencyConfig?: { defaultCurrency?: string } }\n | undefined;\n return f ? { type: f.type, defaultCurrency: f.currencyConfig?.defaultCurrency } : undefined;\n },\n // ADR-0062 D6 — a federated object carries an `external` block (ADR-0015).\n // Reported so NativeSQLStrategy declines it (its hand-compiled FROM would\n // hit the wrong physical table) and the driver-correct ObjectQL path runs.\n isExternalObject: (objectName: string) => {\n const obj = dataEngine()?.getObject?.(objectName);\n return !!(obj && obj.external != null);\n },\n // [#3867] Existence probe for the cube auto-inference gate. Reads the\n // same schema registry the data path's #3770 gate consults, through the\n // engine accessor this bridge already uses above — so \"which objects\n // exist\" has one answer across /data and /analytics.\n //\n // `dataEngine()` resolves lazily and may be absent entirely (analytics\n // installed without a data engine). Reporting `false` there would 404\n // every cube, so an unresolvable engine reports `true` — \"cannot answer,\n // do not block\" — mirroring the tiering #3770 took on the data path.\n isRegisteredObject: (name: string) => {\n const engine = dataEngine();\n if (!engine) return true;\n return engine.getObject?.(name) != null;\n },\n draftRowsResolver,\n };\n\n if (autoBridgedReadScope && securityPresentAtInit) {\n ctx.logger.info('[Analytics] Auto-bridged getReadScope → \"security\" service (getReadFilter)');\n } else if (autoBridgedReadScope) {\n // The bridge IS wired and will resolve at call time — this is only a\n // heads-up that security had not registered yet at our init. It becomes a\n // real problem only if no security service ever appears.\n ctx.logger.info(\n '[Analytics] getReadScope bridged to the \"security\" service; that service is not ' +\n 'registered yet at init and will be resolved per query (plugin order is not significant).',\n );\n } else if (!getReadScope) {\n ctx.logger.warn(\n '[Analytics] No getReadScope configured and no \"security\" service with getReadFilter found — ' +\n 'analytics queries will NOT enforce tenant/RLS scoping (ADR-0021 D-C). ' +\n 'Supply getReadScope or register a security service in multi-tenant deployments.',\n );\n }\n\n if (autoBridged) {\n ctx.logger.info('[Analytics] Auto-bridged executeAggregate → \"data\" service (IDataEngine)');\n }\n if (autoBridgedRawSql) {\n ctx.logger.info('[Analytics] Auto-bridged executeRawSql → \"data\" service (IDataEngine.execute)');\n }\n\n this.service = new AnalyticsService(config);\n\n // Register or replace the analytics service\n if (fallbackService) {\n ctx.replaceService('analytics', this.service);\n } else {\n ctx.registerService('analytics', this.service);\n }\n\n if (this.options.debug) {\n ctx.hook('analytics:beforeQuery', async (query: unknown) => {\n ctx.logger.debug('[Analytics] Before query', { query });\n });\n }\n\n ctx.logger.info('[Analytics] Service initialized');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n if (!this.service) return;\n\n // Notify other plugins that analytics is ready\n await ctx.trigger('analytics:ready', this.service);\n\n ctx.logger.info(\n `[Analytics] Service started with ${this.service.cubeRegistry.size} cubes: ` +\n `${this.service.cubeRegistry.names().join(', ') || '(none)'}`,\n );\n }\n\n async destroy(): Promise<void> {\n this.service = undefined;\n }\n}\n"],"mappings":"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","names":["import_core","n","y","import_core"]}
|