@objectstack/service-analytics 4.0.4 → 4.1.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 +439 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +439 -47
- package/dist/index.js.map +1 -1
- package/package.json +31 -6
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -65
- package/src/__tests__/analytics-service.test.ts +0 -469
- package/src/analytics-service.ts +0 -231
- package/src/cube-registry.ts +0 -147
- package/src/index.ts +0 -19
- package/src/plugin.ts +0 -133
- package/src/strategies/native-sql-strategy.ts +0 -184
- package/src/strategies/objectql-strategy.ts +0 -178
- package/src/strategies/types.ts +0 -11
- package/tsconfig.json +0 -17
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/analytics-service.ts","../src/cube-registry.ts","../src/strategies/native-sql-strategy.ts","../src/strategies/objectql-strategy.ts","../src/plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n// Core service\nexport { AnalyticsService } from './analytics-service.js';\nexport type { AnalyticsServiceConfig } from './analytics-service.js';\n\n// Kernel plugin\nexport { AnalyticsServicePlugin } from './plugin.js';\nexport type { AnalyticsServicePluginOptions } from './plugin.js';\n\n// Cube registry\nexport { CubeRegistry } from './cube-registry.js';\n\n// Strategies\nexport { NativeSQLStrategy } from './strategies/native-sql-strategy.js';\nexport { ObjectQLStrategy } from './strategies/objectql-strategy.js';\nexport type { AnalyticsStrategy, StrategyContext, DriverCapabilities } from './strategies/types.js';\n\n// Note: InMemoryStrategy is exported from @objectstack/driver-memory\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IAnalyticsService,\n AnalyticsQuery,\n AnalyticsResult,\n CubeMeta,\n} from '@objectstack/spec/contracts';\nimport type { Cube } from '@objectstack/spec/data';\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';\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\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 private readonly strategyCtx: StrategyContext;\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 // Build strategy context\n this.strategyCtx = {\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 };\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 * Execute an analytical query by delegating to the first capable strategy.\n */\n async query(query: AnalyticsQuery): Promise<AnalyticsResult> {\n if (!query.cube) {\n throw new Error('Cube name is required in analytics query');\n }\n\n const strategy = this.resolveStrategy(query);\n this.logger.debug(`[Analytics] Query on cube \"${query.cube}\" → ${strategy.name}`);\n\n return strategy.execute(query, this.strategyCtx);\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): Promise<{ sql: string; params: unknown[] }> {\n if (!query.cube) {\n throw new Error('Cube name is required for SQL generation');\n }\n\n const strategy = this.resolveStrategy(query);\n this.logger.debug(`[Analytics] generateSql on cube \"${query.cube}\" → ${strategy.name}`);\n\n return strategy.generateSql(query, this.strategyCtx);\n }\n\n // ── Internal ─────────────────────────────────────────────────────\n\n /**\n * Walk the strategy chain and return the first strategy that can handle the query.\n */\n private resolveStrategy(query: AnalyticsQuery): AnalyticsStrategy {\n for (const strategy of this.strategies) {\n if (strategy.canHandle(query, this.strategyCtx)) {\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(', ')}. ` +\n 'Ensure a compatible driver is configured or a fallback service is registered.',\n );\n }\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\nimport type { AnalyticsQuery, AnalyticsResult } from '@objectstack/spec/contracts';\nimport type { Cube } from '@objectstack/spec/data';\nimport type { AnalyticsStrategy, StrategyContext } from './types.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\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);\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);\n selectClauses.push(`${aggExpr} AS \"${measure}\"`);\n }\n }\n\n // Build WHERE clause\n const whereClauses: string[] = [];\n if (query.filters && query.filters.length > 0) {\n for (const filter of query.filters) {\n const colExpr = this.resolveFieldSql(cube, filter.member);\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);\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 const tableName = this.extractObjectName(cube);\n let sql = `SELECT ${selectClauses.join(', ')} FROM \"${tableName}\"`;\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 private resolveDimensionSql(cube: Cube, member: string): string {\n const fieldName = member.includes('.') ? member.split('.')[1] : member;\n const dim = cube.dimensions[fieldName];\n return dim ? dim.sql : fieldName;\n }\n\n private resolveMeasureSql(cube: Cube, member: string): string {\n const fieldName = member.includes('.') ? member.split('.')[1] : member;\n const measure = cube.measures[fieldName];\n if (!measure) return `COUNT(*)`;\n\n const col = measure.sql;\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(cube: Cube, member: string): string {\n const fieldName = member.includes('.') ? member.split('.')[1] : member;\n const dim = cube.dimensions[fieldName];\n if (dim) return dim.sql;\n const measure = cube.measures[fieldName];\n if (measure) return measure.sql;\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 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 params.push(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 fieldName = dim.includes('.') ? dim.split('.')[1] : dim;\n const d = cube.dimensions[fieldName];\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';\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\n const groupBy: string[] = [];\n if (query.dimensions && query.dimensions.length > 0) {\n for (const dim of query.dimensions) {\n groupBy.push(this.resolveFieldName(cube, dim, 'dimension'));\n }\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\n const filter: Record<string, unknown> = {};\n if (query.filters && query.filters.length > 0) {\n for (const f of query.filters) {\n const fieldName = this.resolveFieldName(cube, f.member, 'any');\n filter[fieldName] = this.convertFilter(f.operator, f.values);\n }\n }\n\n const rows = await ctx.executeAggregate!(objectName, {\n groupBy: groupBy.length > 0 ? groupBy : undefined,\n aggregations: aggregations.length > 0 ? aggregations : undefined,\n filter: Object.keys(filter).length > 0 ? filter : undefined,\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 private resolveFieldName(cube: Cube, member: string, kind: 'dimension' | 'measure' | 'any'): string {\n const fieldName = member.includes('.') ? member.split('.')[1] : member;\n if (kind === 'dimension' || kind === 'any') {\n const dim = cube.dimensions[fieldName];\n if (dim) return dim.sql.replace(/^\\$/, '');\n }\n if (kind === 'measure' || kind === 'any') {\n const measure = cube.measures[fieldName];\n if (measure) return measure.sql.replace(/^\\$/, '');\n }\n return fieldName;\n }\n\n private resolveMeasureAggregation(cube: Cube, measureName: string): { field: string; method: string } {\n const fieldName = measureName.includes('.') ? measureName.split('.')[1] : measureName;\n const measure = cube.measures[fieldName];\n if (!measure) return { field: '*', method: 'count' };\n return {\n field: measure.sql.replace(/^\\$/, ''),\n method: measure.type === 'count_distinct' ? 'count_distinct' : measure.type,\n };\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 switch (operator) {\n case 'equals': return values[0];\n case 'notEquals': return { $ne: values[0] };\n case 'gt': return { $gt: values[0] };\n case 'gte': return { $gte: values[0] };\n case 'lt': return { $lt: values[0] };\n case 'lte': return { $lte: values[0] };\n case 'contains': return { $regex: values[0] };\n default: return values[0];\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 fieldName = dim.includes('.') ? dim.split('.')[1] : dim;\n const d = cube.dimensions[fieldName];\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 { Plugin, PluginContext } from '@objectstack/core';\nimport type { Cube } from '@objectstack/spec/data';\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';\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 }) => Promise<Record<string, unknown>[]>;\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 const config: AnalyticsServiceConfig = {\n cubes: this.options.cubes,\n logger: ctx.logger,\n queryCapabilities: this.options.queryCapabilities,\n executeRawSql: this.options.executeRawSql,\n executeAggregate: this.options.executeAggregate,\n fallbackService,\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/index.ts","../src/analytics-service.ts","../src/cube-registry.ts","../src/strategies/filter-normalizer.ts","../src/strategies/native-sql-strategy.ts","../src/strategies/objectql-strategy.ts","../src/plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n// Core service\nexport { AnalyticsService } from './analytics-service.js';\nexport type { AnalyticsServiceConfig } from './analytics-service.js';\n\n// Kernel plugin\nexport { AnalyticsServicePlugin } from './plugin.js';\nexport type { AnalyticsServicePluginOptions } from './plugin.js';\n\n// Cube registry\nexport { CubeRegistry } from './cube-registry.js';\n\n// Strategies\nexport { NativeSQLStrategy } from './strategies/native-sql-strategy.js';\nexport { ObjectQLStrategy } from './strategies/objectql-strategy.js';\nexport type { AnalyticsStrategy, StrategyContext, DriverCapabilities } from './strategies/types.js';\n\n// Note: InMemoryStrategy is exported from @objectstack/driver-memory\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IAnalyticsService,\n AnalyticsQuery,\n AnalyticsResult,\n CubeMeta,\n} from '@objectstack/spec/contracts';\nimport type { Cube } from '@objectstack/spec/data';\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';\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\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 private readonly strategyCtx: StrategyContext;\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 // Build strategy context\n this.strategyCtx = {\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 };\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 * Execute an analytical query by delegating to the first capable strategy.\n */\n async query(query: AnalyticsQuery): 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 strategy = this.resolveStrategy(query);\n this.logger.debug(`[Analytics] Query on cube \"${query.cube}\" → ${strategy.name}`);\n\n return strategy.execute(query, this.strategyCtx);\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): 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 strategy = this.resolveStrategy(query);\n this.logger.debug(`[Analytics] generateSql on cube \"${query.cube}\" → ${strategy.name}`);\n\n return strategy.generateSql(query, this.strategyCtx);\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 query.\n */\n private resolveStrategy(query: AnalyticsQuery): AnalyticsStrategy {\n for (const strategy of this.strategies) {\n if (strategy.canHandle(query, this.strategyCtx)) {\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(', ')}. ` +\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) 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 * 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 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 * 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 ): 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 joins.set(\n alias,\n `LEFT JOIN \"${alias}\" 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);\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);\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);\n const measure = this.lookupMember(cube, member, 'measure');\n if (measure) return this.qualifyAndRegisterJoin(measure.sql, parentTable, joins);\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\n const groupBy: string[] = [];\n if (query.dimensions && query.dimensions.length > 0) {\n for (const dim of query.dimensions) {\n groupBy.push(this.resolveFieldName(cube, dim, 'dimension'));\n }\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\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 filter[fieldName] = this.convertFilter(f.operator, f.values);\n }\n }\n\n const rows = await ctx.executeAggregate!(objectName, {\n groupBy: groupBy.length > 0 ? groupBy : undefined,\n aggregations: aggregations.length > 0 ? aggregations : undefined,\n filter: Object.keys(filter).length > 0 ? filter : undefined,\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) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { Cube } from '@objectstack/spec/data';\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';\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 }): Promise<unknown[]>;\n execute?(command: unknown, options?: Record<string, unknown>): Promise<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 }) => Promise<Record<string, unknown>[]>;\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 }) => {\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 });\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 if (Array.isArray(result)) return result as Record<string, unknown>[];\n if (result && 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 const config: AnalyticsServiceConfig = {\n cubes: this.options.cubes,\n logger: ctx.logger,\n queryCapabilities,\n executeRawSql,\n executeAggregate,\n fallbackService,\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":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -133,6 +133,21 @@ declare class AnalyticsService implements IAnalyticsService {
|
|
|
133
133
|
sql: string;
|
|
134
134
|
params: unknown[];
|
|
135
135
|
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Ensure a cube exists for the given query and that it knows about every
|
|
138
|
+
* measure referenced by the query.
|
|
139
|
+
*
|
|
140
|
+
* - If no cube is registered for `query.cube`, infer a minimal cube from
|
|
141
|
+
* the query so downstream strategies (which assume `cube.sql` exists)
|
|
142
|
+
* don't crash.
|
|
143
|
+
* - If a cube exists but the query references measures that aren't in
|
|
144
|
+
* `cube.measures` (e.g. `amount_sum`, `amount_avg` emitted by dashboard
|
|
145
|
+
* widget translators), inject suffix-inferred Metric entries so the
|
|
146
|
+
* strategies pick the right aggregation function and field.
|
|
147
|
+
*/
|
|
148
|
+
private ensureCube;
|
|
149
|
+
/** Build a minimal Cube from the fields referenced by an AnalyticsQuery. */
|
|
150
|
+
private inferCubeFromQuery;
|
|
136
151
|
/**
|
|
137
152
|
* Walk the strategy chain and return the first strategy that can handle the query.
|
|
138
153
|
*/
|
|
@@ -226,6 +241,39 @@ declare class NativeSQLStrategy implements AnalyticsStrategy {
|
|
|
226
241
|
sql: string;
|
|
227
242
|
params: unknown[];
|
|
228
243
|
}>;
|
|
244
|
+
/**
|
|
245
|
+
* Resolve a dimension/measure/filter SQL expression that may reference a
|
|
246
|
+
* related table via dot notation (e.g. `account.industry`).
|
|
247
|
+
*
|
|
248
|
+
* When the resolved `sql` contains a dot, treat the prefix as a lookup
|
|
249
|
+
* field on the cube's table and synthesise a `LEFT JOIN` against the
|
|
250
|
+
* related table. The convention (matching the auto-cube generator and
|
|
251
|
+
* ObjectStack object schemas) is:
|
|
252
|
+
*
|
|
253
|
+
* <parentTable>.<lookupField> = <lookupField>.id
|
|
254
|
+
*
|
|
255
|
+
* i.e. the lookup field name on the parent table equals the related
|
|
256
|
+
* table name. This holds for all `Field.lookup({ object: '...' })`
|
|
257
|
+
* declarations where the field is named after its target object.
|
|
258
|
+
*
|
|
259
|
+
* Returns the qualified SQL reference (e.g. `"account"."industry"`).
|
|
260
|
+
* Pure column references (no dot) are returned as-is.
|
|
261
|
+
*/
|
|
262
|
+
private qualifyAndRegisterJoin;
|
|
263
|
+
/**
|
|
264
|
+
* Resolve a member reference (dimension, measure, or filter field) to its
|
|
265
|
+
* cube definition.
|
|
266
|
+
*
|
|
267
|
+
* Accepts three naming conventions:
|
|
268
|
+
* 1. `<cube>.<field>` — the canonical analytics qualifier (stripped to `<field>`).
|
|
269
|
+
* 2. `<lookup>.<field>` — a relation traversal (e.g. `account.industry`).
|
|
270
|
+
* First tried as the literal key, then as the underscore-flattened
|
|
271
|
+
* key (`account_industry`), and finally returned as a synthetic
|
|
272
|
+
* definition whose `sql` is the dotted reference so the JOIN
|
|
273
|
+
* machinery can pick it up.
|
|
274
|
+
* 3. `<field>` — a bare field name on the cube's table.
|
|
275
|
+
*/
|
|
276
|
+
private lookupMember;
|
|
229
277
|
private resolveDimensionSql;
|
|
230
278
|
private resolveMeasureSql;
|
|
231
279
|
private resolveFieldSql;
|
|
@@ -250,6 +298,19 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
|
|
|
250
298
|
sql: string;
|
|
251
299
|
params: unknown[];
|
|
252
300
|
}>;
|
|
301
|
+
/**
|
|
302
|
+
* Resolve a member ref to a `{ sql, type? }` definition.
|
|
303
|
+
*
|
|
304
|
+
* Mirrors `NativeSQLStrategy.lookupMember` so the two strategies
|
|
305
|
+
* accept the same naming conventions:
|
|
306
|
+
* 1. `<cube>.<field>` — canonical analytics qualifier.
|
|
307
|
+
* 2. `<lookup>.<field>` — relation traversal (e.g. `account.industry`).
|
|
308
|
+
* Tries literal key, then underscore-flattened key, then falls
|
|
309
|
+
* back to a synthetic dim whose `sql` is the dotted path so the
|
|
310
|
+
* ObjectQL aggregate engine can traverse it via the lookup field.
|
|
311
|
+
* 3. `<field>` — bare column on the cube's table.
|
|
312
|
+
*/
|
|
313
|
+
private lookupMember;
|
|
253
314
|
private resolveFieldName;
|
|
254
315
|
private resolveMeasureAggregation;
|
|
255
316
|
private convertFilter;
|
package/dist/index.d.ts
CHANGED
|
@@ -133,6 +133,21 @@ declare class AnalyticsService implements IAnalyticsService {
|
|
|
133
133
|
sql: string;
|
|
134
134
|
params: unknown[];
|
|
135
135
|
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Ensure a cube exists for the given query and that it knows about every
|
|
138
|
+
* measure referenced by the query.
|
|
139
|
+
*
|
|
140
|
+
* - If no cube is registered for `query.cube`, infer a minimal cube from
|
|
141
|
+
* the query so downstream strategies (which assume `cube.sql` exists)
|
|
142
|
+
* don't crash.
|
|
143
|
+
* - If a cube exists but the query references measures that aren't in
|
|
144
|
+
* `cube.measures` (e.g. `amount_sum`, `amount_avg` emitted by dashboard
|
|
145
|
+
* widget translators), inject suffix-inferred Metric entries so the
|
|
146
|
+
* strategies pick the right aggregation function and field.
|
|
147
|
+
*/
|
|
148
|
+
private ensureCube;
|
|
149
|
+
/** Build a minimal Cube from the fields referenced by an AnalyticsQuery. */
|
|
150
|
+
private inferCubeFromQuery;
|
|
136
151
|
/**
|
|
137
152
|
* Walk the strategy chain and return the first strategy that can handle the query.
|
|
138
153
|
*/
|
|
@@ -226,6 +241,39 @@ declare class NativeSQLStrategy implements AnalyticsStrategy {
|
|
|
226
241
|
sql: string;
|
|
227
242
|
params: unknown[];
|
|
228
243
|
}>;
|
|
244
|
+
/**
|
|
245
|
+
* Resolve a dimension/measure/filter SQL expression that may reference a
|
|
246
|
+
* related table via dot notation (e.g. `account.industry`).
|
|
247
|
+
*
|
|
248
|
+
* When the resolved `sql` contains a dot, treat the prefix as a lookup
|
|
249
|
+
* field on the cube's table and synthesise a `LEFT JOIN` against the
|
|
250
|
+
* related table. The convention (matching the auto-cube generator and
|
|
251
|
+
* ObjectStack object schemas) is:
|
|
252
|
+
*
|
|
253
|
+
* <parentTable>.<lookupField> = <lookupField>.id
|
|
254
|
+
*
|
|
255
|
+
* i.e. the lookup field name on the parent table equals the related
|
|
256
|
+
* table name. This holds for all `Field.lookup({ object: '...' })`
|
|
257
|
+
* declarations where the field is named after its target object.
|
|
258
|
+
*
|
|
259
|
+
* Returns the qualified SQL reference (e.g. `"account"."industry"`).
|
|
260
|
+
* Pure column references (no dot) are returned as-is.
|
|
261
|
+
*/
|
|
262
|
+
private qualifyAndRegisterJoin;
|
|
263
|
+
/**
|
|
264
|
+
* Resolve a member reference (dimension, measure, or filter field) to its
|
|
265
|
+
* cube definition.
|
|
266
|
+
*
|
|
267
|
+
* Accepts three naming conventions:
|
|
268
|
+
* 1. `<cube>.<field>` — the canonical analytics qualifier (stripped to `<field>`).
|
|
269
|
+
* 2. `<lookup>.<field>` — a relation traversal (e.g. `account.industry`).
|
|
270
|
+
* First tried as the literal key, then as the underscore-flattened
|
|
271
|
+
* key (`account_industry`), and finally returned as a synthetic
|
|
272
|
+
* definition whose `sql` is the dotted reference so the JOIN
|
|
273
|
+
* machinery can pick it up.
|
|
274
|
+
* 3. `<field>` — a bare field name on the cube's table.
|
|
275
|
+
*/
|
|
276
|
+
private lookupMember;
|
|
229
277
|
private resolveDimensionSql;
|
|
230
278
|
private resolveMeasureSql;
|
|
231
279
|
private resolveFieldSql;
|
|
@@ -250,6 +298,19 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
|
|
|
250
298
|
sql: string;
|
|
251
299
|
params: unknown[];
|
|
252
300
|
}>;
|
|
301
|
+
/**
|
|
302
|
+
* Resolve a member ref to a `{ sql, type? }` definition.
|
|
303
|
+
*
|
|
304
|
+
* Mirrors `NativeSQLStrategy.lookupMember` so the two strategies
|
|
305
|
+
* accept the same naming conventions:
|
|
306
|
+
* 1. `<cube>.<field>` — canonical analytics qualifier.
|
|
307
|
+
* 2. `<lookup>.<field>` — relation traversal (e.g. `account.industry`).
|
|
308
|
+
* Tries literal key, then underscore-flattened key, then falls
|
|
309
|
+
* back to a synthetic dim whose `sql` is the dotted path so the
|
|
310
|
+
* ObjectQL aggregate engine can traverse it via the lookup field.
|
|
311
|
+
* 3. `<field>` — bare column on the cube's table.
|
|
312
|
+
*/
|
|
313
|
+
private lookupMember;
|
|
253
314
|
private resolveFieldName;
|
|
254
315
|
private resolveMeasureAggregation;
|
|
255
316
|
private convertFilter;
|