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
TypeScript
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 { }