UNPKG

drizzle-cube

Version:

Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.

1,288 lines (1,218 loc) 48 kB
import { AnyColumn } from 'drizzle-orm'; import { SQL } from 'drizzle-orm'; /** * Abstract base class for database executors */ export declare abstract class BaseDatabaseExecutor<TSchema extends Record<string, any> = Record<string, any>> implements DatabaseExecutor<TSchema> { db: DrizzleDatabase<TSchema>; schema?: TSchema | undefined; databaseAdapter: DatabaseAdapter; constructor(db: DrizzleDatabase<TSchema>, schema?: TSchema | undefined, engineType?: 'postgres' | 'mysql' | 'sqlite'); abstract execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T>; abstract getEngineType(): 'postgres' | 'mysql' | 'sqlite'; } /** * Base query definition that can be extended dynamically * Returns just the FROM/JOIN/WHERE setup, not a complete SELECT */ export declare interface BaseQueryDefinition { /** Main table to query from */ from: any; /** Optional joins to other tables */ joins?: Array<{ table: any; on: SQL; type?: 'left' | 'right' | 'inner' | 'full'; }>; /** Base WHERE conditions (typically security context filtering) */ where?: SQL; } /** * Compiled cube with execution function */ export declare interface CompiledCube<TSchema extends Record<string, any> = Record<string, any>> extends Cube<TSchema> { /** Execute a query against this cube */ queryFn: (query: SemanticQuery, securityContext: SecurityContext) => Promise<QueryResult>; } /** * Auto-detect database type and create appropriate executor * @param db - Drizzle database instance * @param schema - Optional schema for type inference * @param engineType - Optional explicit engine type override * @returns Appropriate database executor */ export declare function createDatabaseExecutor<TSchema extends Record<string, any>>(db: DrizzleDatabase<TSchema>, schema?: TSchema, engineType?: 'postgres' | 'mysql' | 'sqlite'): DatabaseExecutor<TSchema>; /** * Create a new semantic layer instance with Drizzle integration * Use this when you need multiple isolated instances */ export declare function createDrizzleSemanticLayer<TSchema extends Record<string, any>>(options: { drizzle: DrizzleDatabase<TSchema>; schema?: TSchema; }): SemanticLayerCompiler<TSchema>; /** * Helper to create multi-cube query context */ export declare function createMultiCubeContext<TSchema extends Record<string, any>>(baseContext: QueryContext<TSchema>, cubes: Map<string, Cube<TSchema>>, currentCube: Cube<TSchema>): MultiCubeQueryContext<TSchema>; export declare function createMySQLExecutor<TSchema extends Record<string, any>>(db: DrizzleDatabase<TSchema>, schema?: TSchema): MySQLExecutor<TSchema>; /** * Factory functions for creating database executors */ export declare function createPostgresExecutor<TSchema extends Record<string, any>>(db: DrizzleDatabase<TSchema>, schema?: TSchema): PostgresExecutor<TSchema>; /** * Create a new semantic layer compiler with type inference */ export declare function createSemanticLayer<TSchema extends Record<string, any>>(options?: { drizzle?: DatabaseExecutor<TSchema>['db']; schema?: TSchema; databaseExecutor?: DatabaseExecutor<TSchema>; engineType?: 'postgres' | 'mysql' | 'sqlite'; }): SemanticLayerCompiler<TSchema>; export declare function createSQLiteExecutor<TSchema extends Record<string, any>>(db: DrizzleDatabase<TSchema>, schema?: TSchema): SQLiteExecutor<TSchema>; /** * Cube definition focused on dynamic query building */ export declare interface Cube<TSchema extends Record<string, any> = Record<string, any>> { name: string; title?: string; description?: string; /** * Base query setup - returns the foundation that can be extended * Should return FROM/JOIN/WHERE setup, NOT a complete SELECT */ sql: (ctx: QueryContext<TSchema>) => BaseQueryDefinition; /** Cube dimensions using direct column references */ dimensions: Record<string, Dimension<TSchema>>; /** Cube measures using direct column references */ measures: Record<string, Measure<TSchema>>; /** Optional joins to other cubes for multi-cube queries */ joins?: Record<string, CubeJoin<TSchema>>; } /** * Helper type for creating type-safe cubes */ export declare interface CubeDefiner<TSchema extends Record<string, any>> { <TName extends string>(name: TName, definition: CubeDefinition<TSchema>): SemanticCube<TSchema> & { name: TName; }; } /** * Utility type for cube definition with schema inference */ export declare type CubeDefinition<TSchema extends Record<string, any>> = Omit<SemanticCube<TSchema>, 'name'> & { name?: string; }; /** * Type-safe cube join definition with lazy loading support */ export declare interface CubeJoin<TSchema extends Record<string, any> = Record<string, any>> { /** Target cube reference - lazy loaded to avoid circular dependencies */ targetCube: Cube<TSchema> | (() => Cube<TSchema>); /** Semantic relationship - determines join behavior */ relationship: 'belongsTo' | 'hasOne' | 'hasMany'; /** Array of join conditions - supports multi-column joins */ on: Array<{ /** Column from source cube */ source: AnyColumn; /** Column from target cube */ target: AnyColumn; /** Comparison operator - defaults to eq */ as?: (source: AnyColumn, target: AnyColumn) => SQL; }>; /** Override default SQL join type (derived from relationship) */ sqlJoinType?: 'inner' | 'left' | 'right' | 'full'; } /** * Cube metadata for API responses */ export declare interface CubeMetadata { name: string; title: string; description?: string; measures: MeasureMetadata[]; dimensions: DimensionMetadata[]; segments: any[]; } declare interface DatabaseAdapter { /** * Get the database engine type this adapter supports */ getEngineType(): 'postgres' | 'mysql' | 'sqlite'; /** * Build time dimension expression with granularity truncation * @param granularity - Time granularity (day, month, year, etc.) * @param fieldExpr - The date/timestamp field expression * @returns SQL expression for truncated time dimension */ buildTimeDimension(granularity: TimeGranularity, fieldExpr: AnyColumn | SQL): SQL; /** * Build case-insensitive string matching condition * @param fieldExpr - The field to search in * @param operator - The string matching operator * @param value - The value to match * @returns SQL expression for string matching */ buildStringCondition(fieldExpr: AnyColumn | SQL, operator: 'contains' | 'notContains' | 'startsWith' | 'endsWith', value: string): SQL; /** * Cast expression to specific database type * @param fieldExpr - The field expression to cast * @param targetType - Target database type * @returns SQL expression with type casting */ castToType(fieldExpr: AnyColumn | SQL, targetType: 'timestamp' | 'decimal' | 'integer'): SQL; /** * Build AVG aggregation expression with database-specific null handling * @param fieldExpr - The field expression to average * @returns SQL expression for AVG aggregation (COALESCE vs IFNULL for null handling) */ buildAvg(fieldExpr: AnyColumn | SQL): SQL; /** * Build CASE WHEN conditional expression * @param conditions - Array of condition/result pairs * @param elseValue - Optional ELSE clause value * @returns SQL expression for CASE WHEN statement */ buildCaseWhen(conditions: Array<{ when: SQL; then: any; }>, elseValue?: any): SQL; /** * Build boolean literal expression * @param value - Boolean value to represent * @returns SQL expression for boolean literal (TRUE/FALSE/1/0 depending on database) */ buildBooleanLiteral(value: boolean): SQL; /** * Convert filter values to database-compatible types * @param value - The filter value to convert * @returns Converted value for database queries */ convertFilterValue(value: any): any; /** * Prepare date value for database-specific storage format * @param date - Date value to prepare * @returns Database-compatible date representation */ prepareDateValue(date: Date): any; /** * Check if this database stores timestamps as integers * @returns True if timestamps are stored as integers (milliseconds), false for native timestamps */ isTimestampInteger(): boolean; /** * Convert time dimension result values back to Date objects for consistency * @param value - The time dimension value from query results * @returns Date object or original value if not a time dimension */ convertTimeDimensionResult(value: any): any; } /** * Database executor that wraps Drizzle ORM * Provides type-safe SQL execution with engine-specific implementations */ export declare interface DatabaseExecutor<TSchema extends Record<string, any> = Record<string, any>> { /** The Drizzle database instance */ db: DrizzleDatabase<TSchema>; /** Optional schema for type inference */ schema?: TSchema; /** Database adapter for SQL dialect-specific operations */ databaseAdapter: DatabaseAdapter; /** Execute a Drizzle SQL query or query object */ execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T>; /** Get the database engine type */ getEngineType(): 'postgres' | 'mysql' | 'sqlite'; } /** * Main semantic layer instance * Use this for simple single-instance usage */ export declare const defaultSemanticLayer: SemanticLayerCompiler<Record<string, any>>; /** * Helper function to create cubes */ export declare function defineCube<TSchema extends Record<string, any>>(name: string, definition: Omit<Cube<TSchema>, 'name'>): Cube<TSchema>; /** * Create a type-safe cube definition with schema inference * @param _schema - Drizzle schema containing table definitions (used for type inference only) * @param definition - Cube definition with type inference * @returns Type-safe semantic cube */ export declare function defineLegacyCube<TSchema extends Record<string, any>>(_schema: TSchema, definition: CubeDefinition<TSchema> & { name: string; }): SemanticCube<TSchema>; /** * Dimension definition */ export declare interface Dimension<TSchema extends Record<string, any> = Record<string, any>> { name: string; title?: string; description?: string; type: DimensionType; /** Direct column reference or SQL expression */ sql: AnyColumn | SQL | ((ctx: QueryContext<TSchema>) => AnyColumn | SQL); /** Whether this is a primary key */ primaryKey?: boolean; /** Display format */ format?: string; } export declare interface DimensionAnnotation { title: string; shortTitle: string; type: string; format?: DimensionFormat; } export declare type DimensionFormat = 'currency' | 'percent' | 'number' | 'date' | 'datetime' | 'id' | 'link'; export declare interface DimensionMetadata { name: string; title: string; shortTitle: string; type: string; format?: DimensionFormat; description?: string; } export declare type DimensionType = 'string' | 'number' | 'time' | 'boolean'; /** * Drizzle column reference type */ export declare type DrizzleColumn = { _: { name: string; dataType: string; columnType: string; }; }; /** * Generic Drizzle database type * Supports any Drizzle client (PostgreSQL, MySQL, SQLite) */ export declare type DrizzleDatabase<TSchema extends Record<string, any> = Record<string, any>> = { select: (fields?: any) => any; insert: (table: any) => any; update: (table: any) => any; delete: (table: any) => any; execute?: (query: SQL) => Promise<any>; run?: (query: SQL) => any; all?: (query: SQL) => any[]; get?: (query: SQL) => any; $with: (alias: string) => any; with: (...args: any[]) => any; schema?: TSchema; }; /** * Filter definitions with logical operators */ export declare type Filter = FilterCondition | LogicalFilter; export declare interface FilterCondition { member: string; operator: FilterOperator; values: any[]; } /** * Supported filter operators */ export declare type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'startsWith' | 'notStartsWith' | 'endsWith' | 'notEndsWith' | 'gt' | 'gte' | 'lt' | 'lte' | 'set' | 'notSet' | 'inDateRange' | 'beforeDate' | 'afterDate'; /** * Derive SQL join type from semantic relationship */ export declare function getJoinType(relationship: string, override?: string): string; export declare type JoinType = 'left' | 'right' | 'inner' | 'full'; /** * Compiled cube with executable query function */ export declare interface LegacyCompiledCube<TSchema extends Record<string, any> = Record<string, any>> extends SemanticCube<TSchema> { queryFn: (query: SemanticQuery, securityContext: SecurityContext) => Promise<QueryResult>; } /** * Query context passed to cube SQL functions * Provides access to database, schema, and security context */ export declare interface LegacyQueryContext<TSchema extends Record<string, any> = Record<string, any>> { /** Drizzle database instance */ db: DrizzleDatabase<TSchema>; /** Database schema (tables, columns, etc.) */ schema: TSchema; /** Security context for filtering */ securityContext: SecurityContext; /** The semantic query being executed */ query: SemanticQuery; /** The compiled cube being queried */ cube: LegacyCompiledCube<TSchema>; } export declare interface LogicalFilter { and?: Filter[]; or?: Filter[]; } /** * Measure definition */ export declare interface Measure<TSchema extends Record<string, any> = Record<string, any>> { name: string; title?: string; description?: string; type: MeasureType; /** Column to aggregate or SQL expression */ sql: AnyColumn | SQL | ((ctx: QueryContext<TSchema>) => AnyColumn | SQL); /** Display format */ format?: string; /** Filters applied to this measure */ filters?: Array<(ctx: QueryContext<TSchema>) => SQL>; } /** * Annotation interfaces for UI metadata */ export declare interface MeasureAnnotation { title: string; shortTitle: string; type: MeasureType; format?: MeasureFormat; } /** * Display formats */ export declare type MeasureFormat = 'currency' | 'percent' | 'number' | 'integer'; export declare interface MeasureMetadata { name: string; title: string; shortTitle: string; type: MeasureType; format?: MeasureFormat; description?: string; } /** * Measure aggregation types */ export declare type MeasureType = 'count' | 'countDistinct' | 'countDistinctApprox' | 'sum' | 'avg' | 'min' | 'max' | 'runningTotal' | 'number'; /** * Multi-cube query context for cross-cube operations */ export declare interface MultiCubeQueryContext<TSchema extends Record<string, any> = Record<string, any>> extends QueryContext<TSchema> { /** Available cubes for cross-cube operations */ cubes: Map<string, Cube<TSchema>>; /** Current cube being processed */ currentCube: Cube<TSchema>; } export declare type MultiCubeQueryPlan<TSchema extends Record<string, any> = Record<string, any>> = QueryPlan<TSchema>; /** * MySQL database executor * Works with mysql2 driver */ export declare class MySQLExecutor<TSchema extends Record<string, any> = Record<string, any>> extends BaseDatabaseExecutor<TSchema> { execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T>; /** * Convert numeric string fields to numbers (measure fields + numeric dimensions) */ private convertNumericFields; /** * Coerce a value to a number if it represents a numeric type */ private coerceToNumber; getEngineType(): 'mysql'; } /** * PostgreSQL database executor * Works with postgres.js and Neon drivers */ export declare class PostgresExecutor<TSchema extends Record<string, any> = Record<string, any>> extends BaseDatabaseExecutor<TSchema> { execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T>; /** * Convert numeric string fields to numbers (only for measure fields) */ private convertNumericFields; /** * Coerce a value to a number if it represents a numeric type */ private coerceToNumber; getEngineType(): 'postgres'; } export declare class QueryBuilder<TSchema extends Record<string, any> = Record<string, any>> { private databaseAdapter; constructor(databaseAdapter: DatabaseAdapter); /** * Build dynamic selections for measures, dimensions, and time dimensions * Works for both single and multi-cube queries */ buildSelections(cubes: Map<string, Cube<TSchema>> | Cube<TSchema>, query: SemanticQuery, context: QueryContext<TSchema>): Record<string, SQL | AnyColumn>; /** * Build measure expression for HAVING clause, handling CTE references correctly */ private buildHavingMeasureExpression; /** * Build measure expression with aggregation and filters */ buildMeasureExpression(measure: any, context: QueryContext<TSchema>): SQL; /** * Build time dimension expression with granularity using database adapter */ buildTimeDimensionExpression(dimensionSql: any, granularity: string | undefined, context: QueryContext<TSchema>): SQL; /** * Build WHERE conditions from semantic query filters (dimensions only) * Works for both single and multi-cube queries */ buildWhereConditions(cubes: Map<string, Cube<TSchema>> | Cube<TSchema>, query: SemanticQuery, context: QueryContext<TSchema>, queryPlan?: QueryPlan<TSchema>): SQL[]; /** * Build HAVING conditions from semantic query filters (measures only) * Works for both single and multi-cube queries */ buildHavingConditions(cubes: Map<string, Cube<TSchema>> | Cube<TSchema>, query: SemanticQuery, context: QueryContext<TSchema>, queryPlan?: QueryPlan<TSchema>): SQL[]; /** * Process a single filter (basic or logical) * @param filterType - 'where' for dimension filters, 'having' for measure filters */ private processFilter; /** * Build filter condition using Drizzle operators */ private buildFilterCondition; /** * Build date range condition for time dimensions */ buildDateRangeCondition(fieldExpr: AnyColumn | SQL, dateRange: string | string[]): SQL | null; /** * Parse relative date range expressions like "today", "yesterday", "last 7 days", "this month", etc. * Handles all 14 DATE_RANGE_OPTIONS from the client */ private parseRelativeDateRange; /** * Normalize date values to handle strings, numbers, and Date objects * Always returns a JavaScript Date object or null * Database-agnostic - just ensures we have a valid Date */ private normalizeDate; /** * Build GROUP BY fields from dimensions and time dimensions * Works for both single and multi-cube queries */ buildGroupByFields(cubes: Map<string, Cube<TSchema>> | Cube<TSchema>, query: SemanticQuery, context: QueryContext<TSchema>, queryPlan?: any): (SQL | AnyColumn)[]; /** * Build ORDER BY clause with automatic time dimension sorting */ buildOrderBy(query: SemanticQuery, selectedFields?: string[]): SQL[]; /** * Collect numeric field names (measures + numeric dimensions) for type conversion * Works for both single and multi-cube queries */ collectNumericFields(cubes: Map<string, Cube<TSchema>> | Cube<TSchema>, query: SemanticQuery): string[]; /** * Apply LIMIT and OFFSET to a query with validation * If offset is provided without limit, add a reasonable default limit */ applyLimitAndOffset<T>(query: T, semanticQuery: SemanticQuery): T; } /** * Query context for dynamic building */ export declare interface QueryContext<TSchema extends Record<string, any> = Record<string, any>> { /** Drizzle database instance */ db: DrizzleDatabase<TSchema>; /** Database schema */ schema: TSchema; /** Security context for filtering */ securityContext: SecurityContext; } export declare class QueryExecutor<TSchema extends Record<string, any> = Record<string, any>> { private dbExecutor; private queryBuilder; private queryPlanner; private databaseAdapter; constructor(dbExecutor: DatabaseExecutor<TSchema>); /** * Unified query execution method that handles both single and multi-cube queries */ execute(cubes: Map<string, Cube<TSchema>>, query: SemanticQuery, securityContext: SecurityContext): Promise<QueryResult>; /** * Legacy interface for single cube queries */ executeQuery(cube: Cube<TSchema>, query: SemanticQuery, securityContext: SecurityContext): Promise<QueryResult>; /** * Build pre-aggregation CTE for hasMany relationships */ private buildPreAggregationCTE; /** * Build join condition for CTE */ private buildCTEJoinCondition; /** * Build unified query that works for both single and multi-cube queries */ private buildUnifiedQuery; /** * Convert query plan to cube map for QueryBuilder methods */ private getCubesFromPlan; /** * Generate raw SQL for debugging (without execution) - unified approach */ generateSQL(cube: Cube<TSchema>, query: SemanticQuery, securityContext: SecurityContext): Promise<{ sql: string; params?: any[]; }>; /** * Generate raw SQL for multi-cube queries without execution - unified approach */ generateMultiCubeSQL(cubes: Map<string, Cube<TSchema>>, query: SemanticQuery, securityContext: SecurityContext): Promise<{ sql: string; params?: any[]; }>; /** * Generate SQL using unified approach (works for both single and multi-cube) */ private generateUnifiedSQL; /** * Generate annotations for UI metadata - unified approach */ private generateAnnotations; } /** * Unified Query Plan for both single and multi-cube queries * - For single-cube queries: joinCubes array is empty * - For multi-cube queries: joinCubes contains the additional cubes to join * - selections, whereConditions, and groupByFields are populated by QueryBuilder */ export declare interface QueryPlan<TSchema extends Record<string, any> = Record<string, any>> { /** Primary cube that drives the query */ primaryCube: Cube<TSchema>; /** Additional cubes to join (empty for single-cube queries) */ joinCubes: Array<{ cube: Cube<TSchema>; alias: string; joinType: 'inner' | 'left' | 'right' | 'full'; joinCondition: SQL; }>; /** Combined field selections across all cubes (built by QueryBuilder) */ selections: Record<string, SQL | AnyColumn>; /** WHERE conditions for the entire query (built by QueryBuilder) */ whereConditions: SQL[]; /** GROUP BY fields if aggregations are present (built by QueryBuilder) */ groupByFields: (SQL | AnyColumn)[]; /** Pre-aggregation CTEs for hasMany relationships to prevent fan-out */ preAggregationCTEs?: Array<{ cube: Cube<TSchema>; alias: string; cteAlias: string; joinKeys: Array<{ sourceColumn: string; targetColumn: string; sourceColumnObj?: AnyColumn; targetColumnObj?: AnyColumn; }>; measures: string[]; }>; } /** * Pre-aggregation plan for handling hasMany relationships */ export declare class QueryPlanner<TSchema extends Record<string, any> = Record<string, any>> { /** * Analyze a semantic query to determine which cubes are involved */ analyzeCubeUsage(query: SemanticQuery): Set<string>; /** * Recursively extract cube names from filters (handles logical filters) */ private extractCubeNamesFromFilter; /** * Extract measures referenced in filters (for CTE inclusion) */ private extractMeasuresFromFilters; /** * Recursively extract measures from filters for a specific cube */ private extractMeasuresFromFilter; /** * Create a unified query plan that works for both single and multi-cube queries */ createQueryPlan(cubes: Map<string, Cube<TSchema>>, query: SemanticQuery, ctx: QueryContext<TSchema>): QueryPlan<TSchema>; /** * Choose the primary cube based on query analysis * Uses a consistent strategy to avoid measure order dependencies */ choosePrimaryCube(cubeNames: string[], query: SemanticQuery, cubes?: Map<string, Cube<TSchema>>): string; /** * Check if a cube can reach all other cubes in the list via joins */ private canReachAllCubes; /** * Build join plan for multi-cube query * Supports both direct joins and transitive joins through intermediate cubes */ private buildJoinPlan; /** * Build join condition from new array-based join definition */ private buildJoinCondition; /** * Find join path from source cube to target cube * Returns array of join steps to reach target */ private findJoinPath; /** * Plan pre-aggregation CTEs for hasMany relationships to prevent fan-out */ private planPreAggregationCTEs; /** * Find hasMany join definition from primary cube to target cube */ private findHasManyJoinDef; } /** * Query execution result */ export declare interface QueryResult { data: any[]; annotation: { measures: Record<string, MeasureAnnotation>; dimensions: Record<string, DimensionAnnotation>; segments: Record<string, any>; timeDimensions: Record<string, TimeDimensionAnnotation>; }; } /** * Resolve cube reference (handles both direct and lazy references) */ export declare function resolveCubeReference<TSchema extends Record<string, any>>(ref: Cube<TSchema> | (() => Cube<TSchema>)): Cube<TSchema>; /** * Helper to resolve SQL expressions */ export declare function resolveSqlExpression<TSchema extends Record<string, any>>(expr: AnyColumn | SQL | ((ctx: QueryContext<TSchema>) => AnyColumn | SQL), ctx: QueryContext<TSchema>): AnyColumn | SQL; /** * Security context passed to cube SQL functions * Contains user/tenant-specific data for filtering */ export declare interface SecurityContext { [key: string]: any; } /** * Semantic cube definition with Drizzle integration */ export declare interface SemanticCube<TSchema extends Record<string, any> = Record<string, any>> { name: string; title?: string; description?: string; /** Base SQL for the cube - can use Drizzle query builder */ sql: string | SQL | ((context: LegacyQueryContext<TSchema>) => SQL | Promise<SQL>); /** Cube dimensions */ dimensions: Record<string, SemanticDimension<TSchema>>; /** Cube measures */ measures: Record<string, SemanticMeasure<TSchema>>; /** Joins to other cubes */ joins?: Record<string, SemanticJoin<TSchema>>; /** Whether cube is publicly accessible */ public?: boolean; /** SQL alias for the cube */ sqlAlias?: string; /** Data source identifier */ dataSource?: string; /** Refresh configuration */ refreshKey?: { every?: string; sql?: string | SQL; }; /** Pre-aggregation definitions */ preAggregations?: Record<string, SemanticPreAggregation>; /** Additional metadata */ meta?: Record<string, any>; } /** * Semantic dimension with Drizzle column support */ export declare interface SemanticDimension<TSchema extends Record<string, any> = Record<string, any>> { name: string; title?: string; description?: string; type: DimensionType; /** SQL expression - can be Drizzle column reference or SQL template */ sql: string | SQL | DrizzleColumn | ((context: LegacyQueryContext<TSchema>) => SQL | DrizzleColumn); /** Whether this is a primary key */ primaryKey?: boolean; /** Whether to show in UI */ shown?: boolean; /** Display format */ format?: DimensionFormat; /** Additional metadata */ meta?: Record<string, any>; } /** * Join definition between cubes */ export declare interface SemanticJoin<TSchema extends Record<string, any> = Record<string, any>> { name?: string; type?: JoinType; /** Join condition using Drizzle SQL */ sql: string | SQL | ((context: LegacyQueryContext<TSchema>) => SQL); /** Relationship type */ relationship: 'belongsTo' | 'hasOne' | 'hasMany'; } export declare const semanticLayer: SemanticLayerCompiler<Record<string, any>>; export declare class SemanticLayerCompiler<TSchema extends Record<string, any> = Record<string, any>> { private cubes; private dbExecutor?; private metadataCache?; private metadataCacheTimestamp?; private readonly METADATA_CACHE_TTL; constructor(options?: { drizzle?: DatabaseExecutor<TSchema>['db']; schema?: TSchema; databaseExecutor?: DatabaseExecutor<TSchema>; engineType?: 'postgres' | 'mysql' | 'sqlite'; }); /** * Set or update the database executor */ setDatabaseExecutor(executor: DatabaseExecutor<TSchema>): void; /** * Get the database engine type for SQL formatting */ getEngineType(): 'postgres' | 'mysql' | 'sqlite' | undefined; /** * Set Drizzle instance and schema directly */ setDrizzle(db: DatabaseExecutor<TSchema>['db'], schema?: TSchema, engineType?: 'postgres' | 'mysql' | 'sqlite'): void; /** * Check if database executor is configured */ hasExecutor(): boolean; /** * Register a simplified cube with dynamic query building */ registerCube(cube: Cube<TSchema>): void; /** * Get a cube by name */ getCube(name: string): Cube<TSchema> | undefined; /** * Get all registered cubes */ getAllCubes(): Cube<TSchema>[]; /** * Get all cubes as a Map for multi-cube queries */ getAllCubesMap(): Map<string, Cube<TSchema>>; /** * Unified query execution method that handles both single and multi-cube queries */ execute(query: SemanticQuery, securityContext: SecurityContext): Promise<QueryResult>; /** * Execute a multi-cube query */ executeMultiCubeQuery(query: SemanticQuery, securityContext: SecurityContext): Promise<QueryResult>; /** * Execute a single cube query */ executeQuery(cubeName: string, query: SemanticQuery, securityContext: SecurityContext): Promise<QueryResult>; /** * Get metadata for all cubes (for API responses) * Uses caching to improve performance for repeated requests */ getMetadata(): CubeMetadata[]; /** * Generate cube metadata for API responses from cubes * Optimized version that minimizes object iterations */ private generateCubeMetadata; /** * Get SQL for a query without executing it (debugging) */ generateSQL(cubeName: string, query: SemanticQuery, securityContext: SecurityContext): Promise<{ sql: string; params?: any[]; }>; /** * Get SQL for a multi-cube query without executing it (debugging) */ generateMultiCubeSQL(query: SemanticQuery, securityContext: SecurityContext): Promise<{ sql: string; params?: any[]; }>; /** * Check if a cube exists */ hasCube(name: string): boolean; /** * Remove a cube */ removeCube(name: string): boolean; /** * Clear all cubes */ clearCubes(): void; /** * Invalidate the metadata cache * Called whenever cubes are modified */ private invalidateMetadataCache; /** * Get cube names */ getCubeNames(): string[]; /** * Validate a query against registered cubes * Ensures all referenced cubes and fields exist */ validateQuery(query: SemanticQuery): { isValid: boolean; errors: string[]; }; } /** * Utility functions for working with the semantic layer */ export declare const SemanticLayerUtils: { /** * Create a simple query builder */ query: () => { measures: (measures: string[]) => { dimensions: (dimensions?: string[]) => { filters: (filters?: any[]) => { timeDimensions: (timeDimensions?: any[]) => { limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; timeDimensions: (timeDimensions?: any[]) => { filters: (filters?: any[]) => { limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; filters: (filters?: any[]) => { dimensions: (dimensions?: string[]) => { timeDimensions: (timeDimensions?: any[]) => { limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; timeDimensions: (timeDimensions?: any[]) => { dimensions: (dimensions?: string[]) => { limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; limit: (limit?: number) => { order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; order: (order?: Record<string, "asc" | "desc">) => { measures: string[]; dimensions: string[]; filters: any[]; timeDimensions: any[]; limit: number | undefined; order: Record<string, "asc" | "desc"> | undefined; }; }; }; }; /** * Create filters */ filters: { equals: (member: string, value: any) => { member: string; operator: "equals"; values: any[]; }; notEquals: (member: string, value: any) => { member: string; operator: "notEquals"; values: any[]; }; contains: (member: string, value: string) => { member: string; operator: "contains"; values: string[]; }; greaterThan: (member: string, value: any) => { member: string; operator: "gt"; values: any[]; }; lessThan: (member: string, value: any) => { member: string; operator: "lt"; values: any[]; }; inDateRange: (member: string, from: string, to: string) => { member: string; operator: "inDateRange"; values: string[]; }; set: (member: string) => { member: string; operator: "set"; values: never[]; }; notSet: (member: string) => { member: string; operator: "notSet"; values: never[]; }; }; /** * Create time dimensions */ timeDimensions: { create: (dimension: string, granularity?: TimeGranularity, dateRange?: string | string[]) => { dimension: string; granularity: TimeGranularity | undefined; dateRange: string | string[] | undefined; }; }; }; /** * Semantic measure with Drizzle aggregation support */ export declare interface SemanticMeasure<TSchema extends Record<string, any> = Record<string, any>> { name: string; title?: string; description?: string; type: MeasureType; /** SQL expression - can be Drizzle column or aggregation function */ sql: string | SQL | DrizzleColumn | ((context: LegacyQueryContext<TSchema>) => SQL | DrizzleColumn); /** Display format */ format?: MeasureFormat; /** Whether to show in UI */ shown?: boolean; /** Filters applied to this measure */ filters?: Array<{ sql: string | SQL | ((context: LegacyQueryContext<TSchema>) => SQL); }>; /** Rolling window configuration */ rollingWindow?: { trailing?: string; leading?: string; offset?: string; }; /** Additional metadata */ meta?: Record<string, any>; } /** * Pre-aggregation configuration */ export declare interface SemanticPreAggregation { name: string; measures: string[]; dimensions: string[]; timeDimension?: { dimension: string; granularity: TimeGranularity[]; }; refreshKey?: { every: string; sql?: string | SQL; }; indexes?: Record<string, string[]>; } /** * Semantic query structure (Cube.js compatible) */ export declare interface SemanticQuery { measures?: string[]; dimensions?: string[]; filters?: Array<Filter>; timeDimensions?: Array<TimeDimension>; limit?: number; offset?: number; order?: Record<string, 'asc' | 'desc'>; } export { SQL } /** * SQLite database executor * Works with better-sqlite3 driver */ export declare class SQLiteExecutor<TSchema extends Record<string, any> = Record<string, any>> extends BaseDatabaseExecutor<TSchema> { execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T>; /** * Convert numeric string fields to numbers (only for measure fields) */ private convertNumericFields; /** * Coerce a value to a number if it represents a numeric type */ private coerceToNumber; getEngineType(): 'sqlite'; } /** * SQL generation result */ export declare interface SqlResult { sql: string; params?: any[]; } /** * Time dimension with granularity */ export declare interface TimeDimension { dimension: string; granularity?: TimeGranularity; dateRange?: string | string[]; } export declare interface TimeDimensionAnnotation { title: string; shortTitle: string; type: string; granularity?: TimeGranularity; } export declare type TimeGranularity = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; export { }