@bonnard/cli 0.2.15 → 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.
@@ -0,0 +1,181 @@
1
+ # Browser / CDN Quickstart
2
+
3
+ > Load the Bonnard SDK via a `<script>` tag and query your semantic layer from any HTML page.
4
+
5
+ ## Setup
6
+
7
+ Add the SDK script tag to your HTML. It exposes `window.Bonnard`.
8
+
9
+ ```html
10
+ <script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
11
+ ```
12
+
13
+ Alternative CDN:
14
+ ```html
15
+ <script src="https://unpkg.com/@bonnard/sdk/dist/bonnard.iife.js"></script>
16
+ ```
17
+
18
+ Pin a specific version:
19
+ ```html
20
+ <script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk@0.4.2/dist/bonnard.iife.js"></script>
21
+ ```
22
+
23
+ ## First query
24
+
25
+ ```html
26
+ <script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
27
+ <script>
28
+ const bon = Bonnard.createClient({
29
+ apiKey: 'bon_pk_YOUR_KEY_HERE',
30
+ });
31
+
32
+ (async () => {
33
+ const { data } = await bon.query({
34
+ measures: ['orders.revenue', 'orders.count'],
35
+ dimensions: ['orders.city'],
36
+ orderBy: { 'orders.revenue': 'desc' },
37
+ limit: 10,
38
+ });
39
+
40
+ console.log(data);
41
+ // [{ "orders.revenue": 125000, "orders.count": 340, "orders.city": "Berlin" }, ...]
42
+ })();
43
+ </script>
44
+ ```
45
+
46
+ Note: the IIFE bundle uses a regular `<script>` tag (not `type="module"`), so top-level `await` is not available. Wrap async code in an IIFE or use `.then()`.
47
+
48
+ ## Async patterns
49
+
50
+ ### IIFE wrapper (recommended)
51
+
52
+ ```html
53
+ <script>
54
+ (async () => {
55
+ const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
56
+ const { data } = await bon.query({ measures: ['orders.revenue'] });
57
+ document.getElementById('revenue').textContent = data[0]['orders.revenue'];
58
+ })();
59
+ </script>
60
+ ```
61
+
62
+ ### Promise chain
63
+
64
+ ```html
65
+ <script>
66
+ const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
67
+
68
+ bon.query({ measures: ['orders.revenue'] })
69
+ .then(({ data }) => {
70
+ document.getElementById('revenue').textContent = data[0]['orders.revenue'];
71
+ })
72
+ .catch(err => {
73
+ console.error('Query failed:', err.message);
74
+ });
75
+ </script>
76
+ ```
77
+
78
+ ### Parallel queries
79
+
80
+ ```html
81
+ <script>
82
+ (async () => {
83
+ const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
84
+
85
+ const [kpis, byCity] = await Promise.all([
86
+ bon.query({ measures: ['orders.revenue', 'orders.count'] }),
87
+ bon.query({
88
+ measures: ['orders.revenue'],
89
+ dimensions: ['orders.city'],
90
+ orderBy: { 'orders.revenue': 'desc' },
91
+ }),
92
+ ]);
93
+
94
+ // Render KPIs
95
+ document.getElementById('revenue').textContent = kpis.data[0]['orders.revenue'];
96
+ document.getElementById('count').textContent = kpis.data[0]['orders.count'];
97
+
98
+ // Render chart with byCity.data...
99
+ })();
100
+ </script>
101
+ ```
102
+
103
+ ## Error handling
104
+
105
+ ```html
106
+ <script>
107
+ (async () => {
108
+ const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
109
+
110
+ try {
111
+ const { data } = await bon.query({
112
+ measures: ['orders.revenue'],
113
+ });
114
+ renderDashboard(data);
115
+ } catch (err) {
116
+ document.getElementById('error').textContent = err.message;
117
+ document.getElementById('error').style.display = 'block';
118
+ }
119
+ })();
120
+ </script>
121
+ ```
122
+
123
+ Common errors:
124
+ - `"Unauthorized"` — invalid or expired API key
125
+ - `"Query failed"` — invalid measure/dimension names or query structure
126
+ - Network errors — API unreachable (CORS, connectivity)
127
+
128
+ ## Custom base URL
129
+
130
+ By default the SDK points to `https://app.bonnard.dev`. Override for self-hosted or preview deployments:
131
+
132
+ ```html
133
+ <script>
134
+ const bon = Bonnard.createClient({
135
+ apiKey: 'bon_pk_...',
136
+ baseUrl: 'https://your-deployment.vercel.app',
137
+ });
138
+ </script>
139
+ ```
140
+
141
+ ## What's on `window.Bonnard`
142
+
143
+ The IIFE bundle exposes two exports:
144
+
145
+ | Export | Purpose |
146
+ |--------|---------|
147
+ | `Bonnard.createClient(config)` | Create an SDK client instance |
148
+ | `Bonnard.toCubeQuery(options)` | Convert `QueryOptions` to a Cube-native query object (useful for debugging) |
149
+
150
+ ## Field naming
151
+
152
+ All field names must be fully qualified with the view name:
153
+
154
+ ```javascript
155
+ // Correct
156
+ bon.query({ measures: ['orders.revenue'], dimensions: ['orders.city'] });
157
+
158
+ // Wrong — will fail
159
+ bon.query({ measures: ['revenue'], dimensions: ['city'] });
160
+ ```
161
+
162
+ ## Discovering available fields
163
+
164
+ Use `explore()` to discover what views, measures, and dimensions are available:
165
+
166
+ ```javascript
167
+ const meta = await bon.explore();
168
+ for (const view of meta.cubes) {
169
+ console.log(view.name);
170
+ console.log(' Measures:', view.measures.map(m => m.name));
171
+ console.log(' Dimensions:', view.dimensions.map(d => d.name));
172
+ }
173
+ ```
174
+
175
+ ## Next steps
176
+
177
+ - [sdk.chartjs](sdk.chartjs) — Build a dashboard with Chart.js
178
+ - [sdk.echarts](sdk.echarts) — Build a dashboard with ECharts
179
+ - [sdk.apexcharts](sdk.apexcharts) — Build a dashboard with ApexCharts
180
+ - [sdk.query-reference](sdk.query-reference) — Full query API reference
181
+ - [sdk.authentication](sdk.authentication) — Auth patterns for multi-tenant apps
@@ -0,0 +1,327 @@
1
+ # Chart.js + Bonnard SDK
2
+
3
+ > Build HTML dashboards with Chart.js and the Bonnard SDK. No build step required.
4
+
5
+ Chart.js is the recommended chart library for HTML dashboards — smallest payload (~65KB gzip), most LLM training data, and excellent documentation.
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/chart.js@4/dist/chart.umd.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 { position: relative; 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"><canvas id="bar-chart"></canvas></div>
64
+ </div>
65
+ <div class="chart-card">
66
+ <div class="chart-title">Revenue Trend</div>
67
+ <div class="chart-container"><canvas id="line-chart"></canvas></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
+ // Chart.js defaults for dark mode
92
+ Chart.defaults.color = '#a1a1aa';
93
+ Chart.defaults.borderColor = '#27272a';
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
+ new Chart(document.getElementById('bar-chart'), {
121
+ type: 'bar',
122
+ data: {
123
+ labels: byCity.data.map(d => d['orders.city']),
124
+ datasets: [{
125
+ label: 'Revenue',
126
+ data: byCity.data.map(d => d['orders.revenue']),
127
+ backgroundColor: '#3b82f6',
128
+ borderRadius: 4,
129
+ }],
130
+ },
131
+ options: {
132
+ responsive: true,
133
+ maintainAspectRatio: false,
134
+ plugins: { legend: { display: false } },
135
+ scales: {
136
+ y: {
137
+ ticks: { callback: v => formatCurrency(v) },
138
+ grid: { color: '#27272a' },
139
+ },
140
+ x: { grid: { display: false } },
141
+ },
142
+ },
143
+ });
144
+
145
+ // Line chart — revenue trend
146
+ const trend = await bon.query({
147
+ measures: ['orders.revenue'],
148
+ timeDimension: {
149
+ dimension: 'orders.created_at',
150
+ granularity: 'month',
151
+ dateRange: 'last 12 months',
152
+ },
153
+ });
154
+
155
+ new Chart(document.getElementById('line-chart'), {
156
+ type: 'line',
157
+ data: {
158
+ labels: trend.data.map(d => {
159
+ const date = new Date(d['orders.created_at']);
160
+ return date.toLocaleDateString('en-US', { month: 'short', year: '2-digit' });
161
+ }),
162
+ datasets: [{
163
+ label: 'Revenue',
164
+ data: trend.data.map(d => d['orders.revenue']),
165
+ borderColor: '#3b82f6',
166
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
167
+ fill: true,
168
+ tension: 0.3,
169
+ pointRadius: 3,
170
+ }],
171
+ },
172
+ options: {
173
+ responsive: true,
174
+ maintainAspectRatio: false,
175
+ plugins: { legend: { display: false } },
176
+ scales: {
177
+ y: {
178
+ ticks: { callback: v => formatCurrency(v) },
179
+ grid: { color: '#27272a' },
180
+ },
181
+ x: { grid: { display: false } },
182
+ },
183
+ },
184
+ });
185
+ } catch (err) {
186
+ showError('Failed to load dashboard: ' + err.message);
187
+ }
188
+ })();
189
+ </script>
190
+ </body>
191
+ </html>
192
+ ```
193
+
194
+ ## Chart types
195
+
196
+ ### Bar chart
197
+
198
+ ```javascript
199
+ new Chart(ctx, {
200
+ type: 'bar',
201
+ data: {
202
+ labels: data.map(d => d['view.dimension']),
203
+ datasets: [{
204
+ label: 'Revenue',
205
+ data: data.map(d => d['view.measure']),
206
+ backgroundColor: '#3b82f6',
207
+ borderRadius: 4,
208
+ }],
209
+ },
210
+ options: {
211
+ responsive: true,
212
+ maintainAspectRatio: false,
213
+ plugins: { legend: { display: false } },
214
+ },
215
+ });
216
+ ```
217
+
218
+ ### Horizontal bar chart
219
+
220
+ ```javascript
221
+ new Chart(ctx, {
222
+ type: 'bar',
223
+ data: { /* same as above */ },
224
+ options: {
225
+ indexAxis: 'y',
226
+ responsive: true,
227
+ maintainAspectRatio: false,
228
+ },
229
+ });
230
+ ```
231
+
232
+ ### Line chart
233
+
234
+ ```javascript
235
+ new Chart(ctx, {
236
+ type: 'line',
237
+ data: {
238
+ labels: data.map(d => new Date(d['view.date']).toLocaleDateString()),
239
+ datasets: [{
240
+ label: 'Revenue',
241
+ data: data.map(d => d['view.measure']),
242
+ borderColor: '#3b82f6',
243
+ tension: 0.3,
244
+ }],
245
+ },
246
+ });
247
+ ```
248
+
249
+ ### Area chart (filled line)
250
+
251
+ ```javascript
252
+ datasets: [{
253
+ label: 'Revenue',
254
+ data: data.map(d => d['view.measure']),
255
+ borderColor: '#3b82f6',
256
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
257
+ fill: true,
258
+ }]
259
+ ```
260
+
261
+ ### Pie / doughnut chart
262
+
263
+ ```javascript
264
+ new Chart(ctx, {
265
+ type: 'doughnut', // or 'pie'
266
+ data: {
267
+ labels: data.map(d => d['view.dimension']),
268
+ datasets: [{
269
+ data: data.map(d => d['view.measure']),
270
+ backgroundColor: ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6'],
271
+ }],
272
+ },
273
+ });
274
+ ```
275
+
276
+ ### Multi-series line chart
277
+
278
+ ```javascript
279
+ // Query with a grouping dimension
280
+ const { data } = await bon.query({
281
+ measures: ['orders.revenue'],
282
+ dimensions: ['orders.city'],
283
+ timeDimension: { dimension: 'orders.created_at', granularity: 'month' },
284
+ });
285
+
286
+ // Group data by city
287
+ const cities = [...new Set(data.map(d => d['orders.city']))];
288
+ const dates = [...new Set(data.map(d => d['orders.created_at']))];
289
+
290
+ new Chart(ctx, {
291
+ type: 'line',
292
+ data: {
293
+ labels: dates.map(d => new Date(d).toLocaleDateString()),
294
+ datasets: cities.map((city, i) => ({
295
+ label: city,
296
+ data: dates.map(date =>
297
+ data.find(d => d['orders.city'] === city && d['orders.created_at'] === date)?.['orders.revenue'] || 0
298
+ ),
299
+ borderColor: ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6'][i % 5],
300
+ })),
301
+ },
302
+ });
303
+ ```
304
+
305
+ ## Dark mode setup
306
+
307
+ Set Chart.js defaults before creating charts:
308
+
309
+ ```javascript
310
+ Chart.defaults.color = '#a1a1aa'; // label/tick color
311
+ Chart.defaults.borderColor = '#27272a'; // grid line color
312
+ ```
313
+
314
+ ## Color palette
315
+
316
+ Recommended palette for multi-series charts:
317
+
318
+ ```javascript
319
+ const COLORS = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316'];
320
+ ```
321
+
322
+ ## See also
323
+
324
+ - [sdk.browser](sdk.browser) — Browser / CDN quickstart
325
+ - [sdk.query-reference](sdk.query-reference) — Full query API
326
+ - [sdk.echarts](sdk.echarts) — ECharts alternative
327
+ - [sdk.apexcharts](sdk.apexcharts) — ApexCharts alternative