@bonnard/sdk 0.1.2 → 0.1.3
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 +16 -2
- package/dist/client.js +95 -18
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/query.d.ts +5 -0
- package/dist/query.js +13 -0
- package/dist/types.d.ts +47 -1
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
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 } 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>;
|
|
14
28
|
};
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,17 @@ 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
|
+
},
|
|
84
161
|
};
|
|
85
162
|
}
|
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, } 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
|
|
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,6 +63,34 @@ 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;
|