@objectstack/service-analytics 9.9.0 → 9.10.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 +66 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +66 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../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\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 } 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 * 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 * 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 /** 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 /** 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.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 };\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 // 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 && selection.dimensions?.length) {\n const dims = selection.dimensions\n .map((name) => dataset.dimensions?.find((d) => d.name === name))\n .filter((d): d is NonNullable<typeof 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 }\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 this.logger.warn(\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 );\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/** Stringify a filter value as the internal pipeline requires `values: string[]`. */\nfunction stringifyForCube(v: unknown): string {\n if (v == null) return '';\n if (typeof v === 'boolean') return v ? '1' : '0';\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/**\n * Coerce a stringified filter value back into a runtime type for SQL\n * parameter binding. Better-sqlite3 (and most drivers) bind JS\n * booleans/numbers as their native SQL types, so we recover them here\n * to avoid string-vs-number mismatches against typed 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 if (/^-?\\d+$/.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) return n;\n }\n if (/^-?\\d+\\.\\d+$/.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) return n;\n }\n return 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 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 const clause = this.buildFilterClause(colExpr, filter.operator, filter.values, params);\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 params.push(range[0], range[1]);\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 /**\n * Resolve a dimension/measure/filter SQL expression that may reference a\n * related table via dot notation (e.g. `account.industry`).\n *\n * When the resolved `sql` contains a dot, treat the prefix as a lookup\n * field on the cube's table and synthesise a `LEFT JOIN` against the\n * related table. The convention (matching the auto-cube generator and\n * ObjectStack object schemas) 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('.')) return rawSql;\n // Only the first dotted hop is supported (single-level relation).\n const [alias, ...rest] = rawSql.split('.');\n if (!alias || rest.length === 0) return rawSql;\n const column = rest.join('.');\n if (!joins.has(alias)) {\n // The relationship name is the join ALIAS; the joined TABLE is the\n // related object. For datasets these differ when objects are namespaced\n // (lookup `account` → table `crm_account`), so resolve the table from the\n // Cube's `joins` map (emitted by the dataset compiler). Fall back to the\n // alias 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 \"${parentTable}\".\"${alias}\" = \"${alias}\".\"id\"`,\n );\n }\n return `\"${alias}\".\"${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 private buildFilterClause(col: string, operator: string, values: string[] | undefined, params: unknown[]): 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 const placeholders = values.map(v => { params.push(coerceFilterValueForSql(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\n // (avoids '1' (text) vs 1 (integer) mismatches against typed\n // boolean columns under SQLite/Postgres).\n params.push(coerceFilterValueForSql(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, coerceFilterValueForSql } 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 = coerceFilterValueForSql(values[0]);\n const all = values.map(coerceFilterValueForSql);\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 * Relationship names declared in `include`. The join allowlist (D-C):\n * the 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 * Resolves a relationship name on a base object to the related object/table\n * name, using the runtime's object graph. Optional: when omitted the compiler\n * trusts the declared `include` names (the NativeSQLStrategy convention assumes\n * the relationship name equals the related table name).\n */\nexport type RelationshipResolver = (\n baseObject: string,\n relationshipName: string,\n) => string | undefined;\n\n/** Map a dataset measure's aggregate to the Cube metric `type`. */\nfunction aggregateToMetricType(m: DatasetMeasure): Metric['type'] {\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 prefix of a dotted `relationship.field` path, or null. */\nfunction relationshipPrefix(field: string): string | null {\n const idx = field.indexOf('.');\n return idx > 0 ? field.slice(0, idx) : null;\n}\n\nexport function compileDataset(\n dataset: Dataset,\n resolver?: RelationshipResolver,\n): CompiledDataset {\n const include = dataset.include ?? [];\n const allowedRelationships = new Set(include);\n\n // Resolve each declared relationship to its TARGET TABLE and emit a Cube join.\n // The relationship name (a lookup/master_detail field on the base object) is\n // used as the join ALIAS, but the joined TABLE is the related object — these\n // differ when objects are namespaced (e.g. lookup field `account` →\n // table `crm_account`). Without resolving the table, the strategy would join a\n // non-existent `\"account\"` table. When no resolver is supplied the relationship\n // name is assumed to equal the table name (legacy convention / unit tests).\n const joins: Record<string, CubeJoin> = {};\n for (const rel of include) {\n let targetTable: string = rel;\n if (resolver) {\n const resolved = resolver(dataset.object, rel);\n if (!resolved) {\n throw new Error(\n `[dataset-compiler] dataset \"${dataset.name}\" includes relationship \"${rel}\" ` +\n `which does not exist on object \"${dataset.object}\".`,\n );\n }\n targetTable = resolved;\n }\n // `name` carries the join TABLE; the strategy derives the ON clause from the\n // relationship-name convention (`<base>.<rel> = <rel>.id`).\n joins[rel] = {\n name: targetTable,\n relationship: 'many_to_one',\n sql: `${dataset.object}.${rel} = ${rel}.id`,\n };\n }\n\n // Assert any dotted field only traverses a DECLARED relationship (D-C).\n const assertDeclared = (field: string, ownerKind: string, ownerName: string) => {\n const prefix = relationshipPrefix(field);\n if (prefix && !allowedRelationships.has(prefix)) {\n throw new Error(\n `[dataset-compiler] ${ownerKind} \"${ownerName}\" references relationship \"${prefix}\" ` +\n `via \"${field}\", but \"${prefix}\" is not declared in the dataset's \\`include\\`. ` +\n `v1 only joins along declared relationships.`,\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 } | undefined;\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 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 relationshipResolver,\n labelResolver,\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":[]}
|
|
1
|
+
{"version":3,"sources":["../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\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 } 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 * 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-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 /** 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 /** 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.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 };\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 // 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 && selection.dimensions?.length) {\n const dims = selection.dimensions\n .map((name) => dataset.dimensions?.find((d) => d.name === name))\n .filter((d): d is NonNullable<typeof 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 }\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 this.logger.warn(\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 );\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/** Stringify a filter value as the internal pipeline requires `values: string[]`. */\nfunction stringifyForCube(v: unknown): string {\n if (v == null) return '';\n if (typeof v === 'boolean') return v ? '1' : '0';\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/**\n * Coerce a stringified filter value back into a runtime type for SQL\n * parameter binding. Better-sqlite3 (and most drivers) bind JS\n * booleans/numbers as their native SQL types, so we recover them here\n * to avoid string-vs-number mismatches against typed 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 if (/^-?\\d+$/.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) return n;\n }\n if (/^-?\\d+\\.\\d+$/.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) return n;\n }\n return 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 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 /**\n * Resolve a dimension/measure/filter SQL expression that may reference a\n * related table via dot notation (e.g. `account.industry`).\n *\n * When the resolved `sql` contains a dot, treat the prefix as a lookup\n * field on the cube's table and synthesise a `LEFT JOIN` against the\n * related table. The convention (matching the auto-cube generator and\n * ObjectStack object schemas) 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('.')) return rawSql;\n // Only the first dotted hop is supported (single-level relation).\n const [alias, ...rest] = rawSql.split('.');\n if (!alias || rest.length === 0) return rawSql;\n const column = rest.join('.');\n if (!joins.has(alias)) {\n // The relationship name is the join ALIAS; the joined TABLE is the\n // related object. For datasets these differ when objects are namespaced\n // (lookup `account` → table `crm_account`), so resolve the table from the\n // Cube's `joins` map (emitted by the dataset compiler). Fall back to the\n // alias 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 \"${parentTable}\".\"${alias}\" = \"${alias}\".\"id\"`,\n );\n }\n return `\"${alias}\".\"${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 const [alias, ...rest] = rawSql.split('.');\n const object = cube.joins?.[alias]?.name ?? alias;\n return { object, field: rest.join('.') };\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, coerceFilterValueForSql } 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 = coerceFilterValueForSql(values[0]);\n const all = values.map(coerceFilterValueForSql);\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 * Relationship names declared in `include`. The join allowlist (D-C):\n * the 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 * Resolves a relationship name on a base object to the related object/table\n * name, using the runtime's object graph. Optional: when omitted the compiler\n * trusts the declared `include` names (the NativeSQLStrategy convention assumes\n * the relationship name equals the related table name).\n */\nexport type RelationshipResolver = (\n baseObject: string,\n relationshipName: string,\n) => string | undefined;\n\n/** Map a dataset measure's aggregate to the Cube metric `type`. */\nfunction aggregateToMetricType(m: DatasetMeasure): Metric['type'] {\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 prefix of a dotted `relationship.field` path, or null. */\nfunction relationshipPrefix(field: string): string | null {\n const idx = field.indexOf('.');\n return idx > 0 ? field.slice(0, idx) : null;\n}\n\nexport function compileDataset(\n dataset: Dataset,\n resolver?: RelationshipResolver,\n): CompiledDataset {\n const include = dataset.include ?? [];\n const allowedRelationships = new Set(include);\n\n // Resolve each declared relationship to its TARGET TABLE and emit a Cube join.\n // The relationship name (a lookup/master_detail field on the base object) is\n // used as the join ALIAS, but the joined TABLE is the related object — these\n // differ when objects are namespaced (e.g. lookup field `account` →\n // table `crm_account`). Without resolving the table, the strategy would join a\n // non-existent `\"account\"` table. When no resolver is supplied the relationship\n // name is assumed to equal the table name (legacy convention / unit tests).\n const joins: Record<string, CubeJoin> = {};\n for (const rel of include) {\n let targetTable: string = rel;\n if (resolver) {\n const resolved = resolver(dataset.object, rel);\n if (!resolved) {\n throw new Error(\n `[dataset-compiler] dataset \"${dataset.name}\" includes relationship \"${rel}\" ` +\n `which does not exist on object \"${dataset.object}\".`,\n );\n }\n targetTable = resolved;\n }\n // `name` carries the join TABLE; the strategy derives the ON clause from the\n // relationship-name convention (`<base>.<rel> = <rel>.id`).\n joins[rel] = {\n name: targetTable,\n relationship: 'many_to_one',\n sql: `${dataset.object}.${rel} = ${rel}.id`,\n };\n }\n\n // Assert any dotted field only traverses a DECLARED relationship (D-C).\n const assertDeclared = (field: string, ownerKind: string, ownerName: string) => {\n const prefix = relationshipPrefix(field);\n if (prefix && !allowedRelationships.has(prefix)) {\n throw new Error(\n `[dataset-compiler] ${ownerKind} \"${ownerName}\" references relationship \"${prefix}\" ` +\n `via \"${field}\", but \"${prefix}\" is not declared in the dataset's \\`include\\`. ` +\n `v1 only joins along declared relationships.`,\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 } | 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 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":[]}
|