@databricks/appkit-ui 0.0.2 → 0.1.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/NOTICE.md +3 -1
- package/dist/react/charts/area/index.d.ts +2 -2
- package/dist/react/charts/bar/index.d.ts +2 -2
- package/dist/react/table/data-table.d.ts +2 -2
- package/dist/react/table/data-table.d.ts.map +1 -1
- package/dist/react/ui/accordion.d.ts +5 -5
- package/dist/react/ui/alert-dialog.d.ts +12 -12
- package/dist/react/ui/alert-dialog.d.ts.map +1 -1
- package/dist/react/ui/alert.d.ts +4 -4
- package/dist/react/ui/aspect-ratio.d.ts +2 -2
- package/dist/react/ui/aspect-ratio.d.ts.map +1 -1
- package/dist/react/ui/avatar.d.ts +4 -4
- package/dist/react/ui/badge.d.ts +2 -2
- package/dist/react/ui/breadcrumb.d.ts +8 -8
- package/dist/react/ui/button-group.d.ts +6 -6
- package/dist/react/ui/button.d.ts +2 -2
- package/dist/react/ui/calendar.d.ts +3 -3
- package/dist/react/ui/card.d.ts +8 -8
- package/dist/react/ui/carousel.d.ts +6 -6
- package/dist/react/ui/chart.d.ts +5 -5
- package/dist/react/ui/checkbox.d.ts +2 -2
- package/dist/react/ui/collapsible.d.ts +4 -4
- package/dist/react/ui/command.d.ts +10 -10
- package/dist/react/ui/context-menu.d.ts +16 -16
- package/dist/react/ui/dialog.d.ts +11 -11
- package/dist/react/ui/drawer.d.ts +11 -11
- package/dist/react/ui/dropdown-menu.d.ts +16 -16
- package/dist/react/ui/empty.d.ts +9 -9
- package/dist/react/ui/field.d.ts +13 -13
- package/dist/react/ui/form.d.ts +7 -7
- package/dist/react/ui/hover-card.d.ts +4 -4
- package/dist/react/ui/input-group.d.ts +7 -7
- package/dist/react/ui/input-otp.d.ts +5 -5
- package/dist/react/ui/input.d.ts +2 -2
- package/dist/react/ui/item.d.ts +14 -14
- package/dist/react/ui/kbd.d.ts +3 -3
- package/dist/react/ui/label.d.ts +2 -2
- package/dist/react/ui/menubar.d.ts +17 -17
- package/dist/react/ui/navigation-menu.d.ts +11 -11
- package/dist/react/ui/pagination.d.ts +8 -8
- package/dist/react/ui/popover.d.ts +5 -5
- package/dist/react/ui/progress.d.ts +2 -2
- package/dist/react/ui/radio-group.d.ts +3 -3
- package/dist/react/ui/resizable.d.ts +4 -4
- package/dist/react/ui/scroll-area.d.ts +3 -3
- package/dist/react/ui/select.d.ts +11 -11
- package/dist/react/ui/separator.d.ts +2 -2
- package/dist/react/ui/sheet.d.ts +9 -9
- package/dist/react/ui/sidebar.d.ts +24 -24
- package/dist/react/ui/skeleton.d.ts +2 -2
- package/dist/react/ui/slider.d.ts +2 -2
- package/dist/react/ui/sonner.d.ts +2 -2
- package/dist/react/ui/spinner.d.ts +2 -2
- package/dist/react/ui/switch.d.ts +2 -2
- package/dist/react/ui/table.d.ts +9 -9
- package/dist/react/ui/tabs.d.ts +5 -5
- package/dist/react/ui/textarea.d.ts +2 -2
- package/dist/react/ui/toggle-group.d.ts +3 -3
- package/dist/react/ui/toggle.d.ts +2 -2
- package/dist/react/ui/tooltip.d.ts +5 -5
- package/dist/react/ui/tooltip.d.ts.map +1 -1
- package/llms.txt +1027 -143
- package/package.json +5 -1
package/llms.txt
CHANGED
|
@@ -1,193 +1,1077 @@
|
|
|
1
|
-
# llms.txt —
|
|
2
|
-
|
|
1
|
+
# llms.txt — LLM Guide for Building Great Databricks Apps with AppKit
|
|
3
2
|
Project: Databricks AppKit
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Always use
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
3
|
+
|
|
4
|
+
This document is written *for LLMs* generating code in a brand-new project folder that installs AppKit from npm. It is intentionally prescriptive.
|
|
5
|
+
|
|
6
|
+
## High-level mission
|
|
7
|
+
|
|
8
|
+
Build **full-stack TypeScript apps** on Databricks using:
|
|
9
|
+
|
|
10
|
+
- **Backend**: `@databricks/appkit`
|
|
11
|
+
- **Frontend**: `@databricks/appkit-ui`
|
|
12
|
+
- **Analytics**: SQL files in `config/queries/*.sql` executed via the AppKit analytics plugin
|
|
13
|
+
|
|
14
|
+
This file is designed to work even when you *do not* have access to the AppKit source repo. Prefer only public package APIs and portable project structures.
|
|
15
|
+
|
|
16
|
+
## Hard rules (LLM guardrails)
|
|
17
|
+
|
|
18
|
+
- **Do not invent APIs**. If unsure, stick to the patterns shown in this file and only documented exports from `@databricks/appkit` and `@databricks/appkit-ui`.
|
|
19
|
+
- **`createApp()` is async**. Prefer **top-level `await createApp(...)`**. If you can’t, use `void createApp(...)` and do not ignore promise rejection.
|
|
20
|
+
- **Always memoize query parameters** passed to `useAnalyticsQuery` / charts to avoid refetch loops.
|
|
21
|
+
- **Always handle loading/error/empty states** in UI (use `Skeleton`, error text, empty state).
|
|
22
|
+
- **Always use `sql.*` helpers** for query parameters (do not pass raw strings/numbers unless the query expects none).
|
|
23
|
+
- **Never construct SQL strings dynamically**. Use parameterized queries with `:paramName`.
|
|
24
|
+
- **Never use `require()`**. Use ESM `import/export`.
|
|
25
|
+
|
|
26
|
+
## TypeScript import rules (when using `verbatimModuleSyntax`)
|
|
27
|
+
|
|
28
|
+
If your `tsconfig.json` uses `"verbatimModuleSyntax": true`, **always use `import type` for type-only imports** (otherwise builds can fail in strict setups):
|
|
28
29
|
|
|
29
30
|
```ts
|
|
30
|
-
import {
|
|
31
|
+
import type { ReactNode } from "react";
|
|
32
|
+
import { useMemo } from "react";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Canonical project layout
|
|
36
|
+
|
|
37
|
+
Recommended structure (client/server split):
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
my-app/
|
|
41
|
+
├── server/
|
|
42
|
+
│ ├── index.ts # backend entry point (AppKit)
|
|
43
|
+
│ └── .env # optional local dev env vars (do not commit)
|
|
44
|
+
├── client/
|
|
45
|
+
│ ├── index.html
|
|
46
|
+
│ ├── vite.config.ts
|
|
47
|
+
│ └── src/
|
|
48
|
+
│ ├── main.tsx
|
|
49
|
+
│ └── App.tsx
|
|
50
|
+
├── config/
|
|
51
|
+
│ └── queries/
|
|
52
|
+
│ └── my_query.sql
|
|
53
|
+
├── app.yaml
|
|
54
|
+
├── package.json
|
|
55
|
+
└── tsconfig.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Why this layout:
|
|
59
|
+
|
|
60
|
+
- The AppKit `server()` plugin automatically serves:
|
|
61
|
+
- **Dev**: Vite dev server (HMR) from `client/`
|
|
62
|
+
- **Prod**: static files from `client/dist` (built by Vite)
|
|
63
|
+
|
|
64
|
+
## Project scaffolding (start here)
|
|
65
|
+
|
|
66
|
+
### `package.json`
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"name": "my-app",
|
|
71
|
+
"private": true,
|
|
72
|
+
"version": "0.0.0",
|
|
73
|
+
"type": "module",
|
|
74
|
+
"scripts": {
|
|
75
|
+
"dev": "NODE_ENV=development tsx watch server/index.ts",
|
|
76
|
+
"build": "npm run build:server && npm run build:client",
|
|
77
|
+
"build:server": "tsdown --out-dir build server/index.ts",
|
|
78
|
+
"build:client": "cd client && npm run build",
|
|
79
|
+
"start": "node build/index.mjs"
|
|
80
|
+
},
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"@databricks/appkit": "^0.0.2"
|
|
83
|
+
},
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@types/node": "^20.0.0",
|
|
86
|
+
"tsdown": "^0.15.7",
|
|
87
|
+
"tsx": "^4.19.0",
|
|
88
|
+
"typescript": "~5.6.0"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `client/package.json`
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"name": "client",
|
|
98
|
+
"private": true,
|
|
99
|
+
"version": "0.0.0",
|
|
100
|
+
"type": "module",
|
|
101
|
+
"scripts": {
|
|
102
|
+
"dev": "vite",
|
|
103
|
+
"build": "vite build",
|
|
104
|
+
"preview": "vite preview"
|
|
105
|
+
},
|
|
106
|
+
"dependencies": {
|
|
107
|
+
"@databricks/appkit-ui": "^0.0.2",
|
|
108
|
+
"react": "^18.0.0",
|
|
109
|
+
"react-dom": "^18.0.0",
|
|
110
|
+
"recharts": "^3.0.0"
|
|
111
|
+
},
|
|
112
|
+
"devDependencies": {
|
|
113
|
+
"@types/react": "^18.0.0",
|
|
114
|
+
"@types/react-dom": "^18.0.0",
|
|
115
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
116
|
+
"typescript": "~5.6.0",
|
|
117
|
+
"vite": "^6.0.0"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `client/index.html`
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<!doctype html>
|
|
126
|
+
<html lang="en">
|
|
127
|
+
<head>
|
|
128
|
+
<meta charset="UTF-8" />
|
|
129
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
130
|
+
<title>My App</title>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<div id="root"></div>
|
|
134
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
135
|
+
</body>
|
|
136
|
+
</html>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### `client/src/main.tsx`
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import { StrictMode } from "react";
|
|
143
|
+
import { createRoot } from "react-dom/client";
|
|
144
|
+
import App from "./App";
|
|
145
|
+
|
|
146
|
+
createRoot(document.getElementById("root")!).render(
|
|
147
|
+
<StrictMode>
|
|
148
|
+
<App />
|
|
149
|
+
</StrictMode>,
|
|
150
|
+
);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `client/src/App.tsx` (minimal)
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
export default function App() {
|
|
157
|
+
return (
|
|
158
|
+
<div className="p-8">
|
|
159
|
+
<h1 className="text-2xl font-bold">My App</h1>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `client/vite.config.ts`
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { defineConfig } from "vite";
|
|
169
|
+
import react from "@vitejs/plugin-react";
|
|
170
|
+
|
|
171
|
+
export default defineConfig({
|
|
172
|
+
plugins: [react()],
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### `tsconfig.json`
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"compilerOptions": {
|
|
181
|
+
"target": "ES2022",
|
|
182
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
183
|
+
"module": "ESNext",
|
|
184
|
+
"moduleResolution": "bundler",
|
|
185
|
+
"jsx": "react-jsx",
|
|
186
|
+
"strict": true,
|
|
187
|
+
"skipLibCheck": true,
|
|
188
|
+
"noEmit": true,
|
|
189
|
+
"allowImportingTsExtensions": true,
|
|
190
|
+
"verbatimModuleSyntax": true
|
|
191
|
+
},
|
|
192
|
+
"include": ["server", "client/src"]
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### `server/index.ts`
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { createApp, server } from "@databricks/appkit";
|
|
31
200
|
|
|
32
201
|
await createApp({
|
|
33
|
-
plugins: [
|
|
34
|
-
server({ port: 8000 }),
|
|
35
|
-
analytics(),
|
|
36
|
-
],
|
|
202
|
+
plugins: [server()],
|
|
37
203
|
});
|
|
38
204
|
```
|
|
39
205
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
206
|
+
### Running the app
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Install dependencies
|
|
210
|
+
npm install
|
|
211
|
+
cd client && npm install && cd ..
|
|
212
|
+
|
|
213
|
+
# Development (starts backend + Vite dev server)
|
|
214
|
+
npm run dev
|
|
215
|
+
|
|
216
|
+
# Production build
|
|
217
|
+
npm run build
|
|
218
|
+
npm start
|
|
219
|
+
```
|
|
43
220
|
|
|
44
|
-
|
|
221
|
+
## Integrating into an existing app
|
|
222
|
+
|
|
223
|
+
If you already have a React/Vite app and want to add AppKit:
|
|
224
|
+
|
|
225
|
+
### 1. Install dependencies
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
npm install @databricks/appkit
|
|
229
|
+
npm install -D tsx tsdown
|
|
230
|
+
|
|
231
|
+
# If you don't already have a client/ folder, create one and move your Vite app into it:
|
|
232
|
+
# - move index.html -> client/index.html
|
|
233
|
+
# - move vite.config.ts -> client/vite.config.ts
|
|
234
|
+
# - move src/ -> client/src/
|
|
235
|
+
#
|
|
236
|
+
# Then install client deps:
|
|
237
|
+
cd client
|
|
238
|
+
npm install @databricks/appkit-ui react react-dom recharts
|
|
239
|
+
npm install -D vite @vitejs/plugin-react typescript
|
|
240
|
+
cd ..
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 2. Create `server/index.ts` (new file)
|
|
45
244
|
|
|
46
245
|
```ts
|
|
246
|
+
import { createApp, server } from "@databricks/appkit";
|
|
247
|
+
|
|
248
|
+
await createApp({
|
|
249
|
+
plugins: [server()],
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### 3. Update `package.json` scripts
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"scripts": {
|
|
258
|
+
"dev": "NODE_ENV=development tsx watch server/index.ts",
|
|
259
|
+
"build": "npm run build:server && npm run build:client",
|
|
260
|
+
"build:server": "tsdown --out-dir build server/index.ts",
|
|
261
|
+
"build:client": "cd client && npm run build",
|
|
262
|
+
"start": "node build/index.mjs"
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 4. That's it
|
|
268
|
+
|
|
269
|
+
- AppKit's server plugin will automatically serve your Vite app in dev mode and `client/dist` in production.
|
|
270
|
+
- If your Vite app must stay at the repo root (no `client/` folder), AppKit can still work, but the recommended layout is `client/` + `server/`.
|
|
271
|
+
|
|
272
|
+
### Adding analytics to an existing app
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
// server/index.ts
|
|
47
276
|
import { createApp, server, analytics } from "@databricks/appkit";
|
|
48
277
|
|
|
49
|
-
|
|
278
|
+
await createApp({
|
|
279
|
+
plugins: [server(), analytics({})],
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Then create `config/queries/` and add your `.sql` files.
|
|
284
|
+
|
|
285
|
+
## Environment variables
|
|
286
|
+
|
|
287
|
+
### Required for Databricks Apps deployment
|
|
288
|
+
|
|
289
|
+
These are typically **provided by Databricks Apps runtime** (exact set can vary by platform/version):
|
|
290
|
+
|
|
291
|
+
| Variable | Description |
|
|
292
|
+
|----------|-------------|
|
|
293
|
+
| `DATABRICKS_HOST` | Workspace URL (e.g. `https://xxx.cloud.databricks.com`) |
|
|
294
|
+
| `DATABRICKS_APP_PORT` | Port to bind (default: `8000`) |
|
|
295
|
+
| `DATABRICKS_APP_NAME` | App name in Databricks |
|
|
296
|
+
|
|
297
|
+
### Required for SQL queries (analytics plugin)
|
|
298
|
+
|
|
299
|
+
| Variable | Description | How to set |
|
|
300
|
+
|----------|-------------|------------|
|
|
301
|
+
| `DATABRICKS_WAREHOUSE_ID` | SQL warehouse ID | In `app.yaml`: `valueFrom: sql-warehouse` |
|
|
302
|
+
|
|
303
|
+
### Optional
|
|
304
|
+
|
|
305
|
+
| Variable | Description | Default |
|
|
306
|
+
|----------|-------------|---------|
|
|
307
|
+
| `DATABRICKS_WORKSPACE_ID` | Workspace ID | Auto-fetched from API |
|
|
308
|
+
| `NODE_ENV` | `"development"` or `"production"` | — |
|
|
309
|
+
| `FLASK_RUN_HOST` | Host to bind | `0.0.0.0` |
|
|
310
|
+
|
|
311
|
+
### Local development
|
|
312
|
+
|
|
313
|
+
For local development, you need to authenticate with Databricks. Options:
|
|
314
|
+
|
|
315
|
+
**Option 1: Databricks CLI profile (recommended)**
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
# Configure once
|
|
319
|
+
databricks configure --profile my-profile
|
|
320
|
+
|
|
321
|
+
# Then run with profile
|
|
322
|
+
DATABRICKS_CONFIG_PROFILE=my-profile npm run dev
|
|
323
|
+
# If your Databricks SDK expects a different variable name, try:
|
|
324
|
+
# DATABRICKS_PROFILE=my-profile npm run dev
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Option 2: Environment variables**
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
export DATABRICKS_HOST="https://xxx.cloud.databricks.com"
|
|
331
|
+
export DATABRICKS_TOKEN="dapi..."
|
|
332
|
+
export DATABRICKS_WAREHOUSE_ID="abc123..."
|
|
333
|
+
npm run dev
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Option 3: `.env` file (auto-loaded by AppKit)**
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# .env (add to .gitignore!)
|
|
340
|
+
DATABRICKS_HOST=https://xxx.cloud.databricks.com
|
|
341
|
+
DATABRICKS_TOKEN=dapi...
|
|
342
|
+
DATABRICKS_WAREHOUSE_ID=abc123...
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Telemetry (optional)
|
|
346
|
+
|
|
347
|
+
| Variable | Description |
|
|
348
|
+
|----------|-------------|
|
|
349
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OpenTelemetry collector endpoint |
|
|
350
|
+
| `OTEL_SERVICE_NAME` | Service name for traces |
|
|
351
|
+
|
|
352
|
+
## Backend: `@databricks/appkit`
|
|
353
|
+
|
|
354
|
+
### Minimal server (golden template)
|
|
355
|
+
|
|
356
|
+
The smallest valid AppKit server:
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
// server/index.ts
|
|
360
|
+
import { createApp, server } from "@databricks/appkit";
|
|
361
|
+
|
|
362
|
+
await createApp({
|
|
363
|
+
plugins: [server()],
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Server plugin (`server()`)
|
|
368
|
+
|
|
369
|
+
What it does:
|
|
370
|
+
|
|
371
|
+
- Starts an Express server (default `host=0.0.0.0`, `port=8000`)
|
|
372
|
+
- Mounts plugin routes under `/api/<pluginName>/...`
|
|
373
|
+
- Adds `/health` (returns `{ status: "ok" }`)
|
|
374
|
+
- Serves frontend:
|
|
375
|
+
- **Development** (`NODE_ENV=development`): runs a Vite dev server in middleware mode
|
|
376
|
+
- **Production**: auto-detects static frontend directory (checks `dist`, `client/dist`, `build`, `public`, `out`)
|
|
377
|
+
|
|
378
|
+
Config (real options):
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
import { createApp, server } from "@databricks/appkit";
|
|
382
|
+
|
|
383
|
+
await createApp({
|
|
50
384
|
plugins: [
|
|
51
|
-
server({
|
|
52
|
-
|
|
385
|
+
server({
|
|
386
|
+
port: 8000, // default: Number(process.env.DATABRICKS_APP_PORT) || 8000
|
|
387
|
+
host: "0.0.0.0", // default: process.env.FLASK_RUN_HOST || "0.0.0.0"
|
|
388
|
+
autoStart: true, // default: true
|
|
389
|
+
staticPath: "dist", // optional: force a specific static directory
|
|
390
|
+
}),
|
|
53
391
|
],
|
|
54
392
|
});
|
|
55
|
-
|
|
56
|
-
const app = await AppKit.server.start();
|
|
57
|
-
app.get("/ping", (req, res) => res.send("pong"));
|
|
58
393
|
```
|
|
59
394
|
|
|
60
|
-
|
|
61
|
-
# Plugin APIs
|
|
62
|
-
# =====================
|
|
395
|
+
Manual server start (when you need to `.extend()` Express):
|
|
63
396
|
|
|
64
|
-
|
|
397
|
+
```ts
|
|
398
|
+
import { createApp, server } from "@databricks/appkit";
|
|
65
399
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
staticPath?: string; // This provides the path where the frontend assets are.
|
|
74
|
-
autoStart?: boolean;
|
|
75
|
-
})
|
|
400
|
+
const appkit = await createApp({
|
|
401
|
+
plugins: [server({ autoStart: false })],
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
appkit.server.extend((app) => {
|
|
405
|
+
app.get("/custom", (_req, res) => res.json({ ok: true }));
|
|
406
|
+
});
|
|
76
407
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- Purpose: Provide SQL by key interface.
|
|
80
|
-
- Usage: Only for structured query + insert examples. SQL never goes into the call to the function. Any SQL that needs to be written,
|
|
81
|
-
will be written into config/queries/<query_key>.sql. All queries should be parameterized (use placeholders).
|
|
82
|
-
- Default endpoints:
|
|
83
|
-
- POST /api/analytics/:query_key -> `query_key` will be the key to the file that contains the query. Expects a body with the shape { parameters?: Record<string, any>; }. parameters will be bound into the query.
|
|
408
|
+
await appkit.server.start();
|
|
409
|
+
```
|
|
84
410
|
|
|
85
|
-
|
|
86
|
-
# Custom Plugins
|
|
87
|
-
# =====================
|
|
411
|
+
### Analytics plugin (`analytics()`)
|
|
88
412
|
|
|
89
|
-
|
|
90
|
-
Here is an example:
|
|
413
|
+
Add SQL query execution backed by Databricks SQL Warehouses.
|
|
91
414
|
|
|
92
415
|
```ts
|
|
93
|
-
import {
|
|
416
|
+
import { analytics, createApp, server } from "@databricks/appkit";
|
|
94
417
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
418
|
+
await createApp({
|
|
419
|
+
plugins: [server(), analytics({})],
|
|
420
|
+
});
|
|
421
|
+
```
|
|
99
422
|
|
|
100
|
-
|
|
101
|
-
super(config, auth, telemetry);
|
|
423
|
+
Where queries live:
|
|
102
424
|
|
|
103
|
-
|
|
104
|
-
|
|
425
|
+
- Put `.sql` files in `config/queries/`.
|
|
426
|
+
- Query key is the filename without `.sql` (e.g. `spend_summary.sql` → `"spend_summary"`).
|
|
105
427
|
|
|
106
|
-
|
|
107
|
-
}
|
|
428
|
+
SQL parameters:
|
|
108
429
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
430
|
+
- Use `:paramName` placeholders.
|
|
431
|
+
- Optionally annotate parameter types using SQL comments:
|
|
432
|
+
|
|
433
|
+
```sql
|
|
434
|
+
-- @param startDate DATE
|
|
435
|
+
-- @param endDate DATE
|
|
436
|
+
-- @param limit NUMERIC
|
|
437
|
+
SELECT ...
|
|
438
|
+
WHERE usage_date BETWEEN :startDate AND :endDate
|
|
439
|
+
LIMIT :limit
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Supported `-- @param` types (case-insensitive):
|
|
443
|
+
|
|
444
|
+
- `STRING`, `NUMERIC`, `BOOLEAN`, `DATE`, `TIMESTAMP`, `BINARY`
|
|
445
|
+
|
|
446
|
+
Server-injected params (important):
|
|
447
|
+
|
|
448
|
+
- `:workspaceId` is **injected by the server** and **must not** be annotated.
|
|
449
|
+
- Example:
|
|
450
|
+
|
|
451
|
+
```sql
|
|
452
|
+
WHERE workspace_id = :workspaceId
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
HTTP endpoints exposed (mounted under `/api/analytics`):
|
|
456
|
+
|
|
457
|
+
- `POST /api/analytics/query/:query_key`
|
|
458
|
+
- `POST /api/analytics/users/me/query/:query_key`
|
|
459
|
+
- `GET /api/analytics/arrow-result/:jobId`
|
|
460
|
+
- `GET /api/analytics/users/me/arrow-result/:jobId`
|
|
461
|
+
|
|
462
|
+
Formats:
|
|
463
|
+
|
|
464
|
+
- `format: "JSON"` (default) returns JSON rows
|
|
465
|
+
- `format: "ARROW"` returns an Arrow “external links” payload over SSE, then the client fetches binary Arrow from `/api/analytics/arrow-result/:jobId`
|
|
466
|
+
|
|
467
|
+
### Request context (`getRequestContext()`)
|
|
468
|
+
|
|
469
|
+
If a plugin sets `requiresDatabricksClient = true`, AppKit adds middleware that provides request context.
|
|
470
|
+
|
|
471
|
+
Headers used:
|
|
472
|
+
|
|
473
|
+
- `x-forwarded-user`: required in production; identifies the user
|
|
474
|
+
- `x-forwarded-access-token`: optional; enables **user token passthrough** if `DATABRICKS_HOST` is set
|
|
475
|
+
|
|
476
|
+
Context fields (real behavior):
|
|
477
|
+
|
|
478
|
+
- `userId`: derived from `x-forwarded-user` (in development it falls back to `serviceUserId`)
|
|
479
|
+
- `serviceUserId`: service principal/user ID
|
|
480
|
+
- `warehouseId`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID`, or auto-selected in development)
|
|
481
|
+
- `workspaceId`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
|
|
482
|
+
- `userDatabricksClient`: present only when passthrough is available (or in dev it equals service client)
|
|
483
|
+
- `serviceDatabricksClient`: always present
|
|
484
|
+
|
|
485
|
+
### Custom plugins (backend)
|
|
486
|
+
|
|
487
|
+
If you need custom API routes or background logic, implement an AppKit plugin.
|
|
488
|
+
|
|
489
|
+
```ts
|
|
490
|
+
import { Plugin, toPlugin } from "@databricks/appkit";
|
|
491
|
+
import type express from "express";
|
|
492
|
+
|
|
493
|
+
class MyPlugin extends Plugin {
|
|
494
|
+
name = "my-plugin";
|
|
495
|
+
envVars = []; // list required env vars here
|
|
496
|
+
requiresDatabricksClient = false; // set true if you need getRequestContext()
|
|
126
497
|
|
|
127
|
-
/**
|
|
128
|
-
* Optionally the plugin can inject its own routes to the router
|
|
129
|
-
*/
|
|
130
498
|
injectRoutes(router: express.Router) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
router.get("/weather", async (req: any, res: any) => {
|
|
139
|
-
const { lat, lon } = req.query;
|
|
140
|
-
const data = await this.getWeather(lat, lon);
|
|
141
|
-
res.send(data);
|
|
499
|
+
this.route(router, {
|
|
500
|
+
name: "hello",
|
|
501
|
+
method: "get",
|
|
502
|
+
path: "/hello",
|
|
503
|
+
handler: async (_req, res) => {
|
|
504
|
+
res.json({ ok: true });
|
|
505
|
+
},
|
|
142
506
|
});
|
|
143
507
|
}
|
|
144
508
|
}
|
|
145
509
|
|
|
146
|
-
export const
|
|
510
|
+
export const myPlugin = toPlugin<typeof MyPlugin, Record<string, never>, "my-plugin">(
|
|
511
|
+
MyPlugin,
|
|
512
|
+
"my-plugin",
|
|
513
|
+
);
|
|
147
514
|
```
|
|
148
515
|
|
|
149
|
-
|
|
516
|
+
### Caching (global + plugin-level)
|
|
517
|
+
|
|
518
|
+
Global:
|
|
150
519
|
|
|
151
520
|
```ts
|
|
152
|
-
|
|
153
|
-
|
|
521
|
+
await createApp({
|
|
522
|
+
plugins: [server(), analytics({})],
|
|
523
|
+
cache: {
|
|
524
|
+
enabled: true,
|
|
525
|
+
ttl: 3600, // seconds
|
|
526
|
+
strictPersistence: false,
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
- Storage auto-selects **Lakebase persistent cache when healthy**, otherwise falls back to in-memory.
|
|
532
|
+
|
|
533
|
+
Plugin-level:
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
// inside a Plugin subclass:
|
|
537
|
+
const value = await this.cache.getOrExecute(
|
|
538
|
+
["my-plugin", "data", userId],
|
|
539
|
+
async () => expensiveWork(),
|
|
540
|
+
userKey,
|
|
541
|
+
{ ttl: 300 },
|
|
542
|
+
);
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Frontend: `@databricks/appkit-ui`
|
|
546
|
+
|
|
547
|
+
### Imports
|
|
548
|
+
|
|
549
|
+
- React-facing APIs: `@databricks/appkit-ui/react`
|
|
550
|
+
- Non-React utilities (sql markers, arrow, SSE): `@databricks/appkit-ui/js`
|
|
551
|
+
|
|
552
|
+
```tsx
|
|
553
|
+
import { useAnalyticsQuery, Card, Skeleton } from "@databricks/appkit-ui/react";
|
|
554
|
+
import { sql } from "@databricks/appkit-ui/js";
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### `useAnalyticsQuery(queryKey, parameters, options?)`
|
|
558
|
+
|
|
559
|
+
Facts:
|
|
560
|
+
|
|
561
|
+
- Uses **SSE** under the hood (not `fetch()` polling).
|
|
562
|
+
- By default it hits `POST /api/analytics/query/:queryKey`.
|
|
563
|
+
- Returns `{ data, loading, error }` where `data` is `null` until loaded.
|
|
564
|
+
- `format` is `"JSON"` or `"ARROW"` (uppercase).
|
|
565
|
+
|
|
566
|
+
When to use it:
|
|
567
|
+
|
|
568
|
+
- Use `useAnalyticsQuery` **only** when you need a custom UI (cards/KPIs/forms/conditional rendering).
|
|
569
|
+
- If you just need a standard chart or table, prefer the built-in components (`BarChart`, `LineChart`, `DataTable`, etc.) so you don’t re-implement loading/error/empty states.
|
|
570
|
+
|
|
571
|
+
Limitations (common LLM pitfall):
|
|
572
|
+
|
|
573
|
+
- There is **no `enabled` option**. Use conditional rendering to mount/unmount the component.
|
|
574
|
+
- There is **no `refetch()`**. Change `parameters` (memoized) or re-mount to re-run the query.
|
|
575
|
+
|
|
576
|
+
Recommended usage pattern (memoized params + explicit states):
|
|
577
|
+
|
|
578
|
+
```tsx
|
|
579
|
+
import { useMemo } from "react";
|
|
580
|
+
import { useAnalyticsQuery, Skeleton } from "@databricks/appkit-ui/react";
|
|
581
|
+
import { sql } from "@databricks/appkit-ui/js";
|
|
582
|
+
|
|
583
|
+
export function Users() {
|
|
584
|
+
const params = useMemo(
|
|
585
|
+
() => ({
|
|
586
|
+
status: sql.string("active"),
|
|
587
|
+
limit: sql.number(50),
|
|
588
|
+
}),
|
|
589
|
+
[],
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
const { data, loading, error } = useAnalyticsQuery("users_list", params);
|
|
593
|
+
|
|
594
|
+
if (loading) return <Skeleton className="h-24 w-full" />;
|
|
595
|
+
if (error) return <div className="text-destructive">Error: {error}</div>;
|
|
596
|
+
if (!data || data.length === 0) return <div>No results</div>;
|
|
597
|
+
|
|
598
|
+
return <pre>{JSON.stringify(data[0], null, 2)}</pre>;
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Options:
|
|
603
|
+
|
|
604
|
+
- `format?: "JSON" | "ARROW"` (default `"JSON"`)
|
|
605
|
+
- `autoStart?: boolean` (default `true`)
|
|
606
|
+
- `maxParametersSize?: number` (default `100 * 1024` bytes)
|
|
607
|
+
|
|
608
|
+
### `useChartData({ queryKey, parameters, format, transformer })`
|
|
609
|
+
|
|
610
|
+
- `format` here is **lowercase**: `"json" | "arrow" | "auto"` (default `"auto"`)
|
|
611
|
+
- Auto-selection heuristics:
|
|
612
|
+
- If `parameters._preferArrow === true` → Arrow
|
|
613
|
+
- If `parameters._preferJson === true` → JSON
|
|
614
|
+
- If `parameters.limit` is a number > 500 → Arrow
|
|
615
|
+
- If `parameters.startDate` and `parameters.endDate` exist → Arrow
|
|
616
|
+
|
|
617
|
+
### Charts (unified query/data API)
|
|
618
|
+
|
|
619
|
+
All charts support:
|
|
620
|
+
|
|
621
|
+
- **Query mode**: `queryKey` + `parameters`
|
|
622
|
+
- **Data mode**: `data` (inline JSON, no server)
|
|
623
|
+
|
|
624
|
+
Available chart components:
|
|
625
|
+
|
|
626
|
+
- `BarChart`, `LineChart`, `AreaChart`, `PieChart`, `DonutChart`, `HeatmapChart`, `ScatterChart`, `RadarChart`
|
|
627
|
+
|
|
628
|
+
Avoid double-fetching:
|
|
629
|
+
|
|
630
|
+
```tsx
|
|
631
|
+
// ❌ Wrong: fetches the same query twice
|
|
632
|
+
// const { data } = useAnalyticsQuery("spend_data", params);
|
|
633
|
+
// return <LineChart queryKey="spend_data" parameters={params} />;
|
|
634
|
+
|
|
635
|
+
// ✅ Correct: let the chart fetch
|
|
636
|
+
return <LineChart queryKey="spend_data" parameters={params} />;
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Query mode (recommended for Databricks-backed analytics):
|
|
640
|
+
|
|
641
|
+
```tsx
|
|
642
|
+
import { LineChart } from "@databricks/appkit-ui/react";
|
|
643
|
+
import { sql } from "@databricks/appkit-ui/js";
|
|
644
|
+
import { useMemo } from "react";
|
|
645
|
+
|
|
646
|
+
export function SpendChart() {
|
|
647
|
+
const params = useMemo(
|
|
648
|
+
() => ({
|
|
649
|
+
startDate: sql.date("2024-01-01"),
|
|
650
|
+
endDate: sql.date("2024-12-31"),
|
|
651
|
+
aggregationLevel: sql.string("day"),
|
|
652
|
+
}),
|
|
653
|
+
[],
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
return (
|
|
657
|
+
<LineChart
|
|
658
|
+
queryKey="spend_data"
|
|
659
|
+
parameters={params}
|
|
660
|
+
format="auto" // "auto" | "json" | "arrow"
|
|
661
|
+
xKey="period"
|
|
662
|
+
yKey="cost_usd"
|
|
663
|
+
smooth
|
|
664
|
+
showSymbol={false}
|
|
665
|
+
/>
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### SQL helpers (`sql.*`)
|
|
671
|
+
|
|
672
|
+
Use these to build typed parameters (they return marker objects: `{ __sql_type, value }`):
|
|
673
|
+
|
|
674
|
+
- `sql.string(value)` → STRING (accepts string|number|boolean)
|
|
675
|
+
- `sql.number(value)` → NUMERIC (accepts number|string)
|
|
676
|
+
- `sql.boolean(value)` → BOOLEAN (accepts boolean|string("true"/"false")|number(1/0))
|
|
677
|
+
- `sql.date(value)` → DATE (accepts Date or `"YYYY-MM-DD"`)
|
|
678
|
+
- `sql.timestamp(value)` → TIMESTAMP (accepts Date, ISO string, or unix time)
|
|
679
|
+
|
|
680
|
+
Binary parameters (important):
|
|
681
|
+
|
|
682
|
+
- Databricks SQL Warehouse doesn't support `BINARY` as a parameter type.
|
|
683
|
+
- `sql.binary(value)` returns a **STRING marker containing hex**, so use `UNHEX(:param)` in SQL.
|
|
684
|
+
- `sql.binary` accepts `Uint8Array`, `ArrayBuffer`, or a hex string.
|
|
685
|
+
|
|
686
|
+
### SQL result types (important)
|
|
687
|
+
|
|
688
|
+
Databricks SQL JSON results can return some numeric-like fields (especially `DECIMAL`) as strings. If a field behaves like a string at runtime, convert explicitly:
|
|
689
|
+
|
|
690
|
+
```ts
|
|
691
|
+
const value = Number(row.amount);
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
If you need more reliable numeric fidelity for large datasets, prefer `format: "ARROW"` and process Arrow on the client.
|
|
695
|
+
|
|
696
|
+
### `connectSSE` (custom SSE connections)
|
|
697
|
+
|
|
698
|
+
For custom streaming endpoints (not analytics), use the `connectSSE` utility:
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
import { connectSSE } from "@databricks/appkit-ui/js";
|
|
702
|
+
import { useEffect, useState } from "react";
|
|
703
|
+
|
|
704
|
+
function useCustomStream(endpoint: string) {
|
|
705
|
+
const [messages, setMessages] = useState<string[]>([]);
|
|
706
|
+
const [connected, setConnected] = useState(false);
|
|
707
|
+
|
|
708
|
+
useEffect(() => {
|
|
709
|
+
const controller = new AbortController();
|
|
710
|
+
|
|
711
|
+
connectSSE({
|
|
712
|
+
url: endpoint,
|
|
713
|
+
payload: { key: "value" }, // optional: makes it a POST
|
|
714
|
+
onMessage: async ({ data }) => {
|
|
715
|
+
setConnected(true);
|
|
716
|
+
setMessages((prev) => [...prev, data]);
|
|
717
|
+
},
|
|
718
|
+
onError: (error) => {
|
|
719
|
+
console.error("SSE error:", error);
|
|
720
|
+
setConnected(false);
|
|
721
|
+
},
|
|
722
|
+
signal: controller.signal,
|
|
723
|
+
maxRetries: 3, // default: 3
|
|
724
|
+
retryDelay: 2000, // default: 2000ms (exponential backoff)
|
|
725
|
+
timeout: 300000, // default: 5 minutes
|
|
726
|
+
maxBufferSize: 1048576, // default: 1MB
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
return () => controller.abort();
|
|
730
|
+
}, [endpoint]);
|
|
731
|
+
|
|
732
|
+
return { messages, connected };
|
|
733
|
+
}
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
Options:
|
|
737
|
+
|
|
738
|
+
- `url`: SSE endpoint URL (required)
|
|
739
|
+
- `payload`: Optional request body (if provided, uses POST; otherwise GET)
|
|
740
|
+
- `onMessage({ id, data })`: Called for each SSE message
|
|
741
|
+
- `onError(error)`: Called on connection errors
|
|
742
|
+
- `signal`: AbortSignal to cancel the connection
|
|
743
|
+
- `lastEventId`: Resume from a specific event ID
|
|
744
|
+
- `maxRetries`: Max retry attempts (default: 3)
|
|
745
|
+
- `retryDelay`: Base delay between retries in ms (default: 2000)
|
|
746
|
+
- `timeout`: Connection timeout in ms (default: 300000)
|
|
747
|
+
- `maxBufferSize`: Max buffer size in bytes (default: 1MB)
|
|
748
|
+
|
|
749
|
+
### `ArrowClient` (advanced Arrow processing)
|
|
750
|
+
|
|
751
|
+
For low-level Arrow data handling:
|
|
752
|
+
|
|
753
|
+
```tsx
|
|
754
|
+
import { ArrowClient } from "@databricks/appkit-ui/js";
|
|
755
|
+
|
|
756
|
+
// Process Arrow buffer
|
|
757
|
+
const table = await ArrowClient.processArrowBuffer(buffer);
|
|
758
|
+
|
|
759
|
+
// Fetch and process Arrow data in one call
|
|
760
|
+
const table = await ArrowClient.fetchAndProcessArrow(url, headers);
|
|
154
761
|
|
|
155
|
-
|
|
762
|
+
// Extract fields from table
|
|
763
|
+
const fields = ArrowClient.extractArrowFields(table);
|
|
764
|
+
// → [{ name: "date", type: ... }, { name: "value", type: ... }]
|
|
765
|
+
|
|
766
|
+
// Extract columns as arrays
|
|
767
|
+
const columns = ArrowClient.extractArrowColumns(table);
|
|
768
|
+
// → { date: [...], value: [...] }
|
|
769
|
+
|
|
770
|
+
// Extract chart data
|
|
771
|
+
const { xData, yDataMap } = ArrowClient.extractChartData(table, "date", ["value", "count"]);
|
|
772
|
+
// → { xData: [...], yDataMap: { value: [...], count: [...] } }
|
|
773
|
+
|
|
774
|
+
// Auto-detect chart fields from Arrow table
|
|
775
|
+
const detected = ArrowClient.detectFieldsFromArrow(table);
|
|
776
|
+
// → { xField: "date", yFields: ["value"], chartType: "timeseries" }
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
### DataTable
|
|
780
|
+
|
|
781
|
+
`DataTable` is a production-ready table integrated with `useAnalyticsQuery`.
|
|
782
|
+
|
|
783
|
+
Key behaviors:
|
|
784
|
+
|
|
785
|
+
- `parameters` is required (use `{}` if none)
|
|
786
|
+
- Supports opinionated mode (auto columns) and full-control mode (`children(table)`)
|
|
787
|
+
|
|
788
|
+
```tsx
|
|
789
|
+
import { DataTable } from "@databricks/appkit-ui/react";
|
|
790
|
+
|
|
791
|
+
export function UsersTable() {
|
|
792
|
+
return (
|
|
793
|
+
<DataTable
|
|
794
|
+
queryKey="users_list"
|
|
795
|
+
parameters={{}}
|
|
796
|
+
filterColumn="email"
|
|
797
|
+
filterPlaceholder="Filter by email..."
|
|
798
|
+
pageSize={25}
|
|
799
|
+
pageSizeOptions={[10, 25, 50, 100]}
|
|
800
|
+
/>
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### UI components (primitives)
|
|
806
|
+
|
|
807
|
+
AppKit-UI ships shadcn-style primitives. Import from `@databricks/appkit-ui/react`.
|
|
808
|
+
|
|
809
|
+
Note: Exact exports can vary by AppKit-UI version. Prefer using IDE auto-import/autocomplete to confirm what your installed version exports.
|
|
810
|
+
|
|
811
|
+
Radix constraint (common bug):
|
|
812
|
+
|
|
813
|
+
- `SelectItem` cannot have `value=""`. Use a sentinel value like `"all"` or `"none"`.
|
|
814
|
+
|
|
815
|
+
**Available components:**
|
|
816
|
+
|
|
817
|
+
`Accordion`, `Alert`, `AlertDialog`, `AspectRatio`, `Avatar`, `Badge`, `Breadcrumb`, `Button`, `ButtonGroup`, `Calendar`, `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`, `Carousel`, `Checkbox`, `Collapsible`, `Command`, `ContextMenu`, `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogFooter`, `Drawer`, `DropdownMenu`, `Empty`, `Field`, `Form`, `HoverCard`, `Input`, `InputGroup`, `InputOtp`, `Item`, `Kbd`, `Label`, `Menubar`, `NavigationMenu`, `Pagination`, `Popover`, `Progress`, `RadioGroup`, `Resizable`, `ScrollArea`, `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `Separator`, `Sheet`, `Sidebar`, `Skeleton`, `Slider`, `Sonner`, `Spinner`, `Switch`, `Table`, `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `Textarea`, `Toggle`, `ToggleGroup`, `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider`
|
|
818
|
+
|
|
819
|
+
### Card pattern
|
|
820
|
+
|
|
821
|
+
```tsx
|
|
822
|
+
import {
|
|
823
|
+
Card,
|
|
824
|
+
CardHeader,
|
|
825
|
+
CardTitle,
|
|
826
|
+
CardDescription,
|
|
827
|
+
CardContent,
|
|
828
|
+
CardFooter,
|
|
829
|
+
} from "@databricks/appkit-ui/react";
|
|
830
|
+
|
|
831
|
+
function MetricCard({ title, value, description }: Props) {
|
|
832
|
+
return (
|
|
833
|
+
<Card>
|
|
834
|
+
<CardHeader>
|
|
835
|
+
<CardDescription>{description}</CardDescription>
|
|
836
|
+
<CardTitle className="text-3xl">{value}</CardTitle>
|
|
837
|
+
</CardHeader>
|
|
838
|
+
<CardContent>
|
|
839
|
+
{/* Optional content */}
|
|
840
|
+
</CardContent>
|
|
841
|
+
<CardFooter>
|
|
842
|
+
{/* Optional footer */}
|
|
843
|
+
</CardFooter>
|
|
844
|
+
</Card>
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
### Select pattern
|
|
850
|
+
|
|
851
|
+
```tsx
|
|
852
|
+
import {
|
|
853
|
+
Select,
|
|
854
|
+
SelectTrigger,
|
|
855
|
+
SelectValue,
|
|
856
|
+
SelectContent,
|
|
857
|
+
SelectItem,
|
|
858
|
+
} from "@databricks/appkit-ui/react";
|
|
859
|
+
|
|
860
|
+
function DateRangeSelect({ value, onChange }: Props) {
|
|
861
|
+
return (
|
|
862
|
+
<Select value={value} onValueChange={onChange}>
|
|
863
|
+
<SelectTrigger className="w-40">
|
|
864
|
+
<SelectValue placeholder="Select range" />
|
|
865
|
+
</SelectTrigger>
|
|
866
|
+
<SelectContent>
|
|
867
|
+
<SelectItem value="7d">Last 7 days</SelectItem>
|
|
868
|
+
<SelectItem value="30d">Last 30 days</SelectItem>
|
|
869
|
+
<SelectItem value="90d">Last 90 days</SelectItem>
|
|
870
|
+
</SelectContent>
|
|
871
|
+
</Select>
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### Tabs pattern
|
|
877
|
+
|
|
878
|
+
```tsx
|
|
879
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@databricks/appkit-ui/react";
|
|
880
|
+
|
|
881
|
+
function Dashboard() {
|
|
882
|
+
return (
|
|
883
|
+
<Tabs defaultValue="overview">
|
|
884
|
+
<TabsList>
|
|
885
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
886
|
+
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
887
|
+
</TabsList>
|
|
888
|
+
<TabsContent value="overview">
|
|
889
|
+
<p>Overview content</p>
|
|
890
|
+
</TabsContent>
|
|
891
|
+
<TabsContent value="analytics">
|
|
892
|
+
<p>Analytics content</p>
|
|
893
|
+
</TabsContent>
|
|
894
|
+
</Tabs>
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
### Dialog pattern
|
|
900
|
+
|
|
901
|
+
```tsx
|
|
902
|
+
import {
|
|
903
|
+
Dialog,
|
|
904
|
+
DialogTrigger,
|
|
905
|
+
DialogContent,
|
|
906
|
+
DialogHeader,
|
|
907
|
+
DialogTitle,
|
|
908
|
+
DialogDescription,
|
|
909
|
+
DialogFooter,
|
|
910
|
+
Button,
|
|
911
|
+
} from "@databricks/appkit-ui/react";
|
|
912
|
+
|
|
913
|
+
function ConfirmDialog() {
|
|
914
|
+
return (
|
|
915
|
+
<Dialog>
|
|
916
|
+
<DialogTrigger asChild>
|
|
917
|
+
<Button variant="destructive">Delete</Button>
|
|
918
|
+
</DialogTrigger>
|
|
919
|
+
<DialogContent>
|
|
920
|
+
<DialogHeader>
|
|
921
|
+
<DialogTitle>Confirm deletion</DialogTitle>
|
|
922
|
+
<DialogDescription>
|
|
923
|
+
This action cannot be undone.
|
|
924
|
+
</DialogDescription>
|
|
925
|
+
</DialogHeader>
|
|
926
|
+
<DialogFooter>
|
|
927
|
+
<Button variant="outline">Cancel</Button>
|
|
928
|
+
<Button variant="destructive">Delete</Button>
|
|
929
|
+
</DialogFooter>
|
|
930
|
+
</DialogContent>
|
|
931
|
+
</Dialog>
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
### TooltipProvider requirement
|
|
937
|
+
|
|
938
|
+
If using tooltips anywhere in your app, wrap your root component with `TooltipProvider`:
|
|
939
|
+
|
|
940
|
+
```tsx
|
|
941
|
+
import { TooltipProvider } from "@databricks/appkit-ui/react";
|
|
942
|
+
|
|
943
|
+
function App() {
|
|
944
|
+
return (
|
|
945
|
+
<TooltipProvider>
|
|
946
|
+
{/* Your app content */}
|
|
947
|
+
</TooltipProvider>
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
### Button variants
|
|
953
|
+
|
|
954
|
+
```tsx
|
|
955
|
+
import { Button } from "@databricks/appkit-ui/react";
|
|
956
|
+
|
|
957
|
+
<Button variant="default">Primary</Button>
|
|
958
|
+
<Button variant="secondary">Secondary</Button>
|
|
959
|
+
<Button variant="outline">Outline</Button>
|
|
960
|
+
<Button variant="ghost">Ghost</Button>
|
|
961
|
+
<Button variant="destructive">Destructive</Button>
|
|
962
|
+
<Button variant="link">Link</Button>
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
### Loading skeleton pattern
|
|
966
|
+
|
|
967
|
+
```tsx
|
|
968
|
+
import { Card, CardHeader, Skeleton } from "@databricks/appkit-ui/react";
|
|
969
|
+
|
|
970
|
+
function LoadingCard() {
|
|
971
|
+
return (
|
|
972
|
+
<Card>
|
|
973
|
+
<CardHeader>
|
|
974
|
+
<Skeleton className="h-4 w-24 mb-2" />
|
|
975
|
+
<Skeleton className="h-8 w-20 mb-2" />
|
|
976
|
+
<Skeleton className="h-4 w-28" />
|
|
977
|
+
</CardHeader>
|
|
978
|
+
</Card>
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
## Type generation (QueryRegistry + IntelliSense)
|
|
984
|
+
|
|
985
|
+
Goal: generate `client/src/appKitTypes.d.ts` so query keys, params, and result rows are type-safe.
|
|
986
|
+
|
|
987
|
+
### Vite plugin: `appKitTypesPlugin`
|
|
988
|
+
|
|
989
|
+
Correct option names:
|
|
990
|
+
|
|
991
|
+
- `outFile?: string` (default `src/appKitTypes.d.ts`)
|
|
992
|
+
- `watchFolders?: string[]` (default `["../config/queries"]`)
|
|
993
|
+
|
|
994
|
+
```ts
|
|
995
|
+
// client/vite.config.ts
|
|
996
|
+
import { defineConfig } from "vite";
|
|
997
|
+
import react from "@vitejs/plugin-react";
|
|
998
|
+
import { appKitTypesPlugin } from "@databricks/appkit";
|
|
999
|
+
|
|
1000
|
+
export default defineConfig({
|
|
156
1001
|
plugins: [
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
1002
|
+
react(),
|
|
1003
|
+
appKitTypesPlugin({
|
|
1004
|
+
outFile: "src/appKitTypes.d.ts",
|
|
1005
|
+
watchFolders: ["../config/queries"],
|
|
1006
|
+
}),
|
|
160
1007
|
],
|
|
161
1008
|
});
|
|
1009
|
+
```
|
|
162
1010
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
1011
|
+
Important nuance:
|
|
1012
|
+
|
|
1013
|
+
- When the frontend is served through AppKit in dev mode, AppKit’s dev server already includes `appKitTypesPlugin()` internally.
|
|
1014
|
+
- You still want it in your client build pipeline if you run `vite build` separately.
|
|
1015
|
+
|
|
1016
|
+
### CLI: `appkit-generate-types`
|
|
1017
|
+
|
|
1018
|
+
```bash
|
|
1019
|
+
# Requires DATABRICKS_WAREHOUSE_ID (or pass as 3rd arg)
|
|
1020
|
+
npx appkit-generate-types [rootDir] [outFile] [warehouseId]
|
|
1021
|
+
|
|
1022
|
+
# Example:
|
|
1023
|
+
npx appkit-generate-types . client/src/appKitTypes.d.ts
|
|
1024
|
+
|
|
1025
|
+
# Force regeneration (skip cache):
|
|
1026
|
+
npx appkit-generate-types --no-cache
|
|
171
1027
|
```
|
|
172
1028
|
|
|
1029
|
+
## Databricks Apps config: `app.yaml`
|
|
1030
|
+
|
|
1031
|
+
Bind a SQL warehouse for Apps runtime:
|
|
1032
|
+
|
|
1033
|
+
```yaml
|
|
1034
|
+
env:
|
|
1035
|
+
- name: DATABRICKS_WAREHOUSE_ID
|
|
1036
|
+
valueFrom: sql-warehouse
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
Full example with command:
|
|
1040
|
+
|
|
1041
|
+
```yaml
|
|
1042
|
+
command:
|
|
1043
|
+
- node
|
|
1044
|
+
- build/index.mjs
|
|
1045
|
+
env:
|
|
1046
|
+
- name: DATABRICKS_WAREHOUSE_ID
|
|
1047
|
+
valueFrom: sql-warehouse
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
## LLM checklist (before you "finalize" code)
|
|
1051
|
+
|
|
1052
|
+
- **Project setup**
|
|
1053
|
+
- `package.json` has `"type": "module"`
|
|
1054
|
+
- `tsx` is in devDependencies for dev server
|
|
1055
|
+
- `dev` script uses `NODE_ENV=development tsx watch server/index.ts`
|
|
1056
|
+
- `client/index.html` exists with `<div id="root"></div>` and script pointing to `client/src/main.tsx`
|
|
1057
|
+
- `client/package.json` exists and includes `@databricks/appkit-ui`
|
|
1058
|
+
|
|
1059
|
+
- **Backend**
|
|
1060
|
+
- `await createApp({ plugins: [...] })` is used (or `void createApp` with intent)
|
|
1061
|
+
- `server()` is included (always)
|
|
1062
|
+
- If using SQL: `analytics({})` included + `config/queries/*.sql` present
|
|
1063
|
+
- Queries use `:param` placeholders, and params are passed from UI using `sql.*`
|
|
1064
|
+
- If query needs workspace scoping: uses `:workspaceId`
|
|
173
1065
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
- Use ESModules (import/export), not require().
|
|
180
|
-
- Use TypeScript typings in advanced examples if helpful.
|
|
1066
|
+
- **Frontend**
|
|
1067
|
+
- `useMemo` wraps parameters objects
|
|
1068
|
+
- Loading/error/empty states are explicit
|
|
1069
|
+
- Charts use `format="auto"` unless you have a reason to force `"json"`/`"arrow"`
|
|
1070
|
+
- If using tooltips: root is wrapped with `<TooltipProvider>`
|
|
181
1071
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
- ❌ Do not show usage without createApp first.
|
|
1072
|
+
- **Never**
|
|
1073
|
+
- Don't build SQL strings manually
|
|
1074
|
+
- Don't pass untyped raw params for annotated queries
|
|
1075
|
+
- Don't ignore `createApp()`'s promise
|
|
1076
|
+
- Don't invent UI components not listed in this file
|
|
188
1077
|
|
|
189
|
-
# =====================
|
|
190
|
-
# Attribution
|
|
191
|
-
# =====================
|
|
192
|
-
If AI-generated code uses this SDK, attribute:
|
|
193
|
-
"Powered by Databricks AppKit (https://github.com/...)".
|