@bonnard/sdk 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,13 +20,12 @@ const bon = createClient({
20
20
  });
21
21
 
22
22
  const { data } = await bon.query({
23
- cube: 'orders',
24
- measures: ['revenue', 'count'],
25
- dimensions: ['status'],
23
+ measures: ['orders.revenue', 'orders.count'],
24
+ dimensions: ['orders.status'],
26
25
  });
27
26
 
28
27
  console.log(data);
29
- // [{ revenue: 45000, count: 120, status: "completed" }, ...]
28
+ // [{ "orders.revenue": 45000, "orders.count": 120, "orders.status": "completed" }, ...]
30
29
  ```
31
30
 
32
31
  ### With token exchange (multi-tenant)
@@ -59,10 +58,9 @@ const bon = createClient({
59
58
  });
60
59
 
61
60
  const { data } = await bon.query({
62
- cube: 'orders',
63
- measures: ['revenue'],
61
+ measures: ['orders.revenue'],
64
62
  timeDimension: {
65
- dimension: 'created_at',
63
+ dimension: 'orders.created_at',
66
64
  granularity: 'month',
67
65
  dateRange: ['2025-01-01', '2025-12-31'],
68
66
  },
@@ -87,18 +85,17 @@ JSON query against the semantic layer.
87
85
 
88
86
  ```typescript
89
87
  const { data } = await bon.query({
90
- cube: 'orders',
91
- measures: ['revenue', 'count'],
92
- dimensions: ['product_category'],
88
+ measures: ['orders.revenue', 'orders.count'],
89
+ dimensions: ['orders.product_category'],
93
90
  filters: [
94
- { dimension: 'status', operator: 'equals', values: ['completed'] },
91
+ { dimension: 'orders.status', operator: 'equals', values: ['completed'] },
95
92
  ],
96
93
  timeDimension: {
97
- dimension: 'created_at',
94
+ dimension: 'orders.created_at',
98
95
  granularity: 'month',
99
96
  dateRange: ['2025-01-01', '2025-12-31'],
100
97
  },
101
- orderBy: { revenue: 'desc' },
98
+ orderBy: { 'orders.revenue': 'desc' },
102
99
  limit: 100,
103
100
  });
