@bonnard/sdk 0.1.2 → 0.2.1

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/dist/client.d.ts CHANGED
@@ -1,14 +1,36 @@
1
1
  /**
2
2
  * Bonnard SDK — Client for querying semantic layer
3
3
  */
4
- import type { BonnardConfig, QueryOptions, QueryResult, SqlResult } from './types.js';
4
+ import type { BonnardConfig, QueryOptions, QueryResult, SqlResult, CubeQuery, ExploreMeta, ExploreOptions, DashboardResult, DashboardListResult } from './types.js';
5
5
  export declare function createClient(config: BonnardConfig): {
6
6
  /**
7
- * Execute a JSON query against the semantic layer
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
12
  */
9
13
  query<T = Record<string, unknown>>(options: QueryOptions): Promise<QueryResult<T>>;
14
+ /**
15
+ * Execute a raw Cube-native JSON query against the semantic layer.
16
+ * Use this when you already have a Cube API query object.
17
+ */
18
+ rawQuery<T = Record<string, unknown>>(cubeQuery: CubeQuery): Promise<QueryResult<T>>;
10
19
  /**
11
20
  * Execute a SQL query against the semantic layer
12
21
  */
13
22
  sql<T = Record<string, unknown>>(query: string): Promise<SqlResult<T>>;
23
+ /**
24
+ * Discover available cubes, measures, dimensions, and segments.
25
+ * By default returns only views (viewsOnly: true).
26
+ */
27
+ explore(options?: ExploreOptions): Promise<ExploreMeta>;
28
+ /**
29
+ * Fetch a single dashboard by slug.
30
+ */
31
+ getDashboard(slug: string): Promise<DashboardResult>;
32
+ /**
33
+ * List all dashboards for the current organization.
34
+ */
35
+ listDashboards(): Promise<DashboardListResult>;
14
36
  };
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 } from './query.js';
4
+ import { buildCubeQuery, simplifyResult, isCubeNativeFormat } 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.
@@ -22,14 +22,6 @@ function parseJwtExpiry(token) {
22
22
  }
23
23
  const REFRESH_BUFFER_MS = 60_000; // refresh 60s before expiry
