@bonnard/cli 0.2.14 → 0.2.16
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 +2 -2
- package/dist/bin/bon.mjs +173 -2
- package/dist/docs/topics/sdk.apexcharts.md +281 -0
- package/dist/docs/topics/sdk.authentication.md +130 -0
- package/dist/docs/topics/sdk.browser.md +181 -0
- package/dist/docs/topics/sdk.chartjs.md +327 -0
- package/dist/docs/topics/sdk.echarts.md +297 -0
- package/dist/docs/topics/sdk.md +95 -0
- package/dist/docs/topics/sdk.query-reference.md +307 -0
- package/dist/templates/claude/skills/bonnard-build-dashboard/SKILL.md +107 -0
- package/dist/templates/claude/skills/bonnard-get-started/SKILL.md +1 -1
- package/dist/templates/claude/skills/bonnard-metabase-migrate/SKILL.md +1 -1
- package/dist/templates/cursor/rules/bonnard-build-dashboard.mdc +106 -0
- package/dist/templates/cursor/rules/bonnard-get-started.mdc +1 -1
- package/dist/templates/cursor/rules/bonnard-metabase-migrate.mdc +1 -1
- package/dist/templates/shared/bonnard.md +6 -1
- package/package.json +2 -2
- package/dist/docs/topics/dashboards.components.md +0 -227
- package/dist/docs/topics/dashboards.examples.md +0 -270
- package/dist/docs/topics/dashboards.inputs.md +0 -173
- package/dist/docs/topics/dashboards.md +0 -115
- package/dist/docs/topics/dashboards.queries.md +0 -112
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# ECharts + Bonnard SDK
|
|
2
|
+
|
|
3
|
+
> Build HTML dashboards with Apache ECharts and the Bonnard SDK. No build step required.
|
|
4
|
+
|
|
5
|
+
ECharts offers rich interactivity (tooltips, zoom, legend toggling), built-in dark theme, and extensive chart types. Larger payload than Chart.js (~160KB gzip) but more powerful.
|
|
6
|
+
|
|
7
|
+
## Starter template
|
|
8
|
+
|
|
9
|
+
Copy this complete HTML file as a starting point. Replace `bon_pk_YOUR_KEY_HERE` with your publishable API key, and update the view/measure/dimension names to match your schema.
|
|
10
|
+
|
|
11
|
+
Use `explore()` to discover available views and fields — see [sdk.query-reference](sdk.query-reference).
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!DOCTYPE html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
<title>Dashboard</title>
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
|
|
21
|
+
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
|
|
22
|
+
<style>
|
|
23
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
24
|
+
body {
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
26
|
+
background: #09090b; color: #fafafa; padding: 24px;
|
|
27
|
+
}
|
|
28
|
+
h1 { font-size: 24px; font-weight: 600; margin-bottom: 24px; }
|
|
29
|
+
.error { color: #ef4444; background: #1c0a0a; padding: 12px; border-radius: 8px; margin-bottom: 16px; display: none; }
|
|
30
|
+
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 32px; }
|
|
31
|
+
.kpi { background: #18181b; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
|
|
32
|
+
.kpi-label { font-size: 14px; color: #a1a1aa; margin-bottom: 8px; }
|
|
33
|
+
.kpi-value { font-size: 32px; font-weight: 600; }
|
|
34
|
+
.kpi-value.loading { color: #3f3f46; }
|
|
35
|
+
.charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 24px; }
|
|
36
|
+
.chart-card { background: #18181b; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
|
|
37
|
+
.chart-title { font-size: 16px; font-weight: 500; margin-bottom: 16px; }
|
|
38
|
+
.chart-container { height: 300px; }
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<h1>Dashboard</h1>
|
|
43
|
+
<div id="error" class="error"></div>
|
|
44
|
+
|
|
45
|
+
<div class="kpis">
|
|
46
|
+
<div class="kpi">
|
|
47
|
+
<div class="kpi-label">Revenue</div>
|
|
48
|
+
<div class="kpi-value loading" id="kpi-revenue">--</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="kpi">
|
|
51
|
+
<div class="kpi-label">Orders</div>
|
|
52
|
+
<div class="kpi-value loading" id="kpi-orders">--</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="kpi">
|
|
55
|
+
<div class="kpi-label">Avg Value</div>
|
|
56
|
+
<div class="kpi-value loading" id="kpi-avg">--</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="charts">
|
|
61
|
+
<div class="chart-card">
|
|
62
|
+
<div class="chart-title">Revenue by City</div>
|
|
63
|
+
<div class="chart-container" id="bar-chart"></div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="chart-card">
|
|
66
|
+
<div class="chart-title">Revenue Trend</div>
|
|
67
|
+
<div class="chart-container" id="line-chart"></div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<script>
|
|
72
|
+
const bon = Bonnard.createClient({
|
|
73
|
+
apiKey: 'bon_pk_YOUR_KEY_HERE',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// --- Helpers ---
|
|
77
|
+
function showError(msg) {
|
|
78
|
+
const el = document.getElementById('error');
|
|
79
|
+
el.textContent = msg;
|
|
80
|
+
el.style.display = 'block';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function formatNumber(v) {
|
|
84
|
+
return new Intl.NumberFormat().format(v);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatCurrency(v) {
|
|
88
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(v);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Resize all charts on window resize
|
|
92
|
+
const charts = [];
|
|
93
|
+
window.addEventListener('resize', () => charts.forEach(c => c.resize()));
|
|
94
|
+
|
|
95
|
+
// --- Load data ---
|
|
96
|
+
(async () => {
|
|
97
|
+
try {
|
|
98
|
+
// KPIs
|
|
99
|
+
const kpis = await bon.query({
|
|
100
|
+
measures: ['orders.revenue', 'orders.count', 'orders.avg_value'],
|
|
101
|
+
});
|
|
102
|
+
if (kpis.data.length > 0) {
|
|
103
|
+
const row = kpis.data[0];
|
|
104
|
+
document.getElementById('kpi-revenue').textContent = formatCurrency(row['orders.revenue']);
|
|
105
|
+
document.getElementById('kpi-revenue').classList.remove('loading');
|
|
106
|
+
document.getElementById('kpi-orders').textContent = formatNumber(row['orders.count']);
|
|
107
|
+
document.getElementById('kpi-orders').classList.remove('loading');
|
|
108
|
+
document.getElementById('kpi-avg').textContent = formatCurrency(row['orders.avg_value']);
|
|
109
|
+
document.getElementById('kpi-avg').classList.remove('loading');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Bar chart — revenue by city
|
|
113
|
+
const byCity = await bon.query({
|
|
114
|
+
measures: ['orders.revenue'],
|
|
115
|
+
dimensions: ['orders.city'],
|
|
116
|
+
orderBy: { 'orders.revenue': 'desc' },
|
|
117
|
+
limit: 10,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const barChart = echarts.init(document.getElementById('bar-chart'), 'dark');
|
|
121
|
+
charts.push(barChart);
|
|
122
|
+
barChart.setOption({
|
|
123
|
+
tooltip: { trigger: 'axis', formatter: p => `${p[0].name}: ${formatCurrency(p[0].value)}` },
|
|
124
|
+
xAxis: { type: 'category', data: byCity.data.map(d => d['orders.city']) },
|
|
125
|
+
yAxis: { type: 'value', axisLabel: { formatter: v => formatCurrency(v) } },
|
|
126
|
+
series: [{
|
|
127
|
+
type: 'bar',
|
|
128
|
+
data: byCity.data.map(d => d['orders.revenue']),
|
|
129
|
+
itemStyle: { color: '#3b82f6', borderRadius: [4, 4, 0, 0] },
|
|
130
|
+
}],
|
|
131
|
+
grid: { left: 80, right: 20, top: 20, bottom: 40 },
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Line chart — revenue trend
|
|
135
|
+
const trend = await bon.query({
|
|
136
|
+
measures: ['orders.revenue'],
|
|
137
|
+
timeDimension: {
|
|
138
|
+
dimension: 'orders.created_at',
|
|
139
|
+
granularity: 'month',
|
|
140
|
+
dateRange: 'last 12 months',
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const lineChart = echarts.init(document.getElementById('line-chart'), 'dark');
|
|
145
|
+
charts.push(lineChart);
|
|
146
|
+
lineChart.setOption({
|
|
147
|
+
tooltip: { trigger: 'axis', formatter: p => `${p[0].name}: ${formatCurrency(p[0].value)}` },
|
|
148
|
+
xAxis: {
|
|
149
|
+
type: 'category',
|
|
150
|
+
data: trend.data.map(d => {
|
|
151
|
+
const date = new Date(d['orders.created_at']);
|
|
152
|
+
return date.toLocaleDateString('en-US', { month: 'short', year: '2-digit' });
|
|
153
|
+
}),
|
|
154
|
+
},
|
|
155
|
+
yAxis: { type: 'value', axisLabel: { formatter: v => formatCurrency(v) } },
|
|
156
|
+
series: [{
|
|
157
|
+
type: 'line',
|
|
158
|
+
data: trend.data.map(d => d['orders.revenue']),
|
|
159
|
+
smooth: true,
|
|
160
|
+
itemStyle: { color: '#3b82f6' },
|
|
161
|
+
areaStyle: { color: 'rgba(59, 130, 246, 0.15)' },
|
|
162
|
+
}],
|
|
163
|
+
grid: { left: 80, right: 20, top: 20, bottom: 40 },
|
|
164
|
+
});
|
|
165
|
+
} catch (err) {
|
|
166
|
+
showError('Failed to load dashboard: ' + err.message);
|
|
167
|
+
}
|
|
168
|
+
})();
|
|
169
|
+
</script>
|
|
170
|
+
</body>
|
|
171
|
+
</html>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Chart types
|
|
175
|
+
|
|
176
|
+
### Bar chart
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
const chart = echarts.init(el, 'dark');
|
|
180
|
+
chart.setOption({
|
|
181
|
+
xAxis: { type: 'category', data: data.map(d => d['view.dimension']) },
|
|
182
|
+
yAxis: { type: 'value' },
|
|
183
|
+
series: [{ type: 'bar', data: data.map(d => d['view.measure']) }],
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Horizontal bar chart
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
chart.setOption({
|
|
191
|
+
xAxis: { type: 'value' },
|
|
192
|
+
yAxis: { type: 'category', data: data.map(d => d['view.dimension']) },
|
|
193
|
+
series: [{ type: 'bar', data: data.map(d => d['view.measure']) }],
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Line chart
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
chart.setOption({
|
|
201
|
+
xAxis: { type: 'category', data: labels },
|
|
202
|
+
yAxis: { type: 'value' },
|
|
203
|
+
series: [{ type: 'line', data: values, smooth: true }],
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Area chart
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
series: [{
|
|
211
|
+
type: 'line',
|
|
212
|
+
data: values,
|
|
213
|
+
smooth: true,
|
|
214
|
+
areaStyle: { color: 'rgba(59, 130, 246, 0.15)' },
|
|
215
|
+
}]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Pie chart
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
chart.setOption({
|
|
222
|
+
tooltip: { trigger: 'item' },
|
|
223
|
+
series: [{
|
|
224
|
+
type: 'pie',
|
|
225
|
+
radius: ['40%', '70%'], // doughnut — use '60%' for full pie
|
|
226
|
+
data: data.map(d => ({
|
|
227
|
+
name: d['view.dimension'],
|
|
228
|
+
value: d['view.measure'],
|
|
229
|
+
})),
|
|
230
|
+
}],
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Multi-series line chart
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const cities = [...new Set(data.map(d => d['orders.city']))];
|
|
238
|
+
const dates = [...new Set(data.map(d => d['orders.created_at']))];
|
|
239
|
+
|
|
240
|
+
chart.setOption({
|
|
241
|
+
tooltip: { trigger: 'axis' },
|
|
242
|
+
legend: { data: cities },
|
|
243
|
+
xAxis: { type: 'category', data: dates.map(d => new Date(d).toLocaleDateString()) },
|
|
244
|
+
yAxis: { type: 'value' },
|
|
245
|
+
series: cities.map(city => ({
|
|
246
|
+
name: city,
|
|
247
|
+
type: 'line',
|
|
248
|
+
smooth: true,
|
|
249
|
+
data: dates.map(date =>
|
|
250
|
+
data.find(d => d['orders.city'] === city && d['orders.created_at'] === date)?.['orders.revenue'] || 0
|
|
251
|
+
),
|
|
252
|
+
})),
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Dark mode
|
|
257
|
+
|
|
258
|
+
ECharts has a built-in dark theme — pass `'dark'` as the second argument to `echarts.init()`:
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
const chart = echarts.init(document.getElementById('chart'), 'dark');
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
The dark theme sets appropriate background, text, axis, and tooltip colors automatically.
|
|
265
|
+
|
|
266
|
+
## Resize handling
|
|
267
|
+
|
|
268
|
+
ECharts charts don't auto-resize. Add a resize listener:
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
const charts = [];
|
|
272
|
+
|
|
273
|
+
// After creating each chart:
|
|
274
|
+
charts.push(chart);
|
|
275
|
+
|
|
276
|
+
// Global resize handler:
|
|
277
|
+
window.addEventListener('resize', () => charts.forEach(c => c.resize()));
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Color palette
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
const COLORS = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316'];
|
|
284
|
+
|
|
285
|
+
// Apply to a series:
|
|
286
|
+
series: data.map((d, i) => ({
|
|
287
|
+
// ...
|
|
288
|
+
itemStyle: { color: COLORS[i % COLORS.length] },
|
|
289
|
+
}))
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## See also
|
|
293
|
+
|
|
294
|
+
- [sdk.browser](sdk.browser) — Browser / CDN quickstart
|
|
295
|
+
- [sdk.query-reference](sdk.query-reference) — Full query API
|
|
296
|
+
- [sdk.chartjs](sdk.chartjs) — Chart.js alternative (smaller payload)
|
|
297
|
+
- [sdk.apexcharts](sdk.apexcharts) — ApexCharts alternative
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# SDK
|
|
2
|
+
|
|
3
|
+
> Build data apps and dashboards on top of your semantic layer.
|
|
4
|
+
|
|
5
|
+
The Bonnard SDK (`@bonnard/sdk`) is a lightweight TypeScript/JavaScript client for querying your deployed semantic layer. Zero dependencies, ~3KB minified — works in Node.js, browsers, and edge runtimes.
|
|
6
|
+
|
|
7
|
+
## Two ways to use it
|
|
8
|
+
|
|
9
|
+
### npm (TypeScript / Node.js / React)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @bonnard/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { createClient } from '@bonnard/sdk';
|
|
17
|
+
|
|
18
|
+
const bon = createClient({ apiKey: 'bon_pk_...' });
|
|
19
|
+
const { data } = await bon.query({
|
|
20
|
+
measures: ['orders.revenue'],
|
|
21
|
+
dimensions: ['orders.city'],
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### CDN (browser / HTML dashboards)
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
|
|
29
|
+
<script>
|
|
30
|
+
const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
|
|
31
|
+
</script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Exposes `window.Bonnard` with `createClient` and `toCubeQuery`. See [sdk.browser](sdk.browser) for the full browser quickstart.
|
|
35
|
+
|
|
36
|
+
## Authentication
|
|
37
|
+
|
|
38
|
+
Two modes depending on your use case:
|
|
39
|
+
|
|
40
|
+
| Mode | Key type | Use case |
|
|
41
|
+
|------|----------|----------|
|
|
42
|
+
| **Publishable key** | `bon_pk_...` | Public dashboards, client-side apps — safe to expose in HTML |
|
|
43
|
+
| **Token exchange** | `bon_sk_...` → JWT | Multi-tenant / embedded analytics — server exchanges secret key for scoped token |
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Simple: publishable key
|
|
47
|
+
const bon = createClient({ apiKey: 'bon_pk_...' });
|
|
48
|
+
|
|
49
|
+
// Multi-tenant: token exchange
|
|
50
|
+
const bon = createClient({
|
|
51
|
+
fetchToken: async () => {
|
|
52
|
+
const res = await fetch('/my-backend/bonnard-token');
|
|
53
|
+
const { token } = await res.json();
|
|
54
|
+
return token;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The SDK automatically caches tokens and refreshes them 60 seconds before expiry.
|
|
60
|
+
|
|
61
|
+
See [sdk.authentication](sdk.authentication) for the full auth guide.
|
|
62
|
+
|
|
63
|
+
## What you can query
|
|
64
|
+
|
|
65
|
+
| Method | Purpose |
|
|
66
|
+
|--------|---------|
|
|
67
|
+
| `query()` | JSON query — measures, dimensions, filters, time dimensions |
|
|
68
|
+
| `rawQuery()` | Pass a Cube-native query object directly |
|
|
69
|
+
| `sql()` | SQL with `MEASURE()` syntax |
|
|
70
|
+
| `explore()` | Discover available views, measures, and dimensions |
|
|
71
|
+
|
|
72
|
+
See [sdk.query-reference](sdk.query-reference) for the full API reference.
|
|
73
|
+
|
|
74
|
+
## Building HTML dashboards
|
|
75
|
+
|
|
76
|
+
The SDK pairs with any chart library for single-file HTML dashboards. No build step required — load both from CDN and start querying.
|
|
77
|
+
|
|
78
|
+
| Library | CDN size (gzip) | Best for |
|
|
79
|
+
|---------|----------------|----------|
|
|
80
|
+
| **Chart.js** | ~65 KB | Default choice — most LLM training data, smallest payload |
|
|
81
|
+
| **ECharts** | ~160 KB | Rich interactivity, built-in dark theme |
|
|
82
|
+
| **ApexCharts** | ~130 KB | Best defaults out of box, SVG rendering |
|
|
83
|
+
|
|
84
|
+
Each guide includes a complete, copy-pasteable HTML starter template:
|
|
85
|
+
|
|
86
|
+
- [sdk.chartjs](sdk.chartjs) — Chart.js + SDK
|
|
87
|
+
- [sdk.echarts](sdk.echarts) — ECharts + SDK
|
|
88
|
+
- [sdk.apexcharts](sdk.apexcharts) — ApexCharts + SDK
|
|
89
|
+
|
|
90
|
+
## See also
|
|
91
|
+
|
|
92
|
+
- [sdk.browser](sdk.browser) — Browser / CDN quickstart
|
|
93
|
+
- [sdk.query-reference](sdk.query-reference) — Full query API reference
|
|
94
|
+
- [sdk.authentication](sdk.authentication) — Auth patterns
|
|
95
|
+
- [security-context](security-context) — Row-level security for multi-tenant apps
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Query API Reference
|
|
2
|
+
|
|
3
|
+
> Complete reference for querying the Bonnard semantic layer via the SDK.
|
|
4
|
+
|
|
5
|
+
## query()
|
|
6
|
+
|
|
7
|
+
Execute a JSON query against the semantic layer. All field names must be fully qualified (e.g. `orders.revenue`, not `revenue`).
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const { data } = await bon.query({
|
|
11
|
+
measures: ['orders.revenue', 'orders.count'],
|
|
12
|
+
dimensions: ['orders.city'],
|
|
13
|
+
filters: [
|
|
14
|
+
{ dimension: 'orders.status', operator: 'equals', values: ['completed'] },
|
|
15
|
+
],
|
|
16
|
+
timeDimension: {
|
|
17
|
+
dimension: 'orders.created_at',
|
|
18
|
+
granularity: 'month',
|
|
19
|
+
dateRange: ['2025-01-01', '2025-12-31'],
|
|
20
|
+
},
|
|
21
|
+
orderBy: { 'orders.revenue': 'desc' },
|
|
22
|
+
limit: 10,
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Parameters
|
|
27
|
+
|
|
28
|
+
| Parameter | Type | Required | Description |
|
|
29
|
+
|-----------|------|----------|-------------|
|
|
30
|
+
| `measures` | `string[]` | No | Numeric aggregations to compute (e.g. `['orders.revenue']`) |
|
|
31
|
+
| `dimensions` | `string[]` | No | Group-by columns (e.g. `['orders.city']`) |
|
|
32
|
+
| `filters` | `Filter[]` | No | Row-level filters |
|
|
33
|
+
| `timeDimension` | `TimeDimension` | No | Time-based grouping and date range |
|
|
34
|
+
| `orderBy` | `Record<string, 'asc' \| 'desc'>` | No | Sort order |
|
|
35
|
+
| `limit` | `number` | No | Max rows to return |
|
|
36
|
+
|
|
37
|
+
At least one of `measures` or `dimensions` is required.
|
|
38
|
+
|
|
39
|
+
### Response
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
{
|
|
43
|
+
data: [
|
|
44
|
+
{ "orders.revenue": 125000, "orders.count": 340, "orders.city": "Berlin" },
|
|
45
|
+
{ "orders.revenue": 98000, "orders.count": 280, "orders.city": "Munich" },
|
|
46
|
+
],
|
|
47
|
+
annotation: {
|
|
48
|
+
measures: {
|
|
49
|
+
"orders.revenue": { title: "Revenue", type: "number" },
|
|
50
|
+
"orders.count": { title: "Count", type: "number" },
|
|
51
|
+
},
|
|
52
|
+
dimensions: {
|
|
53
|
+
"orders.city": { title: "City", type: "string" },
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- `data` — array of result rows, keyed by fully qualified field names
|
|
60
|
+
- `annotation` — optional metadata with field titles and types
|
|
61
|
+
|
|
62
|
+
## Filters
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
filters: [
|
|
66
|
+
{ dimension: 'orders.status', operator: 'equals', values: ['completed'] },
|
|
67
|
+
{ dimension: 'orders.revenue', operator: 'gt', values: [1000] },
|
|
68
|
+
]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Filter operators
|
|
72
|
+
|
|
73
|
+
| Operator | Description | Example values |
|
|
74
|
+
|----------|-------------|---------------|
|
|
75
|
+
| `equals` | Exact match (any of values) | `['completed', 'shipped']` |
|
|
76
|
+
| `notEquals` | Exclude matches | `['cancelled']` |
|
|
77
|
+
| `contains` | Substring match | `['berlin']` |
|
|
78
|
+
| `gt` | Greater than | `[1000]` |
|
|
79
|
+
| `gte` | Greater than or equal | `[1000]` |
|
|
80
|
+
| `lt` | Less than | `[100]` |
|
|
81
|
+
| `lte` | Less than or equal | `[100]` |
|
|
82
|
+
|
|
83
|
+
## Time dimensions
|
|
84
|
+
|
|
85
|
+
Group data by time periods and filter by date range.
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
timeDimension: {
|
|
89
|
+
dimension: 'orders.created_at',
|
|
90
|
+
granularity: 'month',
|
|
91
|
+
dateRange: ['2025-01-01', '2025-06-30'],
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Granularities
|
|
96
|
+
|
|
97
|
+
| Granularity | Groups by |
|
|
98
|
+
|-------------|-----------|
|
|
99
|
+
| `day` | Calendar day |
|
|
100
|
+
| `week` | ISO week (Monday start) |
|
|
101
|
+
| `month` | Calendar month |
|
|
102
|
+
| `quarter` | Calendar quarter |
|
|
103
|
+
| `year` | Calendar year |
|
|
104
|
+
|
|
105
|
+
Omit `granularity` to filter by date range without time grouping.
|
|
106
|
+
|
|
107
|
+
### Date range formats
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// ISO date strings (tuple)
|
|
111
|
+
dateRange: ['2025-01-01', '2025-12-31']
|
|
112
|
+
|
|
113
|
+
// Single string (relative)
|
|
114
|
+
dateRange: 'last 30 days'
|
|
115
|
+
dateRange: 'last 6 months'
|
|
116
|
+
dateRange: 'this year'
|
|
117
|
+
dateRange: 'last year'
|
|
118
|
+
dateRange: 'today'
|
|
119
|
+
dateRange: 'yesterday'
|
|
120
|
+
dateRange: 'last week'
|
|
121
|
+
dateRange: 'last month'
|
|
122
|
+
dateRange: 'last quarter'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Ordering and pagination
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
// Sort by revenue descending
|
|
129
|
+
orderBy: { 'orders.revenue': 'desc' }
|
|
130
|
+
|
|
131
|
+
// Multiple sort keys
|
|
132
|
+
orderBy: { 'orders.city': 'asc', 'orders.revenue': 'desc' }
|
|
133
|
+
|
|
134
|
+
// Limit results
|
|
135
|
+
limit: 10
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## rawQuery()
|
|
139
|
+
|
|
140
|
+
Pass a Cube-native query object directly, bypassing the SDK's query format conversion. Use when you need features not exposed by `query()` (e.g. segments, offset).
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
const { data } = await bon.rawQuery({
|
|
144
|
+
measures: ['orders.revenue'],
|
|
145
|
+
dimensions: ['orders.city'],
|
|
146
|
+
order: [['orders.revenue', 'desc']],
|
|
147
|
+
limit: 10,
|
|
148
|
+
offset: 20,
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## sql()
|
|
153
|
+
|
|
154
|
+
Execute a SQL query using Cube's SQL API. Use `MEASURE()` to reference semantic layer measures.
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const { data } = await bon.sql(
|
|
158
|
+
`SELECT city, MEASURE(revenue), MEASURE(count)
|
|
159
|
+
FROM orders
|
|
160
|
+
WHERE status = 'completed'
|
|
161
|
+
GROUP BY 1
|
|
162
|
+
ORDER BY 2 DESC
|
|
163
|
+
LIMIT 10`
|
|
164
|
+
);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Response includes an optional schema:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
{
|
|
171
|
+
data: [
|
|
172
|
+
{ city: "Berlin", revenue: 125000, count: 340 },
|
|
173
|
+
],
|
|
174
|
+
schema: [
|
|
175
|
+
{ name: "city", type: "string" },
|
|
176
|
+
{ name: "revenue", type: "number" },
|
|
177
|
+
{ name: "count", type: "number" },
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## explore()
|
|
183
|
+
|
|
184
|
+
Discover available views, measures, dimensions, and segments.
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
const meta = await bon.explore();
|
|
188
|
+
|
|
189
|
+
for (const view of meta.cubes) {
|
|
190
|
+
console.log(`${view.name}: ${view.description || ''}`);
|
|
191
|
+
for (const m of view.measures) {
|
|
192
|
+
console.log(` measure: ${m.name} (${m.type})`);
|
|
193
|
+
}
|
|
194
|
+
for (const d of view.dimensions) {
|
|
195
|
+
console.log(` dimension: ${d.name} (${d.type})`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
By default returns only views (`viewsOnly: true`). To include underlying cubes:
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
const meta = await bon.explore({ viewsOnly: false });
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Response shape
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
{
|
|
210
|
+
cubes: [
|
|
211
|
+
{
|
|
212
|
+
name: "orders",
|
|
213
|
+
title: "Orders",
|
|
214
|
+
description: "Customer orders",
|
|
215
|
+
type: "view",
|
|
216
|
+
measures: [
|
|
217
|
+
{ name: "orders.revenue", title: "Revenue", type: "number" },
|
|
218
|
+
{ name: "orders.count", title: "Count", type: "number" },
|
|
219
|
+
],
|
|
220
|
+
dimensions: [
|
|
221
|
+
{ name: "orders.city", title: "City", type: "string" },
|
|
222
|
+
{ name: "orders.created_at", title: "Created At", type: "time" },
|
|
223
|
+
],
|
|
224
|
+
segments: [],
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## toCubeQuery()
|
|
231
|
+
|
|
232
|
+
Utility function that converts SDK `QueryOptions` into a Cube-native query object. Useful for debugging or when you need to inspect the query before sending.
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
import { toCubeQuery } from '@bonnard/sdk';
|
|
236
|
+
// or in browser: Bonnard.toCubeQuery(...)
|
|
237
|
+
|
|
238
|
+
const cubeQuery = Bonnard.toCubeQuery({
|
|
239
|
+
measures: ['orders.revenue'],
|
|
240
|
+
dimensions: ['orders.city'],
|
|
241
|
+
orderBy: { 'orders.revenue': 'desc' },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
console.log(JSON.stringify(cubeQuery, null, 2));
|
|
245
|
+
// {
|
|
246
|
+
// "measures": ["orders.revenue"],
|
|
247
|
+
// "dimensions": ["orders.city"],
|
|
248
|
+
// "order": [["orders.revenue", "desc"]]
|
|
249
|
+
// }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Common patterns
|
|
253
|
+
|
|
254
|
+
### KPI query (single row, no dimensions)
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
const { data } = await bon.query({
|
|
258
|
+
measures: ['orders.revenue', 'orders.count', 'orders.avg_value'],
|
|
259
|
+
});
|
|
260
|
+
const kpis = data[0];
|
|
261
|
+
// { "orders.revenue": 1250000, "orders.count": 3400, "orders.avg_value": 367.6 }
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Top N with dimension
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
const { data } = await bon.query({
|
|
268
|
+
measures: ['orders.revenue'],
|
|
269
|
+
dimensions: ['orders.city'],
|
|
270
|
+
orderBy: { 'orders.revenue': 'desc' },
|
|
271
|
+
limit: 10,
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Time series
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
const { data } = await bon.query({
|
|
279
|
+
measures: ['orders.revenue'],
|
|
280
|
+
timeDimension: {
|
|
281
|
+
dimension: 'orders.created_at',
|
|
282
|
+
granularity: 'month',
|
|
283
|
+
dateRange: 'last 12 months',
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
// data[0]["orders.created_at"] is an ISO date string like "2025-01-01T00:00:00.000"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Filtered breakdown
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
const { data } = await bon.query({
|
|
293
|
+
measures: ['orders.revenue'],
|
|
294
|
+
dimensions: ['orders.product_category'],
|
|
295
|
+
filters: [
|
|
296
|
+
{ dimension: 'orders.city', operator: 'equals', values: ['Berlin'] },
|
|
297
|
+
{ dimension: 'orders.revenue', operator: 'gt', values: [100] },
|
|
298
|
+
],
|
|
299
|
+
orderBy: { 'orders.revenue': 'desc' },
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## See also
|
|
304
|
+
|
|
305
|
+
- [sdk.browser](sdk.browser) — Browser / CDN quickstart
|
|
306
|
+
- [sdk.authentication](sdk.authentication) — Auth patterns
|
|
307
|
+
- [sdk.chartjs](sdk.chartjs) — Building dashboards with Chart.js
|