@general-dexterity/cube-records
Version:
Opinionated wrapper around @cubejs-client/react
267 lines (263 loc) • 12 kB
text/typescript
import { BinaryOperator, UnaryOperator, TimeDimension, ResultSet } from '@cubejs-client/core';
import { UseCubeQueryOptions } from '@cubejs-client/react';
/**
* Base interface for defining a CubeRecord.
*/
interface CubeRecordDefinition {
name: string;
measures: Record<string, {
type: unknown;
__cubetype?: string;
}>;
dimensions: Record<string, {
type: unknown;
__cubetype?: string;
}>;
joins?: readonly string[];
segments?: readonly string[];
}
/**
* Global interface that should be extended by users to define their cube records.
* Users should declare this interface in their project:
*
* declare global {
* interface CubeRecordMap {
* users: {
* measures: { count: { type: number } };
* dimensions: { id: { type: string } };
* joins: readonly ['orders'];
* };
* }
* }
*/
interface CubeRecordMap {
__empty: {
measures: {};
dimensions: {};
joins: [];
};
}
/**
* Type for cube record names
*/
type CubeRecordName = keyof CubeRecordMap;
/**
* Extracts measure names from a specific cube record
*/
type CubeRecordMeasure<T extends CubeRecordName> = keyof CubeRecordMap[T]['measures'] & string;
/**
* Extracts dimension names from a specific cube record
*/
type CubeRecordDimension<T extends CubeRecordName> = keyof CubeRecordMap[T]['dimensions'] & string;
/**
* Creates measure names from joined cube records
*/
type JoinedCubeRecordMeasure<T extends CubeRecordName> = T extends CubeRecordName ? CubeRecordMap[T]['joins'] extends infer J ? J extends readonly string[] ? J[number] extends infer JoinName ? JoinName extends CubeRecordName ? `${JoinName}.${keyof CubeRecordMap[JoinName]['measures'] & string}` : never : never : never : never : never;
/**
* Creates dimension names from joined cube records
*/
type JoinedCubeRecordDimension<T extends CubeRecordName> = T extends CubeRecordName ? CubeRecordMap[T]['joins'] extends infer J ? J extends readonly string[] ? J[number] extends infer JoinName ? JoinName extends CubeRecordName ? `${JoinName}.${keyof CubeRecordMap[JoinName]['dimensions'] & string}` : never : never : never : never : never;
/**
* Type for measures of a specific cube record, including joined measures
*/
type CubeRecordQueryMeasure<N extends CubeRecordName> = CubeRecordMeasure<N> | JoinedCubeRecordMeasure<N>;
/**
* Type for dimensions of a specific cube record, including joined dimensions
*/
type CubeRecordQueryDimension<N extends CubeRecordName> = CubeRecordDimension<N> | JoinedCubeRecordDimension<N>;
/**
* Type for all attributes (measures and dimensions) of a cube record
*/
type CubeRecordAttribute<N extends CubeRecordName> = CubeRecordQueryMeasure<N> | CubeRecordQueryDimension<N>;
/**
* Extracts the type information for a list of cube record measures
*/
type ExtractCubeRecordMeasureTypes<N extends CubeRecordName, M extends CubeRecordQueryMeasure<N>[]> = M extends [] ? Record<string, never> : {
[K in M[number]]: K extends `${infer J}.${infer D}` ? J extends keyof CubeRecordMap ? D extends keyof CubeRecordMap[J]['measures'] ? CubeRecordMap[J]['measures'][D] extends {
type: infer R;
} ? R : unknown : unknown : unknown : K extends keyof CubeRecordMap[N]['measures'] ? CubeRecordMap[N]['measures'][K] extends {
type: infer R;
} ? R : unknown : unknown;
};
/**
* Extracts the type information for a list of cube record dimensions
*/
type ExtractCubeRecordDimensionTypes<N extends CubeRecordName, D extends CubeRecordQueryDimension<N>[]> = D extends [] ? Record<string, never> : {
[K in D[number]]: K extends `${infer J}.${infer Dim}` ? J extends keyof CubeRecordMap ? Dim extends keyof CubeRecordMap[J]['dimensions'] ? CubeRecordMap[J]['dimensions'][Dim] extends {
type: infer R;
} ? R : unknown : unknown : unknown : K extends keyof CubeRecordMap[N]['dimensions'] ? CubeRecordMap[N]['dimensions'][K] extends {
type: infer R;
} ? R : unknown : unknown;
};
/**
* Represents a row in a cube record query result, with typed measures and dimensions
*/
type CubeRecordQueryRow<N extends CubeRecordName, M extends CubeRecordQueryMeasure<N>[] = [], D extends CubeRecordQueryDimension<N>[] = []> = ExtractCubeRecordMeasureTypes<N, M> & ExtractCubeRecordDimensionTypes<N, D>;
/**
* Extracts only time dimensions from a cube record
*/
type CubeRecordTimeDimension<N extends CubeRecordName> = {
[K in CubeRecordDimension<N>]: CubeRecordMap[N]['dimensions'][K] extends {
__cubetype: 'time';
} ? K : never;
}[CubeRecordDimension<N>];
/**
* Extracts only time dimensions from joined cube records
*/
type JoinedCubeRecordTimeDimension<T extends CubeRecordName> = T extends CubeRecordName ? CubeRecordMap[T]['joins'] extends infer J ? J extends readonly string[] ? J[number] extends infer JoinName ? JoinName extends CubeRecordName ? {
[K in keyof CubeRecordMap[JoinName]['dimensions']]: CubeRecordMap[JoinName]['dimensions'][K] extends {
__cubetype: 'time';
} ? `${JoinName}.${K & string}` : never;
}[keyof CubeRecordMap[JoinName]['dimensions']] : never : never : never : never : never;
/**
* Type for time dimensions of a specific cube record, including joined time dimensions
*/
type CubeRecordQueryTimeDimension<N extends CubeRecordName> = CubeRecordTimeDimension<N> | JoinedCubeRecordTimeDimension<N>;
/**
* Utility type to extract only string-typed fields from a type
*/
type StringFields<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
/**
* Utility type to pick only string-typed fields from a type
*/
type PickStringFields<T> = Pick<T, StringFields<T>>;
/**
* Utility type to extract only number-typed fields from a type
*/
type NumberFields<T> = {
[K in keyof T]: T[K] extends number ? K : never;
}[keyof T];
/**
* Utility type to pick only number-typed fields from a type
*/
type PickNumberFields<T> = Pick<T, NumberFields<T>>;
/**
* Helper type to check if all values in a union are numbers
*/
type AllNumber<T> = T extends number ? true : false;
/**
* Helper type to check if all values in a union are strings
*/
type AllString<T> = T extends string ? true : false;
/**
* Helper to get all measure types for a cube
*/
type GetAllMeasureTypes<N extends CubeRecordName> = {
[K in keyof CubeRecordMap[N]['measures']]: CubeRecordMap[N]['measures'][K] extends {
type: infer T;
} ? T : never;
}[keyof CubeRecordMap[N]['measures']];
/**
* Smart measure type that infers the type based on all measures
* If all measures are numbers, returns number
* If all measures are strings, returns string
* Otherwise returns the union of all measure types
*/
type CubeRecordQueryMeasureType<N extends CubeRecordName> = GetAllMeasureTypes<N> extends infer Types ? [Types] extends [never] ? unknown : AllNumber<Types> extends true ? number : AllString<Types> extends true ? string : Types : unknown;
/**
* Helper to extract measure types when all are the same type
*/
type SmartMeasureTypes<N extends CubeRecordName, M extends CubeRecordQueryMeasure<N>[]> = M extends [] ? Record<string, never> : CubeRecordQueryMeasureType<N> extends number ? {
[K in M[number]]: number;
} : CubeRecordQueryMeasureType<N> extends string ? {
[K in M[number]]: string;
} : ExtractCubeRecordMeasureTypes<N, M>;
/**
* Enhanced query row type with better inference for measures
* When all measures in a cube are numbers, measure fields will be typed as number
* When all measures are strings, they'll be typed as string
* Otherwise falls back to specific types per measure
*/
type CubeRecordQueryRowEnhanced<N extends CubeRecordName, M extends CubeRecordQueryMeasure<N>[] = [], D extends CubeRecordQueryDimension<N>[] = []> = SmartMeasureTypes<N, M> & ExtractCubeRecordDimensionTypes<N, D>;
/**
* Type-safe groupBy helper - extracts keys that are strings
*/
type GroupByKey<T> = StringFields<T>;
/**
* Remap keys by adding the model prefix to non-joined attributes
*/
type RemapKeysWithModel<N extends CubeRecordName, T> = {
[K in keyof T as K extends string ? K extends `${string}.${string}` ? K : `${N}.${K}` : K]: T[K];
};
/**
* Filter definition for cube record queries
*/
type CubeRecordQueryFilter<N extends CubeRecordName> = {
member: CubeRecordQueryMeasure<N> | CubeRecordQueryDimension<N>;
operator: BinaryOperator;
values: string[];
} | {
member: CubeRecordQueryMeasure<N> | CubeRecordQueryDimension<N>;
operator: UnaryOperator;
values?: never;
};
/**
* Order type that accepts any valid attribute (measure or dimension) from the cube
*/
type CubeRecordOrder<N extends CubeRecordName> = Partial<Record<CubeRecordAttribute<N>, 'asc' | 'desc'>>;
/**
* Type-safe time dimension that only accepts dimensions with time type
*/
type CubeRecordTimeDimensionParam<N extends CubeRecordName> = Omit<TimeDimension, 'dimension'> & {
dimension: CubeRecordQueryTimeDimension<N>;
};
/**
* Query parameters for the useCubeRecordQuery hook
*/
interface CubeRecordQueryParams<N extends CubeRecordName> {
measures?: CubeRecordQueryMeasure<N>[];
dimensions?: CubeRecordQueryDimension<N>[];
filters?: CubeRecordQueryFilter<N>[];
segments?: string[];
timeDimensions?: CubeRecordTimeDimensionParam<N>[];
limit?: number;
offset?: number;
order?: CubeRecordOrder<N>;
timezone?: string;
renewQuery?: boolean;
ungrouped?: boolean;
total?: boolean;
}
/**
* Result of the useCubeRecordQuery hook
*/
interface CubeRecordQueryResult<N extends CubeRecordName, M extends CubeRecordQueryMeasure<N>[] = [], D extends CubeRecordQueryDimension<N>[] = [], Strip extends boolean = true> {
isLoading: boolean;
error?: Error;
resultSet?: ResultSet;
refetch: () => Promise<void>;
data: (Strip extends true ? CubeRecordQueryRow<N, M, D> : RemapKeysWithModel<N, CubeRecordQueryRow<N, M, D>>)[];
totalResultCount: number | null;
}
type UseCubeRecordFormattingOptions = {
stripModelPrefix?: boolean;
};
type UseCubeRecordOptions = UseCubeQueryOptions & UseCubeRecordFormattingOptions;
type UseCubeRecordQueryProps<N extends CubeRecordName, M extends CubeRecordQueryMeasure<N>[] = [], D extends CubeRecordQueryDimension<N>[] = []> = {
model: N;
query: CubeRecordQueryParams<N> & {
measures?: M;
dimensions?: D;
};
options?: UseCubeRecordOptions;
};
/**
* A strongly-typed wrapper around Cube.js useCubeQuery hook for CubeRecords
*
* @param cubeRecordName - The name of the cube record
* @param query - Query parameters including measures, dimensions, filters, etc.
* @returns Strongly typed query result with data, loading state, and error handling
*/
declare function useCubeRecordQuery<N extends CubeRecordName, M extends CubeRecordQueryMeasure<N>[] = [], D extends CubeRecordQueryDimension<N>[] = []>(props: UseCubeRecordQueryProps<N, M, D> & {
options?: UseCubeRecordOptions & {
stripModelPrefix?: true | undefined;
};
}): CubeRecordQueryResult<N, M, D, true>;
declare function useCubeRecordQuery<N extends CubeRecordName, M extends CubeRecordQueryMeasure<N>[] = [], D extends CubeRecordQueryDimension<N>[] = []>(props: UseCubeRecordQueryProps<N, M, D> & {
options: UseCubeRecordOptions & {
stripModelPrefix: false;
};
}): CubeRecordQueryResult<N, M, D, false>;
export { type CubeRecordAttribute, type CubeRecordDefinition, type CubeRecordMap, type CubeRecordName, type CubeRecordOrder, type CubeRecordQueryDimension, type CubeRecordQueryFilter, type CubeRecordQueryMeasure, type CubeRecordQueryMeasureType, type CubeRecordQueryParams, type CubeRecordQueryResult, type CubeRecordQueryRow, type CubeRecordQueryRowEnhanced, type CubeRecordQueryTimeDimension, type CubeRecordTimeDimension, type CubeRecordTimeDimensionParam, type GroupByKey, type NumberFields, type PickNumberFields, type PickStringFields, type StringFields, type UseCubeRecordOptions, type UseCubeRecordQueryProps, useCubeRecordQuery };