24
24
  export function createClient(config) {
25
- const hasApiKey = !!config.apiKey;
26
- const hasFetchToken = !!config.fetchToken;
27
- if (!hasApiKey && !hasFetchToken) {
28
- throw new Error('BonnardConfig requires either apiKey or fetchToken');
29
- }
30
- if (hasApiKey && hasFetchToken) {
31
- throw new Error('BonnardConfig requires either apiKey or fetchToken, not both');
32
- }
33
25
  const baseUrl = config.baseUrl || 'https://app.bonnard.dev';
34
26
  // Token cache for fetchToken mode
35
27
  let cachedToken = null;
@@ -40,14 +32,17 @@ export function createClient(config) {
40
32
  return config.apiKey;
41
33
  }
42
34
  // Token callback — cache and refresh
43
- const now = Date.now();
44
- if (cachedToken && cachedExpiry - REFRESH_BUFFER_MS > now) {
45
- return cachedToken;
35
+ if (config.fetchToken) {
36
+ const now = Date.now();
37
+ if (cachedToken && cachedExpiry - REFRESH_BUFFER_MS > now) {
38
+ return cachedToken;
39
+ }
40
+ const token = await config.fetchToken();
41
+ cachedToken = token;
42
+ cachedExpiry = parseJwtExpiry(token);
43
+ return token;
46
44
  }
47
- const token = await config.fetchToken();
48
- cachedToken = token;
49
- cachedExpiry = parseJwtExpiry(token);
50
- return token;
45
+ throw new Error('BonnardConfig requires either apiKey or fetchToken');
51
46
  }
52
47
  async function request(endpoint, body) {
53
48
  const token = await getToken();
@@ -65,12 +60,82 @@ export function createClient(config) {
65
60
  }
66
61
  return res.json();
67
62
  }
63
+ async function requestGet(endpoint) {
64
+ const token = await getToken();
65
+ const res = await fetch(`${baseUrl}${endpoint}`, {
66
+ method: 'GET',
67
+ headers: {
68
+ 'Authorization': `Bearer ${token}`,
69
+ },
70
+ });
71
+ if (!res.ok) {
72
+ const error = await res.json().catch(() => ({ error: res.statusText }));
73
+ throw new Error(error.error || 'Request failed');
74
+ }
75
+ return res.json();
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
+ }
68
110
  return {
69
111
  /**
70
- * Execute a JSON query against the semantic layer
112
+ * 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"`)
71
117
  */
72
118
  async query(options) {
73
- const cubeQuery = buildCubeQuery(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
+ }
130
+ const result = await request('/api/cube/query', { query: cubeQuery });
131
+ const simplifiedData = simplifyResult(result.data);
132
+ return { data: simplifiedData, annotation: result.annotation };
133
+ },
134
+ /**
135
+ * Execute a raw Cube-native JSON query against the semantic layer.
136
+ * Use this when you already have a Cube API query object.
137
+ */
138
+ async rawQuery(cubeQuery) {
74
139
  const result = await request('/api/cube/query', { query: cubeQuery });
75
140
  const simplifiedData = simplifyResult(result.data);
76
141
  return { data: simplifiedData, annotation: result.annotation };
@@ -81,5 +146,29 @@ export function createClient(config) {
81
146
  async sql(query) {
82
147
  return request('/api/cube/query', { sql: query });
83
148
  },
149
+ /**
150
+ * Discover available cubes, measures, dimensions, and segments.
151
+ * By default returns only views (viewsOnly: true).
152
+ */
153
+ async explore(options) {
154
+ const meta = await requestGet('/api/cube/meta');
155
+ const viewsOnly = options?.viewsOnly ?? true;
156
+ if (viewsOnly) {
157
+ return { cubes: meta.cubes.filter(c => c.type === 'view') };
158
+ }
159
+ return meta;
160
+ },
161
+ /**
162
+ * Fetch a single dashboard by slug.
163
+ */
164
+ async getDashboard(slug) {
165
+ return requestGet(`/api/dashboards/${encodeURIComponent(slug)}`);
166
+ },
167
+ /**
168
+ * List all dashboards for the current organization.
169
+ */
170
+ async listDashboards() {
171
+ return requestGet('/api/dashboards');
172
+ },
84
173
  };
85
174
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { createClient } from './client.js';
2
- export { buildCubeQuery, simplifyResult } from './query.js';
3
- export type { BonnardConfig, QueryOptions, QueryResult, SqlResult, Filter, TimeDimension, InferQueryResult, } from './types.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';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { createClient } from './client.js';
2
- export { buildCubeQuery, simplifyResult } from './query.js';
2
+ export { buildCubeQuery, simplifyResult, isCubeNativeFormat } from './query.js';
package/dist/query.d.ts CHANGED
@@ -2,6 +2,11 @@
2
2
  * Bonnard SDK — Pure query-building functions (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;
5
10
  /**
6
11
  * Convert SDK QueryOptions into a Cube-native query object.
7
12
  * Handles cube-name prefixing for all field references.
package/dist/query.js CHANGED
@@ -1,6 +1,19 @@
1
1
  /**
2
2
  * Bonnard SDK — Pure query-building functions (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
+ }
4
17
  /**
5
18
  * Convert SDK QueryOptions into a Cube-native query object.
6
19
  * Handles cube-name prefixing for all field references.
package/dist/types.d.ts CHANGED
@@ -7,7 +7,7 @@ export interface BonnardConfig {
7
7
  baseUrl?: string;
8
8
  }
9
9
  export interface QueryOptions {
10
- cube: string;
10
+ cube?: string;
11
11
  measures?: string[];
12
12
  dimensions?: string[];
13
13
  filters?: Filter[];
@@ -15,6 +15,24 @@ export interface QueryOptions {
15
15
  orderBy?: Record<string, 'asc' | 'desc'>;
16
16
  limit?: number;
17
17
  }
18
+ export interface CubeQuery {
19
+ measures?: string[];
20
+ dimensions?: string[];
21
+ timeDimensions?: Array<{
22
+ dimension: string;
23
+ granularity?: 'day' | 'week' | 'month' | 'quarter' | 'year';
24
+ dateRange?: string | [string, string];
25
+ }>;
26
+ filters?: Array<{
27
+ member: string;
28
+ operator: string;
29
+ values?: (string | number)[];
30
+ }>;
31
+ segments?: string[];
32
+ order?: Record<string, 'asc' | 'desc'> | Array<[string, 'asc' | 'desc']>;
33
+ limit?: number;
34
+ offset?: number;
35
+ }
18
36
  export interface Filter {
19
37
  dimension: string;
20
38
  operator: 'equals' | 'notEquals' | 'contains' | 'gt' | 'gte' | 'lt' | 'lte';
@@ -45,7 +63,55 @@ export interface SqlResult<T = Record<string, unknown>> {
45
63
  type: string;
46
64
  }>;
47
65
  }
66
+ export interface ExploreMeta {
67
+ cubes: CubeMetaItem[];
68
+ }
69
+ export interface CubeMetaItem {
70
+ name: string;
71
+ title?: string;
72
+ description?: string;
73
+ type?: string;
74
+ measures: CubeFieldMeta[];
75
+ dimensions: CubeFieldMeta[];
76
+ segments: CubeSegmentMeta[];
77
+ }
78
+ export interface CubeFieldMeta {
79
+ name: string;
80
+ title?: string;
81
+ shortTitle?: string;
82
+ description?: string;
83
+ type: string;
84
+ }
85
+ export interface CubeSegmentMeta {
86
+ name: string;
87
+ title?: string;
88
+ shortTitle?: string;
89
+ description?: string;
90
+ }
91
+ export interface ExploreOptions {
92
+ viewsOnly?: boolean;
93
+ }
48
94
  /** Type helper for defining cube schemas */
49
95
  export type InferQueryResult<C extends string, M extends string[], D extends string[]> = {
50
96
  [K in M[number] | D[number]]: K extends M[number] ? number : string;
51
97
  };
98
+ export interface DashboardResult {
99
+ dashboard: {
100
+ slug: string;
101
+ title: string;
102
+ description: string | null;
103
+ content: string;
104
+ version: number;
105
+ created_at: string;
106
+ updated_at: string;
107
+ };
108
+ }
109
+ export interface DashboardListResult {
110
+ dashboards: Array<{
111
+ slug: string;
112
+ title: string;
113
+ description: string | null;
114
+ version: number;
115
+ updated_at: string;
116
+ }>;
117
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonnard/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
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",