UNPKG

@general-dexterity/cube-records

Version:

Opinionated wrapper around @cubejs-client/react

1 lines 11.3 kB
{"version":3,"sources":["../src/index.ts","../src/use-cube-record-query.ts"],"sourcesContent":["export type {\n CubeRecordAttribute,\n CubeRecordDefinition,\n CubeRecordMap,\n CubeRecordName,\n CubeRecordQueryDimension,\n CubeRecordQueryMeasure,\n CubeRecordQueryMeasureType,\n CubeRecordQueryRow,\n CubeRecordQueryRowEnhanced,\n CubeRecordQueryTimeDimension,\n CubeRecordTimeDimension,\n GroupByKey,\n NumberFields,\n PickNumberFields,\n PickStringFields,\n StringFields,\n} from './types';\nexport type {\n CubeRecordOrder,\n CubeRecordQueryFilter,\n CubeRecordQueryParams,\n CubeRecordQueryResult,\n CubeRecordTimeDimensionParam,\n UseCubeRecordQueryProps,\n UseCubeRecordOptions,\n} from './use-cube-record-query';\nexport { useCubeRecordQuery } from './use-cube-record-query';\n","import type {\n BinaryOperator,\n Query as CoreCubeQuery,\n ResultSet,\n TimeDimension,\n UnaryOperator,\n} from '@cubejs-client/core';\nimport {\n type UseCubeQueryOptions as UseCoreCubeQueryOptions,\n useCubeQuery as useCoreCubeQuery,\n} from '@cubejs-client/react';\nimport type {\n CubeRecordAttribute,\n CubeRecordName,\n CubeRecordQueryDimension,\n CubeRecordQueryMeasure,\n CubeRecordQueryRow,\n CubeRecordQueryTimeDimension,\n} from './types';\n\n/**\n * Remap keys by adding the model prefix to non-joined attributes\n */\n// TODO: Delete this when we handle non stripping in the `CubeRecordQueryRow` type.\ntype RemapKeysWithModel<N extends CubeRecordName, T> = {\n [K in keyof T as K extends string\n ? K extends `${string}.${string}`\n ? K\n : `${N}.${K}`\n : K]: T[K];\n};\n\n/**\n * Filter definition for cube record queries\n */\nexport type CubeRecordQueryFilter<N extends CubeRecordName> =\n | {\n member: CubeRecordQueryMeasure<N> | CubeRecordQueryDimension<N>;\n operator: BinaryOperator;\n values: string[];\n }\n | {\n member: CubeRecordQueryMeasure<N> | CubeRecordQueryDimension<N>;\n operator: UnaryOperator;\n values?: never;\n };\n\n/**\n * Order type that accepts any valid attribute (measure or dimension) from the cube\n */\nexport type CubeRecordOrder<N extends CubeRecordName> = Partial<\n Record<CubeRecordAttribute<N>, 'asc' | 'desc'>\n>;\n\n/**\n * Type-safe time dimension that only accepts dimensions with time type\n */\nexport type CubeRecordTimeDimensionParam<N extends CubeRecordName> = Omit<\n TimeDimension,\n 'dimension'\n> & {\n dimension: CubeRecordQueryTimeDimension<N>;\n};\n\n/**\n * Query parameters for the useCubeRecordQuery hook\n */\nexport interface CubeRecordQueryParams<N extends CubeRecordName> {\n measures?: CubeRecordQueryMeasure<N>[];\n dimensions?: CubeRecordQueryDimension<N>[];\n filters?: CubeRecordQueryFilter<N>[];\n segments?: string[];\n timeDimensions?: CubeRecordTimeDimensionParam<N>[];\n limit?: number;\n offset?: number;\n order?: CubeRecordOrder<N>;\n timezone?: string;\n renewQuery?: boolean;\n ungrouped?: boolean;\n total?: boolean;\n}\n\n/**\n * Result of the useCubeRecordQuery hook\n */\nexport interface CubeRecordQueryResult<\n N extends CubeRecordName,\n M extends CubeRecordQueryMeasure<N>[] = [],\n D extends CubeRecordQueryDimension<N>[] = [],\n Strip extends boolean = true,\n> {\n isLoading: boolean;\n error?: Error;\n resultSet?: ResultSet;\n refetch: () => Promise<void>;\n // TODO: Use a single type that handles both stripped and non-stripped cases.\n data: (Strip extends true\n ? CubeRecordQueryRow<N, M, D>\n : RemapKeysWithModel<N, CubeRecordQueryRow<N, M, D>>\n )[];\n totalResultCount: number | null;\n}\n\n/**\n * Formats filters for Cube.js query\n */\nconst formatFilters = <N extends CubeRecordName>(\n filters: CubeRecordQueryFilter<N>[],\n cubeRecordName: string\n) => {\n return filters.map((filter) => {\n const member = filter.member.includes('.')\n ? filter.member\n : `${cubeRecordName}.${filter.member}`;\n\n if ('values' in filter && filter.values) {\n return {\n member,\n operator: filter.operator,\n values: filter.values,\n };\n }\n\n return {\n member,\n operator: filter.operator,\n };\n });\n};\n\n/**\n * Formats result set data by removing cube name prefixes from keys\n */\nconst formatResultSet = <_N extends CubeRecordName>(\n resultSet: ResultSet | null | undefined,\n cubeRecordName: string,\n strip: boolean\n): Record<string, unknown>[] => {\n if (!resultSet) {\n return [];\n }\n\n const data = resultSet.tablePivot();\n\n if (!strip) {\n // Keep as-is\n return data as unknown as Record<string, unknown>[];\n }\n\n return data.map((row) => {\n const formattedRow: Record<string, unknown> = {};\n\n for (const key in row) {\n if (Object.hasOwn(row, key)) {\n // Remove cube name prefix (e.g., \"users.id\" -> \"id\")\n const cleanKey = key.startsWith(`${cubeRecordName}.`)\n ? key.substring(cubeRecordName.length + 1)\n : key;\n formattedRow[cleanKey] = row[key];\n }\n }\n\n return formattedRow;\n });\n};\n\nexport type UseCubeRecordFormattingOptions = {\n stripModelPrefix?: boolean;\n};\n\nexport type UseCubeRecordOptions = UseCoreCubeQueryOptions & UseCubeRecordFormattingOptions;\n\nexport type UseCubeRecordQueryProps<\n N extends CubeRecordName,\n M extends CubeRecordQueryMeasure<N>[] = [],\n D extends CubeRecordQueryDimension<N>[] = [],\n> = {\n model: N;\n query: CubeRecordQueryParams<N> & {\n measures?: M;\n dimensions?: D;\n };\n options?: UseCubeRecordOptions;\n};\n\n/**\n * A strongly-typed wrapper around Cube.js useCubeQuery hook for CubeRecords\n *\n * @param cubeRecordName - The name of the cube record\n * @param query - Query parameters including measures, dimensions, filters, etc.\n * @returns Strongly typed query result with data, loading state, and error handling\n */\n// Overloads for better type narrowing based on options.stripModelPrefix\nexport function useCubeRecordQuery<\n N extends CubeRecordName,\n M extends CubeRecordQueryMeasure<N>[] = [],\n D extends CubeRecordQueryDimension<N>[] = [],\n>(props: UseCubeRecordQueryProps<N, M, D> & {\n options?: UseCubeRecordOptions & { stripModelPrefix?: true | undefined };\n}): CubeRecordQueryResult<N, M, D, true>;\n\nexport function useCubeRecordQuery<\n N extends CubeRecordName,\n M extends CubeRecordQueryMeasure<N>[] = [],\n D extends CubeRecordQueryDimension<N>[] = [],\n>(props: UseCubeRecordQueryProps<N, M, D> & {\n options: UseCubeRecordOptions & { stripModelPrefix: false };\n}): CubeRecordQueryResult<N, M, D, false>;\n\nexport function useCubeRecordQuery<\n N extends CubeRecordName,\n M extends CubeRecordQueryMeasure<N>[] = [],\n D extends CubeRecordQueryDimension<N>[] = [],\n>({\n model: cubeRecordName,\n query,\n options,\n}: UseCubeRecordQueryProps<N, M, D> & { options?: UseCubeRecordOptions }): CubeRecordQueryResult<N, M, D, boolean> {\n // Build the Cube.js query\n const cubeQuery: CoreCubeQuery = {\n measures: query.measures?.map((measure) =>\n measure.includes('.') ? measure : `${cubeRecordName}.${measure}`\n ),\n dimensions: query.dimensions?.map((dimension) =>\n dimension.includes('.') ? dimension : `${cubeRecordName}.${dimension}`\n ),\n filters: query.filters\n ? formatFilters(query.filters, cubeRecordName)\n : undefined,\n segments: query.segments,\n timeDimensions: query.timeDimensions?.map((td) => ({\n ...td,\n dimension: td.dimension.includes('.')\n ? td.dimension\n : `${cubeRecordName}.${td.dimension}`,\n })),\n limit: query.limit,\n offset: query.offset,\n order: query.order\n ? Object.entries(query.order).reduce<Record<string, 'asc' | 'desc'>>(\n (acc, [key, direction]) => {\n const member = key.includes('.') ? key : `${cubeRecordName}.${key}`;\n acc[member] = direction as 'asc' | 'desc';\n return acc;\n },\n {}\n )\n : undefined,\n timezone: query.timezone,\n renewQuery: query.renewQuery,\n ungrouped: query.ungrouped,\n total: query.total,\n };\n\n const hasStripFlag =\n options != null && Object.hasOwn(options as object, 'stripModelPrefix');\n const { stripModelPrefix = true, ...coreOptions } = options ?? {};\n const forwardedOptions = hasStripFlag ? coreOptions : options;\n\n // Use the core Cube.js query hook\n const { resultSet, isLoading, error, refetch } = useCoreCubeQuery(\n cubeQuery,\n forwardedOptions\n );\n\n // Format the result data\n const formattedData = formatResultSet(resultSet, cubeRecordName, stripModelPrefix);\n const totalResultCount = resultSet?.totalRows() ?? null;\n\n // Cast to strongly typed result based on strip flag (overloads drive the API type)\n const data = formattedData as unknown as (\n | CubeRecordQueryRow<N, M, D>[]\n | RemapKeysWithModel<N, CubeRecordQueryRow<N, M, D>>[]\n );\n\n return {\n isLoading,\n error: error as Error | undefined,\n resultSet: resultSet as ResultSet | undefined,\n refetch,\n data: data,\n totalResultCount,\n };\n}\n"],"mappings":"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","names":["useCoreCubeQuery"]}