@bonnard/sdk
Advanced tools
+1
-4
@@ -8,6 +8,3 @@ /** | ||
| * Execute a JSON query against the semantic layer. | ||
| * | ||
| * Supports two modes: | ||
| * - **Short names**: provide `cube` and use unqualified field names (e.g. `"revenue"`) | ||
| * - **Fully-qualified names**: omit `cube` and use dot notation (e.g. `"orders.revenue"`) | ||
| * All field names must be fully qualified (e.g. "orders.revenue"). | ||
| */ | ||
@@ -14,0 +11,0 @@ query<T = Record<string, unknown>>(options: QueryOptions): Promise<QueryResult<T>>; |
+5
-53
| /** | ||
| * Bonnard SDK — Client for querying semantic layer | ||
| */ | ||
| import { buildCubeQuery, simplifyResult, isCubeNativeFormat } from './query.js'; | ||
| import { toCubeQuery } from './query.js'; | ||
| /** | ||
@@ -77,58 +77,11 @@ * Parse JWT expiry from the payload (base64url-decoded middle segment). | ||
| } | ||
| /** | ||
| * Build a Cube-native query from QueryOptions that already use fully-qualified names. | ||
| */ | ||
| function buildNativeQuery(options) { | ||
| const cubeQuery = {}; | ||
| if (options.measures) { | ||
| cubeQuery.measures = options.measures; | ||
| } | ||
| if (options.dimensions) { | ||
| cubeQuery.dimensions = options.dimensions; | ||
| } | ||
| if (options.filters) { | ||
| cubeQuery.filters = options.filters.map(f => ({ | ||
| member: f.dimension, | ||
| operator: f.operator, | ||
| values: f.values, | ||
| })); | ||
| } | ||
| if (options.timeDimension) { | ||
| cubeQuery.timeDimensions = [{ | ||
| dimension: options.timeDimension.dimension, | ||
| granularity: options.timeDimension.granularity, | ||
| dateRange: options.timeDimension.dateRange, | ||
| }]; | ||
| } | ||
| if (options.orderBy) { | ||
| cubeQuery.order = Object.entries(options.orderBy).map(([key, dir]) => [key, dir]); | ||
| } | ||
| if (options.limit) { | ||
| cubeQuery.limit = options.limit; | ||
| } | ||
| return cubeQuery; | ||
| } | ||
| return { | ||
| /** | ||
| * Execute a JSON query against the semantic layer. | ||
| * | ||
| * Supports two modes: | ||
| * - **Short names**: provide `cube` and use unqualified field names (e.g. `"revenue"`) | ||
| * - **Fully-qualified names**: omit `cube` and use dot notation (e.g. `"orders.revenue"`) | ||
| * All field names must be fully qualified (e.g. "orders.revenue"). | ||
| */ | ||
| async query(options) { | ||
| let cubeQuery; | ||
| if (isCubeNativeFormat(options)) { | ||
| cubeQuery = buildNativeQuery(options); | ||
| } | ||
| else { | ||
| if (!options.cube) { | ||
| throw new Error('QueryOptions requires "cube" when using short field names. ' + | ||
| 'Either set "cube" or use fully-qualified names (e.g. "orders.revenue").'); | ||
| } | ||
| cubeQuery = buildCubeQuery(options); | ||
| } | ||
| const cubeQuery = toCubeQuery(options); | ||
| const result = await request('/api/cube/query', { query: cubeQuery }); | ||
| const simplifiedData = simplifyResult(result.data); | ||
| return { data: simplifiedData, annotation: result.annotation }; | ||
| return { data: result.data, annotation: result.annotation }; | ||
| }, | ||
@@ -141,4 +94,3 @@ /** | ||
| const result = await request('/api/cube/query', { query: cubeQuery }); | ||
| const simplifiedData = simplifyResult(result.data); | ||
| return { data: simplifiedData, annotation: result.annotation }; | ||
| return { data: result.data, annotation: result.annotation }; | ||
| }, | ||
@@ -145,0 +97,0 @@ /** |
+1
-1
| export { createClient } from './client.js'; | ||
| export { buildCubeQuery, simplifyResult, isCubeNativeFormat } from './query.js'; | ||
| export { toCubeQuery } from './query.js'; | ||
| export type { BonnardConfig, QueryOptions, QueryResult, SqlResult, Filter, TimeDimension, InferQueryResult, CubeQuery, ExploreMeta, CubeMetaItem, CubeFieldMeta, CubeSegmentMeta, ExploreOptions, DashboardResult, DashboardListResult, } from './types.js'; |
+1
-1
| export { createClient } from './client.js'; | ||
| export { buildCubeQuery, simplifyResult, isCubeNativeFormat } from './query.js'; | ||
| export { toCubeQuery } from './query.js'; |
+3
-13
| /** | ||
| * Bonnard SDK — Pure query-building functions (zero IO) | ||
| * Bonnard SDK — Query format conversion (zero IO) | ||
| */ | ||
| import type { QueryOptions } from './types.js'; | ||
| /** | ||
| * Detect whether a QueryOptions object uses Cube-native fully-qualified names | ||
| * (i.e. "view.field" dot notation) rather than short names that need a cube prefix. | ||
| */ | ||
| export declare function isCubeNativeFormat(options: QueryOptions): boolean; | ||
| /** | ||
| * Convert SDK QueryOptions into a Cube-native query object. | ||
| * Handles cube-name prefixing for all field references. | ||
| * All field names must be fully qualified (e.g. "orders.revenue"). | ||
| */ | ||
| export declare function buildCubeQuery(options: QueryOptions): Record<string, unknown>; | ||
| /** | ||
| * Simplify Cube response keys by removing the cube prefix. | ||
| * e.g. { "orders.revenue": 100 } → { "revenue": 100 } | ||
| */ | ||
| export declare function simplifyResult<T = Record<string, unknown>>(data: Record<string, unknown>[]): T[]; | ||
| export declare function toCubeQuery(options: QueryOptions): Record<string, unknown>; |
+8
-40
| /** | ||
| * Bonnard SDK — Pure query-building functions (zero IO) | ||
| * Bonnard SDK — Query format conversion (zero IO) | ||
| */ | ||
| /** | ||
| * Detect whether a QueryOptions object uses Cube-native fully-qualified names | ||
| * (i.e. "view.field" dot notation) rather than short names that need a cube prefix. | ||
| */ | ||
| export function isCubeNativeFormat(options) { | ||
| const fields = [ | ||
| ...(options.measures ?? []), | ||
| ...(options.dimensions ?? []), | ||
| ...(options.filters?.map(f => f.dimension) ?? []), | ||
| ...(options.timeDimension ? [options.timeDimension.dimension] : []), | ||
| ]; | ||
| return fields.some(f => f.includes('.')); | ||
| } | ||
| /** | ||
| * Convert SDK QueryOptions into a Cube-native query object. | ||
| * Handles cube-name prefixing for all field references. | ||
| * All field names must be fully qualified (e.g. "orders.revenue"). | ||
| */ | ||
| export function buildCubeQuery(options) { | ||
| export function toCubeQuery(options) { | ||
| const cubeQuery = {}; | ||
| if (options.measures) { | ||
| cubeQuery.measures = options.measures.map(m => m.includes('.') ? m : `${options.cube}.${m}`); | ||
| cubeQuery.measures = options.measures; | ||
| } | ||
| if (options.dimensions) { | ||
| cubeQuery.dimensions = options.dimensions.map(d => d.includes('.') ? d : `${options.cube}.${d}`); | ||
| cubeQuery.dimensions = options.dimensions; | ||
| } | ||
| if (options.filters) { | ||
| cubeQuery.filters = options.filters.map(f => ({ | ||
| dimension: f.dimension.includes('.') ? f.dimension : `${options.cube}.${f.dimension}`, | ||
| member: f.dimension, | ||
| operator: f.operator, | ||
@@ -38,5 +25,3 @@ values: f.values, | ||
| cubeQuery.timeDimensions = [{ | ||
| dimension: options.timeDimension.dimension.includes('.') | ||
| ? options.timeDimension.dimension | ||
| : `${options.cube}.${options.timeDimension.dimension}`, | ||
| dimension: options.timeDimension.dimension, | ||
| granularity: options.timeDimension.granularity, | ||
@@ -47,6 +32,3 @@ dateRange: options.timeDimension.dateRange, | ||
| if (options.orderBy) { | ||
| cubeQuery.order = Object.entries(options.orderBy).map(([key, dir]) => [ | ||
| key.includes('.') ? key : `${options.cube}.${key}`, | ||
| dir, | ||
| ]); | ||
| cubeQuery.order = Object.entries(options.orderBy).map(([key, dir]) => [key, dir]); | ||
| } | ||
@@ -58,15 +40,1 @@ if (options.limit) { | ||
| } | ||
| /** | ||
| * Simplify Cube response keys by removing the cube prefix. | ||
| * e.g. { "orders.revenue": 100 } → { "revenue": 100 } | ||
| */ | ||
| export function simplifyResult(data) { | ||
| return data.map(row => { | ||
| const simplified = {}; | ||
| for (const [key, value] of Object.entries(row)) { | ||
| const simpleName = key.includes('.') ? key.split('.').pop() : key; | ||
| simplified[simpleName] = value; | ||
| } | ||
| return simplified; | ||
| }); | ||
| } |
+0
-1
@@ -10,3 +10,2 @@ /** | ||
| export interface QueryOptions { | ||
| cube?: string; | ||
| measures?: string[]; | ||
@@ -13,0 +12,0 @@ dimensions?: string[]; |
+1
-1
| { | ||
| "name": "@bonnard/sdk", | ||
| "version": "0.2.1", | ||
| "version": "0.3.0", | ||
| "description": "Bonnard SDK - query your semantic layer from any JavaScript or TypeScript app", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+10
-13
@@ -23,9 +23,8 @@ # @bonnard/sdk | ||
| const { data } = await bon.query({ | ||
| cube: 'orders', | ||
| measures: ['revenue', 'count'], | ||
| dimensions: ['status'], | ||
| measures: ['orders.revenue', 'orders.count'], | ||
| dimensions: ['orders.status'], | ||
| }); | ||
| console.log(data); | ||
| // [{ revenue: 45000, count: 120, status: "completed" }, ...] | ||
| // [{ "orders.revenue": 45000, "orders.count": 120, "orders.status": "completed" }, ...] | ||
| ``` | ||
@@ -63,6 +62,5 @@ | ||
| const { data } = await bon.query({ | ||
| cube: 'orders', | ||
| measures: ['revenue'], | ||
| measures: ['orders.revenue'], | ||
| timeDimension: { | ||
| dimension: 'created_at', | ||
| dimension: 'orders.created_at', | ||
| granularity: 'month', | ||
@@ -92,14 +90,13 @@ dateRange: ['2025-01-01', '2025-12-31'], | ||
| const { data } = await bon.query({ | ||
| cube: 'orders', | ||
| measures: ['revenue', 'count'], | ||
| dimensions: ['product_category'], | ||
| measures: ['orders.revenue', 'orders.count'], | ||
| dimensions: ['orders.product_category'], | ||
| filters: [ | ||
| { dimension: 'status', operator: 'equals', values: ['completed'] }, | ||
| { dimension: 'orders.status', operator: 'equals', values: ['completed'] }, | ||
| ], | ||
| timeDimension: { | ||
| dimension: 'created_at', | ||
| dimension: 'orders.created_at', | ||
| granularity: 'month', | ||
| dateRange: ['2025-01-01', '2025-12-31'], | ||
| }, | ||
| orderBy: { revenue: 'desc' }, | ||
| orderBy: { 'orders.revenue': 'desc' }, | ||
| limit: 100, | ||
@@ -106,0 +103,0 @@ }); |
15462
-20.34%330
-22.17%123
-2.38%