104
101
  ```
package/dist/client.d.ts CHANGED
@@ -1,14 +1,11 @@
1
1
  /**
2
2
  * Bonnard SDK — Client for querying semantic layer
3
3
  */
4
- import type { BonnardConfig, QueryOptions, QueryResult, SqlResult, CubeQuery, ExploreMeta, ExploreOptions, DashboardResult, DashboardListResult } from './types.js';
4
+ import type { BonnardConfig, QueryOptions, QueryResult, SqlResult, CubeQuery, ExploreMeta, ExploreOptions, DashboardResult, DashboardListResult, DocsOptions, DocsTopicListResult, DocsTopicResult } from './types.js';
5
5
  export declare function createClient(config: BonnardConfig): {
6
6
  /**
7
7
  * Execute a JSON query against the semantic layer.
8
- *
9
- * Supports two modes:
10
- * - **Short names**: provide `cube` and use unqualified field names (e.g. `"revenue"`)
11
- * - **Fully-qualified names**: omit `cube` and use dot notation (e.g. `"orders.revenue"`)
8
+ * All field names must be fully qualified (e.g. "orders.revenue").
12
9
  */
13
10
  query<T = Record<string, unknown>>(options: QueryOptions): Promise<QueryResult<T>>;
14
11
  /**
@@ -33,4 +30,11 @@ export declare function createClient(config: BonnardConfig): {
33
30
  * List all dashboards for the current organization.
34
31
  */
35
32
  listDashboards(): Promise<DashboardListResult>;
33
+ /**
34
+ * Access Bonnard documentation.
35
+ * - `docs()` — list all topics
36
+ * - `docs({ category: 'dashboards' })` — list topics in a category
37
+ * - `docs({ topic: 'dashboards.components' })` — get full markdown content
38
+ */
39
+ docs(options?: DocsOptions): Promise<DocsTopicListResult | DocsTopicResult>;
36
40
  };
package/dist/client.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Bonnard SDK — Client for querying semantic layer
3
3
  */
4
- import { buildCubeQuery, simplifyResult, isCubeNativeFormat } from './query.js';
4
+ import { toCubeQuery } from './query.js';
5
5
  /**
6
6
  * Parse JWT expiry from the payload (base64url-decoded middle segment).
7
7
  * Returns the `exp` claim as a millisecond timestamp, or 0 if unparseable.
@@ -74,62 +74,15 @@ export function createClient(config) {
74
74
  }
75
75
  return res.json();
76
76
  }
77
- /**
78
- * Build a Cube-native query from QueryOptions that already use fully-qualified names.
79
- */
80
- function buildNativeQuery(options) {
81
- const cubeQuery = {};
82
- if (options.measures) {
83
- cubeQuery.measures = options.measures;
84
- }
85
- if (options.dimensions) {
86
- cubeQuery.dimensions = options.dimensions;
87
- }
88
- if (options.filters) {
89
- cubeQuery.filters = options.filters.map(f => ({
90
- member: f.dimension,
91
- operator: f.operator,
92
- values: f.values,
93
- }));
94
- }
95
- if (options.timeDimension) {
96
- cubeQuery.timeDimensions = [{
97
- dimension: options.timeDimension.dimension,
98
- granularity: options.timeDimension.granularity,
99
- dateRange: options.timeDimension.dateRange,
100
- }];
101
- }
102
- if (options.orderBy) {
103
- cubeQuery.order = Object.entries(options.orderBy).map(([key, dir]) => [key, dir]);
104
- }
105
- if (options.limit) {
106
- cubeQuery.limit = options.limit;
107
- }
108
- return cubeQuery;
109
- }
110
77
  return {
111
78
  /**
112
79
  * Execute a JSON query against the semantic layer.
113
- *
114
- * Supports two modes:
115
- * - **Short names**: provide `cube` and use unqualified field names (e.g. `"revenue"`)
116
- * - **Fully-qualified names**: omit `cube` and use dot notation (e.g. `"orders.revenue"`)
80
+ * All field names must be fully qualified (e.g. "orders.revenue").
117
81
  */
118
82
  async query(options) {
119
- let cubeQuery;
120
- if (isCubeNativeFormat(options)) {
121
- cubeQuery = buildNativeQuery(options);
122
- }
123
- else {
124
- if (!options.cube) {
125
- throw new Error('QueryOptions requires "cube" when using short field names. ' +
126
- 'Either set "cube" or use fully-qualified names (e.g. "orders.revenue").');
127
- }
128
- cubeQuery = buildCubeQuery(options);
129
- }
83
+ const cubeQuery = toCubeQuery(options);
130
84
  const result = await request('/api/cube/query', { query: cubeQuery });
131
- const simplifiedData = simplifyResult(result.data);
132
- return { data: simplifiedData, annotation: result.annotation };
85
+ return { data: result.data, annotation: result.annotation };
133
86
  },
134
87
  /**
135
88
  * Execute a raw Cube-native JSON query against the semantic layer.
@@ -137,8 +90,7 @@ export function createClient(config) {
137
90
  */
138
91
  async rawQuery(cubeQuery) {
139
92
  const result = await request('/api/cube/query', { query: cubeQuery });
140
- const simplifiedData = simplifyResult(result.data);
141
- return { data: simplifiedData, annotation: result.annotation };
93
+ return { data: result.data, annotation: result.annotation };
142
94
  },
143
95
  /**
144
96
  * Execute a SQL query against the semantic layer
@@ -170,5 +122,20 @@ export function createClient(config) {
170
122
  async listDashboards() {
171
123
  return requestGet('/api/dashboards');
172
124
  },
125
+ /**
126
+ * Access Bonnard documentation.
127
+ * - `docs()` — list all topics
128
+ * - `docs({ category: 'dashboards' })` — list topics in a category
129
+ * - `docs({ topic: 'dashboards.components' })` — get full markdown content
130
+ */
131
+ async docs(options) {
132
+ const params = new URLSearchParams();
133
+ if (options?.topic)
134
+ params.set('topic', options.topic);
135
+ else if (options?.category)
136
+ params.set('category', options.category);
137
+ const qs = params.toString();
138
+ return requestGet(`/api/docs${qs ? `?${qs}` : ''}`);
139
+ },
173
140
  };
174
141
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { createClient } from './client.js';
2
- export { buildCubeQuery, simplifyResult, isCubeNativeFormat } from './query.js';
3
- export type { BonnardConfig, QueryOptions, QueryResult, SqlResult, Filter, TimeDimension, InferQueryResult, CubeQuery, ExploreMeta, CubeMetaItem, CubeFieldMeta, CubeSegmentMeta, ExploreOptions, DashboardResult, DashboardListResult, } from './types.js';
2
+ export { toCubeQuery } from './query.js';
3
+ export type { BonnardConfig, QueryOptions, QueryResult, SqlResult, Filter, TimeDimension, InferQueryResult, CubeQuery, ExploreMeta, CubeMetaItem, CubeFieldMeta, CubeSegmentMeta, ExploreOptions, DashboardResult, DashboardListResult, DocsOptions, DocsTopicSummary, DocsTopicListResult, DocsTopicResult, } from './types.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { createClient } from './client.js';
2
- export { buildCubeQuery, simplifyResult, isCubeNativeFormat } from './query.js';
2
+ export { toCubeQuery } from './query.js';
package/dist/query.d.ts CHANGED
@@ -1,19 +1,9 @@
1
1
  /**
2
- * Bonnard SDK — Pure query-building functions (zero IO)
2
+ * Bonnard SDK — Query format conversion (zero IO)
3
3
  */
4
4
  import type { QueryOptions } from './types.js';
5
- /**
6
- * Detect whether a QueryOptions object uses Cube-native fully-qualified names
7
- * (i.e. "view.field" dot notation) rather than short names that need a cube prefix.
8
- */
9
- export declare function isCubeNativeFormat(options: QueryOptions): boolean;
10
5
  /**
11
6
  * Convert SDK QueryOptions into a Cube-native query object.
12
- * Handles cube-name prefixing for all field references.
13
- */
14
- export declare function buildCubeQuery(options: QueryOptions): Record<string, unknown>;
15
- /**
16
- * Simplify Cube response keys by removing the cube prefix.
17
- * e.g. { "orders.revenue": 100 } → { "revenue": 100 }
7
+ * All field names must be fully qualified (e.g. "orders.revenue").
18
8
  */
19
- export declare function simplifyResult<T = Record<string, unknown>>(data: Record<string, unknown>[]): T[];
9
+ export declare function toCubeQuery(options: QueryOptions): Record<string, unknown>;
package/dist/query.js CHANGED
@@ -1,69 +1,37 @@
1
1
  /**
2
- * Bonnard SDK — Pure query-building functions (zero IO)
2
+ * Bonnard SDK — Query format conversion (zero IO)
3
3
  */
4
- /**
5
- * Detect whether a QueryOptions object uses Cube-native fully-qualified names
6
- * (i.e. "view.field" dot notation) rather than short names that need a cube prefix.
7
- */
8
- export function isCubeNativeFormat(options) {
9
- const fields = [
10
- ...(options.measures ?? []),
11
- ...(options.dimensions ?? []),
12
- ...(options.filters?.map(f => f.dimension) ?? []),
13
- ...(options.timeDimension ? [options.timeDimension.dimension] : []),
14
- ];
15
- return fields.some(f => f.includes('.'));
16
- }
17
4
  /**
18
5
  * Convert SDK QueryOptions into a Cube-native query object.
19
- * Handles cube-name prefixing for all field references.
6
+ * All field names must be fully qualified (e.g. "orders.revenue").
20
7
  */
21
- export function buildCubeQuery(options) {
8
+ export function toCubeQuery(options) {
22
9
  const cubeQuery = {};
23
10
  if (options.measures) {
24
- cubeQuery.measures = options.measures.map(m => m.includes('.') ? m : `${options.cube}.${m}`);
11
+ cubeQuery.measures = options.measures;
25
12
  }
26
13
  if (options.dimensions) {
27
- cubeQuery.dimensions = options.dimensions.map(d => d.includes('.') ? d : `${options.cube}.${d}`);
14
+ cubeQuery.dimensions = options.dimensions;
28
15
  }
29
16
  if (options.filters) {
30
17
  cubeQuery.filters = options.filters.map(f => ({
31
- dimension: f.dimension.includes('.') ? f.dimension : `${options.cube}.${f.dimension}`,
18
+ member: f.dimension,
32
19
  operator: f.operator,
33
20
  values: f.values,
34
21
  }));
35
22
  }
36
23
  if (options.timeDimension) {
37
24
  cubeQuery.timeDimensions = [{
38
- dimension: options.timeDimension.dimension.includes('.')
39
- ? options.timeDimension.dimension
40
- : `${options.cube}.${options.timeDimension.dimension}`,
25
+ dimension: options.timeDimension.dimension,
41
26
  granularity: options.timeDimension.granularity,
42
27
  dateRange: options.timeDimension.dateRange,
43
28
  }];
44
29
  }
45
30
  if (options.orderBy) {
46
- cubeQuery.order = Object.entries(options.orderBy).map(([key, dir]) => [
47
- key.includes('.') ? key : `${options.cube}.${key}`,
48
- dir,
49
- ]);
31
+ cubeQuery.order = Object.entries(options.orderBy).map(([key, dir]) => [key, dir]);
50
32
  }
51
33
  if (options.limit) {
52
34
  cubeQuery.limit = options.limit;
53
35
  }
54
36
  return cubeQuery;
55
37
  }
56
- /**
57
- * Simplify Cube response keys by removing the cube prefix.
58
- * e.g. { "orders.revenue": 100 } → { "revenue": 100 }
59
- */
60
- export function simplifyResult(data) {
61
- return data.map(row => {
62
- const simplified = {};
63
- for (const [key, value] of Object.entries(row)) {
64
- const simpleName = key.includes('.') ? key.split('.').pop() : key;
65
- simplified[simpleName] = value;
66
- }
67
- return simplified;
68
- });
69
- }
package/dist/types.d.ts CHANGED
@@ -7,7 +7,6 @@ export interface BonnardConfig {
7
7
  baseUrl?: string;
8
8
  }
9
9
  export interface QueryOptions {
10
- cube?: string;
11
10
  measures?: string[];
12
11
  dimensions?: string[];
13
12
  filters?: Filter[];
@@ -115,3 +114,23 @@ export interface DashboardListResult {
115
114
  updated_at: string;
116
115
  }>;
117
116
  }
117
+ export interface DocsOptions {
118
+ topic?: string;
119
+ category?: string;
120
+ }
121
+ export interface DocsTopicSummary {
122
+ id: string;
123
+ title: string;
124
+ description: string | null;
125
+ category: string;
126
+ }
127
+ export interface DocsTopicListResult {
128
+ topics: DocsTopicSummary[];
129
+ }
130
+ export interface DocsTopicResult {
131
+ topic: {
132
+ id: string;
133
+ title: string;
134
+ content: string;
135
+ };
136
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonnard/sdk",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Bonnard SDK - query your semantic layer from any JavaScript or TypeScript app",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",