From 6c3190c056c5a6697bf7581c17710e561d5847c6 Mon Sep 17 00:00:00 2001 From: MarioCadenas Date: Wed, 17 Dec 2025 19:28:30 +0100 Subject: [PATCH 1/2] feat: appkit setup chore: fixup chore: fixup --- biome.json | 1 + packages/app-kit-ui/CLAUDE.md | 319 +++++++++++++++++++++++++ packages/app-kit-ui/package.json | 3 + packages/app-kit/CLAUDE.md | 274 +++++++++++++++++++++ packages/app-kit/package.json | 3 + packages/shared/bin/setup-claude.js | 190 +++++++++++++++ packages/shared/scripts/postinstall.js | 6 + tools/dist.ts | 45 +++- 8 files changed, 840 insertions(+), 1 deletion(-) create mode 100644 packages/app-kit-ui/CLAUDE.md create mode 100644 packages/app-kit/CLAUDE.md create mode 100644 packages/shared/bin/setup-claude.js create mode 100644 packages/shared/scripts/postinstall.js diff --git a/biome.json b/biome.json index b43cea3..f71ce8a 100644 --- a/biome.json +++ b/biome.json @@ -10,6 +10,7 @@ "includes": [ "**", "!**/dist", + "!**/tmp", "!**/*.d.ts", "!**/build", "!**/coverage", diff --git a/packages/app-kit-ui/CLAUDE.md b/packages/app-kit-ui/CLAUDE.md new file mode 100644 index 0000000..4dd6e7d --- /dev/null +++ b/packages/app-kit-ui/CLAUDE.md @@ -0,0 +1,319 @@ +# CLAUDE.md - @databricks/app-kit-ui + +AI assistant guidance for the Databricks AppKit UI component library. + +## Overview + +`@databricks/app-kit-ui` provides React components and JavaScript utilities for building Databricks application frontends. It includes data visualization charts, data tables, and SSE utilities that integrate seamlessly with the `@databricks/app-kit` backend. + +## Installation & Imports + +```typescript +// React components +import { BarChart, LineChart, DataTable, useAnalyticsQuery } from "@databricks/app-kit-ui/react"; + +// JavaScript utilities (SSE, SQL helpers) +import { connectSSE, sql } from "@databricks/app-kit-ui/js"; + +// Global styles (include in your app entry) +import "@databricks/app-kit-ui/styles.css"; +``` + +## Chart Components + +All charts integrate with the analytics backend via `queryKey` and support two modes: +- **Opinionated mode**: Automatic field detection and rendering +- **Full control mode**: Pass children for custom Recharts configuration + +### BarChart + +```tsx +import { BarChart } from "@databricks/app-kit-ui/react"; + +// Opinionated mode - auto-detects x/y fields + + +// Horizontal orientation + + +// With data transformation + data.map(d => ({ name: d.user, count: d.total }))} +/> + +// Full control mode + + + + +``` + +### LineChart + +```tsx +import { LineChart } from "@databricks/app-kit-ui/react"; + + +``` + +### AreaChart + +```tsx +import { AreaChart } from "@databricks/app-kit-ui/react"; + + +``` + +### PieChart + +```tsx +import { PieChart } from "@databricks/app-kit-ui/react"; + + +``` + +### RadarChart + +```tsx +import { RadarChart } from "@databricks/app-kit-ui/react"; + + +``` + +### Common Chart Props + +| Prop | Type | Description | +|------|------|-------------| +| `queryKey` | `string` | Analytics query identifier (maps to `config/queries/*.sql`) | +| `parameters` | `object` | Query parameters | +| `transformer` | `(data) => data` | Transform data before rendering | +| `height` | `string` | Chart height (default: "300px") | +| `orientation` | `"vertical" \| "horizontal"` | Bar chart orientation | +| `chartConfig` | `ChartConfig` | Custom Recharts config | +| `ariaLabel` | `string` | Accessibility label | +| `testId` | `string` | Test ID attribute | +| `className` | `string` | Additional CSS classes | + +## DataTable Component + +Production-ready data table with automatic data fetching, filtering, sorting, and pagination: + +```tsx +import { DataTable } from "@databricks/app-kit-ui/react"; + +// Opinionated mode + + +// With row selection + console.log(selection)} +/> + +// Full control mode + + {(table) => ( +
+ {table.getRowModel().rows.map(row => ( +
{row.original.name}
+ ))} +
+ )} +
+``` + +### DataTable Props + +| Prop | Type | Description | +|------|------|-------------| +| `queryKey` | `string` | Analytics query identifier | +| `parameters` | `object` | Query parameters | +| `filterColumn` | `string` | Column to filter by (auto-detected if not set) | +| `filterPlaceholder` | `string` | Filter input placeholder | +| `transform` | `(data) => data` | Transform data before rendering | +| `pageSize` | `number` | Rows per page (default: 10) | +| `pageSizeOptions` | `number[]` | Page size options (default: [10, 25, 50, 100]) | +| `enableRowSelection` | `boolean` | Enable row selection checkboxes | +| `onRowSelectionChange` | `(selection) => void` | Row selection callback | +| `labels` | `DataTableLabels` | Customize UI labels | + +## Hooks + +### useAnalyticsQuery + +Subscribe to analytics queries via SSE with automatic state management: + +```tsx +import { useAnalyticsQuery } from "@databricks/app-kit-ui/react"; + +function UserList() { + const { data, loading, error } = useAnalyticsQuery( + "users_list", // queryKey + { status: "active" }, // parameters + { autoStart: true } // options + ); + + if (loading) return
Loading...
; + if (error) return
Error: {error}
; + + return ( +
    + {data?.map(user =>
  • {user.name}
  • )} +
+ ); +} +``` + +### useChartData + +Lower-level hook for fetching chart data: + +```tsx +import { useChartData } from "@databricks/app-kit-ui/react"; + +const { data, loading, error } = useChartData({ + queryKey: "metrics", + parameters: { days: 30 }, + transformer: (raw) => raw.filter(d => d.value > 0), +}); +``` + +## SSE Utilities (JavaScript) + +### connectSSE + +Connect to SSE endpoints with automatic retries: + +```typescript +import { connectSSE } from "@databricks/app-kit-ui/js"; + +await connectSSE({ + url: "/api/stream/events", + payload: { filter: "important" }, // Optional POST body + onMessage: (message) => { + console.log("Received:", JSON.parse(message.data)); + }, + onError: (error) => { + console.error("SSE error:", error); + }, + signal: abortController.signal, + maxRetries: 3, + retryDelay: 2000, + timeout: 300000, +}); +``` + +### SQL Type Markers + +Type-safe SQL parameter helpers: + +```typescript +import { sql } from "@databricks/app-kit-ui/js"; + +const params = { + userId: sql.string("user-123"), + createdAt: sql.timestamp(new Date()), + isActive: sql.boolean(true), + count: sql.number(42), +}; +``` + +## Styling + +### CSS Variables + +The library uses CSS variables for theming. Include the base styles: + +```tsx +// In your app entry (e.g., main.tsx) +import "@databricks/app-kit-ui/styles.css"; +``` + +### Tailwind Integration + +Components use Tailwind CSS classes. Extend your `tailwind.config.ts`: + +```typescript +export default { + content: [ + "./src/**/*.{js,ts,jsx,tsx}", + "./node_modules/@databricks/app-kit-ui/dist/**/*.{js,mjs}", + ], + theme: { + extend: { + colors: { + border: "hsl(var(--border))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: "hsl(var(--primary))", + // ... other theme colors + }, + }, + }, +}; +``` + +## Integration with app-kit Backend + +Charts and tables automatically connect to `@databricks/app-kit` analytics endpoints: + +``` +Frontend Backend +──────── ─────── + + │ + └─► POST /api/analytics/query/sales + │ + └─► config/queries/sales.sql + │ + └─► SQL Warehouse +``` + +**Query files** are stored in `config/queries/.sql` on the backend. + +## Best Practices + +1. **Always include styles**: Import `@databricks/app-kit-ui/styles.css` in your app entry +2. **Use queryKey naming**: Match `queryKey` props to SQL file names in `config/queries/` +3. **Handle loading states**: All components handle loading/error states, but you can customize +4. **Prefer opinionated mode**: Start with auto-detection, switch to full control when needed +5. **Use transformers**: Apply data transformations via `transformer`/`transform` props, not in render + +## Anti-Patterns + +- Do not import from internal paths (e.g., `@databricks/app-kit-ui/dist/...`) +- Do not skip the styles import (components may render incorrectly) +- Do not use inline SQL in parameters (SQL lives in backend query files) +- Do not call hooks conditionally (React rules apply) +- Do not mutate data in transformers (return new arrays/objects) + diff --git a/packages/app-kit-ui/package.json b/packages/app-kit-ui/package.json index e06b564..16abb60 100644 --- a/packages/app-kit-ui/package.json +++ b/packages/app-kit-ui/package.json @@ -5,6 +5,9 @@ "packageManager": "pnpm@10.21.0", "files": [ "dist", + "bin", + "scripts", + "CLAUDE.md", "llms.txt", "README.md", "DCO", diff --git a/packages/app-kit/CLAUDE.md b/packages/app-kit/CLAUDE.md new file mode 100644 index 0000000..a4bd127 --- /dev/null +++ b/packages/app-kit/CLAUDE.md @@ -0,0 +1,274 @@ +# CLAUDE.md - @databricks/app-kit + +AI assistant guidance for the Databricks AppKit backend SDK. + +## Overview + +`@databricks/app-kit` is a modular TypeScript SDK for building Databricks applications with a plugin-based architecture. It provides a unified way to create backend services with built-in support for analytics, streaming, caching, and telemetry. + +## Core Pattern + +Always use `createApp` to initialize the SDK with plugins: + +```typescript +import { createApp, server, analytics } from "@databricks/app-kit"; + +const AppKit = await createApp({ + plugins: [ + server({ port: 8000 }), + analytics(), + ], +}); +``` + +After initialization, plugins are accessible via `AppKit[pluginName]`. + +## Built-in Plugins + +### Server Plugin + +Starts an Express server with automatic frontend serving: + +```typescript +import { createApp, server } from "@databricks/app-kit"; + +// Auto-start (default) +await createApp({ + plugins: [server({ port: 8000 })], +}); + +// Manual start for custom routes +const AppKit = await createApp({ + plugins: [server({ port: 8000, autoStart: false })], +}); + +const app = await AppKit.server.start(); +app.get("/custom", (req, res) => res.json({ ok: true })); +``` + +**Configuration Options:** +- `port`: Server port (default: 8000 or `DATABRICKS_APP_PORT`) +- `host`: Server host (default: "0.0.0.0" or `FLASK_RUN_HOST`) +- `autoStart`: Auto-start server on init (default: true) +- `staticPath`: Path to static files (auto-detected if not set) + +### Analytics Plugin + +Execute SQL queries against Databricks SQL Warehouse: + +```typescript +import { createApp, server, analytics } from "@databricks/app-kit"; + +await createApp({ + plugins: [ + server({ port: 8000 }), + analytics(), + ], +}); +``` + +**Endpoints:** +- `POST /api/analytics/query/:query_key` - Execute query as service principal +- `POST /api/analytics/users/me/query/:query_key` - Execute query as user (token passthrough) + +**Query Files:** +Store SQL queries in `config/queries/.sql`. Use parameterized queries: + +```sql +-- config/queries/user_activity.sql +SELECT * FROM users WHERE created_at > :start_date AND status = :status +``` + +**Request Body:** +```json +{ + "parameters": { + "start_date": "2024-01-01", + "status": "active" + } +} +``` + +## Creating Custom Plugins + +Extend the `Plugin` class to create custom functionality: + +```typescript +import { Plugin, toPlugin } from "@databricks/app-kit"; +import type express from "express"; + +interface WeatherConfig { + apiKey?: string; +} + +class WeatherPlugin extends Plugin { + name = "weather"; + protected envVars = ["WEATHER_API_KEY"]; // Required env vars + + async getWeather(city: string): Promise { + // Use this.execute() for interceptor support (cache, retry, timeout) + return this.execute( + async (signal) => { + const response = await fetch(`https://api.weather.com/${city}`, { signal }); + return response.json(); + }, + { + default: { + cache: { enabled: true, cacheKey: ["weather", city], ttl: 300000 }, + retry: { enabled: true, attempts: 3 }, + timeout: 5000, + }, + }, + city, // userKey for cache scoping + ); + } + + // Register HTTP routes (scoped to /api/weather/*) + injectRoutes(router: express.Router) { + router.get("/:city", async (req, res) => { + const data = await this.getWeather(req.params.city); + res.json(data); + }); + } +} + +export const weather = toPlugin( + WeatherPlugin, + "weather" +); +``` + +**Usage:** +```typescript +import { createApp, server } from "@databricks/app-kit"; +import { weather } from "./weather-plugin"; + +const AppKit = await createApp({ + plugins: [ + server({ port: 8000 }), + weather({ apiKey: "..." }), + ], +}); + +// Direct access +const data = await AppKit.weather.getWeather("Seattle"); +``` + +## Execution Interceptors + +Plugins use `execute()` or `executeStream()` which apply interceptors in order: + +1. **TelemetryInterceptor** - Traces execution span +2. **TimeoutInterceptor** - AbortSignal timeout +3. **RetryInterceptor** - Exponential backoff retry +4. **CacheInterceptor** - TTL-based caching + +```typescript +await this.execute( + async (signal) => expensiveOperation(signal), + { + default: { + cache: { enabled: true, cacheKey: ["op", id], ttl: 60000 }, + retry: { enabled: true, attempts: 3, delay: 1000 }, + timeout: 5000, + telemetryInterceptor: { enabled: true }, + }, + }, + userKey, +); +``` + +## SSE Streaming + +For Server-Sent Events streaming with automatic reconnection: + +```typescript +class MyPlugin extends Plugin { + injectRoutes(router: express.Router) { + router.get("/stream", async (req, res) => { + await this.executeStream( + res, + async function* (signal) { + for (let i = 0; i < 10; i++) { + if (signal?.aborted) break; + yield { type: "progress", data: i }; + await new Promise(r => setTimeout(r, 1000)); + } + yield { type: "complete", data: "done" }; + }, + { stream: { streamId: req.query.streamId } }, + "user-key", + ); + }); + } +} +``` + +**Stream Features:** +- Connection ID-based tracking +- Event ring buffer for reconnection replay +- Automatic heartbeat +- Per-stream abort signals + +## Telemetry (OpenTelemetry) + +Enable traces, metrics, and logs: + +```typescript +await createApp({ + plugins: [server(), analytics()], + telemetry: { + traces: true, + metrics: true, + logs: true, + }, +}); +``` + +**Environment Variables:** +- `OTEL_EXPORTER_OTLP_ENDPOINT` - OpenTelemetry collector endpoint + +## Caching + +Configure global cache settings: + +```typescript +await createApp({ + plugins: [...], + cache: { + type: "memory", // or "persistent" + maxSize: 1000, + defaultTTL: 300000, + }, +}); +``` + +## Type Generation + +Generate TypeScript types from SQL queries using the Vite plugin: + +```typescript +// vite.config.ts +import { appKitTypesPlugin } from "@databricks/app-kit"; + +export default { + plugins: [appKitTypesPlugin()], +}; +``` + +## Style Guidelines + +- Always use async/await (never `.then()` chaining) +- Always initialize with `createApp()` before using plugins +- Use ESModules (`import`/`export`), not `require()` +- Store SQL in `config/queries/*.sql`, never inline +- Use parameterized queries for all user input + +## Anti-Patterns + +- Do not access AppKit internals (only use `AppKit[pluginName]`) +- Do not call plugin methods before `createApp()` resolves +- Do not use `.then()` chaining in examples +- Do not hardcode SQL queries in TypeScript files +- Do not bypass `execute()`/`executeStream()` for operations that need interceptors + diff --git a/packages/app-kit/package.json b/packages/app-kit/package.json index b8324f2..2bc95e3 100644 --- a/packages/app-kit/package.json +++ b/packages/app-kit/package.json @@ -7,6 +7,9 @@ "packageManager": "pnpm@10.21.0", "files": [ "dist", + "bin", + "scripts", + "CLAUDE.md", "llms.txt", "README.md", "DCO", diff --git a/packages/shared/bin/setup-claude.js b/packages/shared/bin/setup-claude.js new file mode 100644 index 0000000..b0edd96 --- /dev/null +++ b/packages/shared/bin/setup-claude.js @@ -0,0 +1,190 @@ +#!/usr/bin/env node + +/** + * CLI tool to setup CLAUDE.md for Databricks AppKit packages. + * + * This bin is included in both @databricks/app-kit and @databricks/app-kit-ui + * so it's available regardless of which package the user installs. + * + * Usage: + * npx appkit-setup # Show detected packages and content + * npx appkit-setup --write # Create or update CLAUDE.md file + */ + +import fs from "node:fs"; +import path from "node:path"; + +const PACKAGES = [ + { name: "@databricks/app-kit", description: "Backend SDK" }, + { + name: "@databricks/app-kit-ui", + description: "UI Integration, Charts, Tables, SSE, and more.", + }, +]; + +const SECTION_START = ""; +const SECTION_END = ""; + +/** + * Find which AppKit packages are installed by checking for CLAUDE.md + */ +function findInstalledPackages() { + const cwd = process.cwd(); + const installed = []; + + for (const pkg of PACKAGES) { + const claudePath = path.join(cwd, "node_modules", pkg.name, "CLAUDE.md"); + if (fs.existsSync(claudePath)) { + installed.push(pkg); + } + } + + return installed; +} + +/** + * Generate the AppKit section content + */ +function generateSection(packages) { + const links = packages + .map((pkg) => { + const docPath = `./node_modules/${pkg.name}/CLAUDE.md`; + return `- **${pkg.name}** (${pkg.description}): [${docPath}](${docPath})`; + }) + .join("\n"); + + return `${SECTION_START} +## Databricks AppKit + +This project uses Databricks AppKit packages. For AI assistant guidance on using these packages, refer to: + +${links} +${SECTION_END}`; +} + +/** + * Generate standalone CLAUDE.md content (when no existing file) + */ +function generateStandalone(packages) { + const links = packages + .map((pkg) => { + const docPath = `./node_modules/${pkg.name}/CLAUDE.md`; + return `- **${pkg.name}** (${pkg.description}): [${docPath}](${docPath})`; + }) + .join("\n"); + + return `# AI Assistant Instructions + +${SECTION_START} +## Databricks AppKit + +This project uses Databricks AppKit packages. For AI assistant guidance on using these packages, refer to: + +${links} +${SECTION_END} +`; +} + +/** + * Update existing content with AppKit section + */ +function updateContent(existingContent, packages) { + const newSection = generateSection(packages); + + // Check if AppKit section already exists + const startIndex = existingContent.indexOf(SECTION_START); + const endIndex = existingContent.indexOf(SECTION_END); + + if (startIndex !== -1 && endIndex !== -1) { + // Replace existing section + const before = existingContent.substring(0, startIndex); + const after = existingContent.substring(endIndex + SECTION_END.length); + return before + newSection + after; + } + + // Append section to end + return `${existingContent.trimEnd()}\n\n${newSection}\n`; +} + +/** + * Main CLI logic + */ +function main() { + const args = process.argv.slice(2); + const shouldWrite = args.includes("--write") || args.includes("-w"); + const help = args.includes("--help") || args.includes("-h"); + + if (help) { + console.log(` +Usage: npx appkit-setup [options] + +Options: + --write, -w Create or update CLAUDE.md file in current directory + --help, -h Show this help message + +Examples: + npx appkit-setup # Show detected packages and preview content + npx appkit-setup --write # Create or update CLAUDE.md +`); + return; + } + + // Find installed packages + const installed = findInstalledPackages(); + + if (installed.length === 0) { + console.log("No @databricks/app-kit packages found in node_modules."); + console.log("\nMake sure you've installed at least one of:"); + PACKAGES.forEach((pkg) => { + console.log(` - ${pkg.name}`); + }); + process.exit(1); + } + + console.log("Detected packages:"); + installed.forEach((pkg) => { + console.log(` ✓ ${pkg.name}`); + }); + + const claudePath = path.join(process.cwd(), "CLAUDE.md"); + const existingContent = fs.existsSync(claudePath) + ? fs.readFileSync(claudePath, "utf-8") + : null; + + let finalContent; + let action; + + if (existingContent) { + finalContent = updateContent(existingContent, installed); + action = existingContent.includes(SECTION_START) ? "Updated" : "Added to"; + } else { + finalContent = generateStandalone(installed); + action = "Created"; + } + + if (shouldWrite) { + fs.writeFileSync(claudePath, finalContent); + console.log(`\n✓ ${action} CLAUDE.md`); + console.log(` Path: ${claudePath}`); + } else { + console.log("\nTo create/update CLAUDE.md, run:"); + console.log(" npx appkit-setup --write\n"); + + if (existingContent) { + console.log( + `This will ${ + existingContent.includes(SECTION_START) + ? "update the existing" + : "add a new" + } AppKit section.\n`, + ); + } + + console.log("Preview of AppKit section:\n"); + console.log("─".repeat(50)); + console.log(generateSection(installed)); + console.log("─".repeat(50)); + } +} + +main(); diff --git a/packages/shared/scripts/postinstall.js b/packages/shared/scripts/postinstall.js new file mode 100644 index 0000000..bd86acf --- /dev/null +++ b/packages/shared/scripts/postinstall.js @@ -0,0 +1,6 @@ +#!/usr/bin/env node +console.log(""); +console.log("[@databricks/app-kit] To setup AI assistant instructions, run:"); +console.log(""); +console.log(" npx appkit-setup --write"); +console.log(""); diff --git a/tools/dist.ts b/tools/dist.ts index 3bd26cf..81ef265 100644 --- a/tools/dist.ts +++ b/tools/dist.ts @@ -12,11 +12,54 @@ delete pkg.dependencies.shared; pkg.exports = pkg.publishConfig.exports; delete pkg.publishConfig.exports; +const isAppKitPackage = pkg.name?.startsWith("@databricks/app-kit"); +const sharedBin = path.join( + __dirname, + "../packages/shared/bin/setup-claude.js", +); +const sharedPostinstall = path.join( + __dirname, + "../packages/shared/scripts/postinstall.js", +); + +// Add appkit-setup bin and postinstall for @databricks/app-kit* packages +if (isAppKitPackage) { + if (fs.existsSync(sharedBin)) { + pkg.bin = pkg.bin || {}; + pkg.bin["appkit-setup"] = "./bin/setup-claude.js"; + } + if (fs.existsSync(sharedPostinstall)) { + pkg.scripts = pkg.scripts || {}; + pkg.scripts.postinstall = "node scripts/postinstall.js"; + } +} + fs.writeFileSync("tmp/package.json", JSON.stringify(pkg, null, 2)); fs.cpSync("dist", "tmp/dist", { recursive: true }); -fs.copyFileSync(path.join(__dirname, "../llms.txt"), "tmp/llms.txt"); +// Copy bin and scripts from shared package +if (isAppKitPackage) { + if (fs.existsSync(sharedBin)) { + fs.mkdirSync("tmp/bin", { recursive: true }); + fs.copyFileSync(sharedBin, "tmp/bin/setup-claude.js"); + } + if (fs.existsSync(sharedPostinstall)) { + fs.mkdirSync("tmp/scripts", { recursive: true }); + fs.copyFileSync(sharedPostinstall, "tmp/scripts/postinstall.js"); + } +} + +if (fs.existsSync("CLAUDE.md")) { + fs.copyFileSync("CLAUDE.md", "tmp/CLAUDE.md"); +} + +if (fs.existsSync("llms.txt")) { + fs.copyFileSync("llms.txt", "tmp/llms.txt"); +} else { + fs.copyFileSync(path.join(__dirname, "../llms.txt"), "tmp/llms.txt"); +} + fs.copyFileSync(path.join(__dirname, "../README.md"), "tmp/README.md"); fs.copyFileSync(path.join(__dirname, "../LICENSE"), "tmp/LICENSE"); fs.copyFileSync(path.join(__dirname, "../DCO"), "tmp/DCO"); From ec7df82fd50e0ded4b1099c8aac7efabf21dee0d Mon Sep 17 00:00:00 2001 From: MarioCadenas Date: Fri, 19 Dec 2025 18:02:32 +0100 Subject: [PATCH 2/2] chore: fixup --- packages/app-kit-ui/CLAUDE.md | 320 +------------------------ packages/app-kit/CLAUDE.md | 275 +-------------------- packages/shared/scripts/postinstall.js | 2 +- 3 files changed, 5 insertions(+), 592 deletions(-) diff --git a/packages/app-kit-ui/CLAUDE.md b/packages/app-kit-ui/CLAUDE.md index 4dd6e7d..8ffdd96 100644 --- a/packages/app-kit-ui/CLAUDE.md +++ b/packages/app-kit-ui/CLAUDE.md @@ -1,319 +1,3 @@ -# CLAUDE.md - @databricks/app-kit-ui - -AI assistant guidance for the Databricks AppKit UI component library. - -## Overview - -`@databricks/app-kit-ui` provides React components and JavaScript utilities for building Databricks application frontends. It includes data visualization charts, data tables, and SSE utilities that integrate seamlessly with the `@databricks/app-kit` backend. - -## Installation & Imports - -```typescript -// React components -import { BarChart, LineChart, DataTable, useAnalyticsQuery } from "@databricks/app-kit-ui/react"; - -// JavaScript utilities (SSE, SQL helpers) -import { connectSSE, sql } from "@databricks/app-kit-ui/js"; - -// Global styles (include in your app entry) -import "@databricks/app-kit-ui/styles.css"; -``` - -## Chart Components - -All charts integrate with the analytics backend via `queryKey` and support two modes: -- **Opinionated mode**: Automatic field detection and rendering -- **Full control mode**: Pass children for custom Recharts configuration - -### BarChart - -```tsx -import { BarChart } from "@databricks/app-kit-ui/react"; - -// Opinionated mode - auto-detects x/y fields - - -// Horizontal orientation - - -// With data transformation - data.map(d => ({ name: d.user, count: d.total }))} -/> - -// Full control mode - - - - -``` - -### LineChart - -```tsx -import { LineChart } from "@databricks/app-kit-ui/react"; - - -``` - -### AreaChart - -```tsx -import { AreaChart } from "@databricks/app-kit-ui/react"; - - -``` - -### PieChart - -```tsx -import { PieChart } from "@databricks/app-kit-ui/react"; - - -``` - -### RadarChart - -```tsx -import { RadarChart } from "@databricks/app-kit-ui/react"; - - -``` - -### Common Chart Props - -| Prop | Type | Description | -|------|------|-------------| -| `queryKey` | `string` | Analytics query identifier (maps to `config/queries/*.sql`) | -| `parameters` | `object` | Query parameters | -| `transformer` | `(data) => data` | Transform data before rendering | -| `height` | `string` | Chart height (default: "300px") | -| `orientation` | `"vertical" \| "horizontal"` | Bar chart orientation | -| `chartConfig` | `ChartConfig` | Custom Recharts config | -| `ariaLabel` | `string` | Accessibility label | -| `testId` | `string` | Test ID attribute | -| `className` | `string` | Additional CSS classes | - -## DataTable Component - -Production-ready data table with automatic data fetching, filtering, sorting, and pagination: - -```tsx -import { DataTable } from "@databricks/app-kit-ui/react"; - -// Opinionated mode - - -// With row selection - console.log(selection)} -/> - -// Full control mode - - {(table) => ( -
- {table.getRowModel().rows.map(row => ( -
{row.original.name}
- ))} -
- )} -
-``` - -### DataTable Props - -| Prop | Type | Description | -|------|------|-------------| -| `queryKey` | `string` | Analytics query identifier | -| `parameters` | `object` | Query parameters | -| `filterColumn` | `string` | Column to filter by (auto-detected if not set) | -| `filterPlaceholder` | `string` | Filter input placeholder | -| `transform` | `(data) => data` | Transform data before rendering | -| `pageSize` | `number` | Rows per page (default: 10) | -| `pageSizeOptions` | `number[]` | Page size options (default: [10, 25, 50, 100]) | -| `enableRowSelection` | `boolean` | Enable row selection checkboxes | -| `onRowSelectionChange` | `(selection) => void` | Row selection callback | -| `labels` | `DataTableLabels` | Customize UI labels | - -## Hooks - -### useAnalyticsQuery - -Subscribe to analytics queries via SSE with automatic state management: - -```tsx -import { useAnalyticsQuery } from "@databricks/app-kit-ui/react"; - -function UserList() { - const { data, loading, error } = useAnalyticsQuery( - "users_list", // queryKey - { status: "active" }, // parameters - { autoStart: true } // options - ); - - if (loading) return
Loading...
; - if (error) return
Error: {error}
; - - return ( -
    - {data?.map(user =>
  • {user.name}
  • )} -
- ); -} -``` - -### useChartData - -Lower-level hook for fetching chart data: - -```tsx -import { useChartData } from "@databricks/app-kit-ui/react"; - -const { data, loading, error } = useChartData({ - queryKey: "metrics", - parameters: { days: 30 }, - transformer: (raw) => raw.filter(d => d.value > 0), -}); -``` - -## SSE Utilities (JavaScript) - -### connectSSE - -Connect to SSE endpoints with automatic retries: - -```typescript -import { connectSSE } from "@databricks/app-kit-ui/js"; - -await connectSSE({ - url: "/api/stream/events", - payload: { filter: "important" }, // Optional POST body - onMessage: (message) => { - console.log("Received:", JSON.parse(message.data)); - }, - onError: (error) => { - console.error("SSE error:", error); - }, - signal: abortController.signal, - maxRetries: 3, - retryDelay: 2000, - timeout: 300000, -}); -``` - -### SQL Type Markers - -Type-safe SQL parameter helpers: - -```typescript -import { sql } from "@databricks/app-kit-ui/js"; - -const params = { - userId: sql.string("user-123"), - createdAt: sql.timestamp(new Date()), - isActive: sql.boolean(true), - count: sql.number(42), -}; -``` - -## Styling - -### CSS Variables - -The library uses CSS variables for theming. Include the base styles: - -```tsx -// In your app entry (e.g., main.tsx) -import "@databricks/app-kit-ui/styles.css"; -``` - -### Tailwind Integration - -Components use Tailwind CSS classes. Extend your `tailwind.config.ts`: - -```typescript -export default { - content: [ - "./src/**/*.{js,ts,jsx,tsx}", - "./node_modules/@databricks/app-kit-ui/dist/**/*.{js,mjs}", - ], - theme: { - extend: { - colors: { - border: "hsl(var(--border))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: "hsl(var(--primary))", - // ... other theme colors - }, - }, - }, -}; -``` - -## Integration with app-kit Backend - -Charts and tables automatically connect to `@databricks/app-kit` analytics endpoints: - -``` -Frontend Backend -──────── ─────── - - │ - └─► POST /api/analytics/query/sales - │ - └─► config/queries/sales.sql - │ - └─► SQL Warehouse -``` - -**Query files** are stored in `config/queries/.sql` on the backend. - -## Best Practices - -1. **Always include styles**: Import `@databricks/app-kit-ui/styles.css` in your app entry -2. **Use queryKey naming**: Match `queryKey` props to SQL file names in `config/queries/` -3. **Handle loading states**: All components handle loading/error states, but you can customize -4. **Prefer opinionated mode**: Start with auto-detection, switch to full control when needed -5. **Use transformers**: Apply data transformations via `transformer`/`transform` props, not in render - -## Anti-Patterns - -- Do not import from internal paths (e.g., `@databricks/app-kit-ui/dist/...`) -- Do not skip the styles import (components may render incorrectly) -- Do not use inline SQL in parameters (SQL lives in backend query files) -- Do not call hooks conditionally (React rules apply) -- Do not mutate data in transformers (return new arrays/objects) +# CLAUDE.md - @databricks/appkit-ui +## TBD \ No newline at end of file diff --git a/packages/app-kit/CLAUDE.md b/packages/app-kit/CLAUDE.md index a4bd127..d17f226 100644 --- a/packages/app-kit/CLAUDE.md +++ b/packages/app-kit/CLAUDE.md @@ -1,274 +1,3 @@ -# CLAUDE.md - @databricks/app-kit - -AI assistant guidance for the Databricks AppKit backend SDK. - -## Overview - -`@databricks/app-kit` is a modular TypeScript SDK for building Databricks applications with a plugin-based architecture. It provides a unified way to create backend services with built-in support for analytics, streaming, caching, and telemetry. - -## Core Pattern - -Always use `createApp` to initialize the SDK with plugins: - -```typescript -import { createApp, server, analytics } from "@databricks/app-kit"; - -const AppKit = await createApp({ - plugins: [ - server({ port: 8000 }), - analytics(), - ], -}); -``` - -After initialization, plugins are accessible via `AppKit[pluginName]`. - -## Built-in Plugins - -### Server Plugin - -Starts an Express server with automatic frontend serving: - -```typescript -import { createApp, server } from "@databricks/app-kit"; - -// Auto-start (default) -await createApp({ - plugins: [server({ port: 8000 })], -}); - -// Manual start for custom routes -const AppKit = await createApp({ - plugins: [server({ port: 8000, autoStart: false })], -}); - -const app = await AppKit.server.start(); -app.get("/custom", (req, res) => res.json({ ok: true })); -``` - -**Configuration Options:** -- `port`: Server port (default: 8000 or `DATABRICKS_APP_PORT`) -- `host`: Server host (default: "0.0.0.0" or `FLASK_RUN_HOST`) -- `autoStart`: Auto-start server on init (default: true) -- `staticPath`: Path to static files (auto-detected if not set) - -### Analytics Plugin - -Execute SQL queries against Databricks SQL Warehouse: - -```typescript -import { createApp, server, analytics } from "@databricks/app-kit"; - -await createApp({ - plugins: [ - server({ port: 8000 }), - analytics(), - ], -}); -``` - -**Endpoints:** -- `POST /api/analytics/query/:query_key` - Execute query as service principal -- `POST /api/analytics/users/me/query/:query_key` - Execute query as user (token passthrough) - -**Query Files:** -Store SQL queries in `config/queries/.sql`. Use parameterized queries: - -```sql --- config/queries/user_activity.sql -SELECT * FROM users WHERE created_at > :start_date AND status = :status -``` - -**Request Body:** -```json -{ - "parameters": { - "start_date": "2024-01-01", - "status": "active" - } -} -``` - -## Creating Custom Plugins - -Extend the `Plugin` class to create custom functionality: - -```typescript -import { Plugin, toPlugin } from "@databricks/app-kit"; -import type express from "express"; - -interface WeatherConfig { - apiKey?: string; -} - -class WeatherPlugin extends Plugin { - name = "weather"; - protected envVars = ["WEATHER_API_KEY"]; // Required env vars - - async getWeather(city: string): Promise { - // Use this.execute() for interceptor support (cache, retry, timeout) - return this.execute( - async (signal) => { - const response = await fetch(`https://api.weather.com/${city}`, { signal }); - return response.json(); - }, - { - default: { - cache: { enabled: true, cacheKey: ["weather", city], ttl: 300000 }, - retry: { enabled: true, attempts: 3 }, - timeout: 5000, - }, - }, - city, // userKey for cache scoping - ); - } - - // Register HTTP routes (scoped to /api/weather/*) - injectRoutes(router: express.Router) { - router.get("/:city", async (req, res) => { - const data = await this.getWeather(req.params.city); - res.json(data); - }); - } -} - -export const weather = toPlugin( - WeatherPlugin, - "weather" -); -``` - -**Usage:** -```typescript -import { createApp, server } from "@databricks/app-kit"; -import { weather } from "./weather-plugin"; - -const AppKit = await createApp({ - plugins: [ - server({ port: 8000 }), - weather({ apiKey: "..." }), - ], -}); - -// Direct access -const data = await AppKit.weather.getWeather("Seattle"); -``` - -## Execution Interceptors - -Plugins use `execute()` or `executeStream()` which apply interceptors in order: - -1. **TelemetryInterceptor** - Traces execution span -2. **TimeoutInterceptor** - AbortSignal timeout -3. **RetryInterceptor** - Exponential backoff retry -4. **CacheInterceptor** - TTL-based caching - -```typescript -await this.execute( - async (signal) => expensiveOperation(signal), - { - default: { - cache: { enabled: true, cacheKey: ["op", id], ttl: 60000 }, - retry: { enabled: true, attempts: 3, delay: 1000 }, - timeout: 5000, - telemetryInterceptor: { enabled: true }, - }, - }, - userKey, -); -``` - -## SSE Streaming - -For Server-Sent Events streaming with automatic reconnection: - -```typescript -class MyPlugin extends Plugin { - injectRoutes(router: express.Router) { - router.get("/stream", async (req, res) => { - await this.executeStream( - res, - async function* (signal) { - for (let i = 0; i < 10; i++) { - if (signal?.aborted) break; - yield { type: "progress", data: i }; - await new Promise(r => setTimeout(r, 1000)); - } - yield { type: "complete", data: "done" }; - }, - { stream: { streamId: req.query.streamId } }, - "user-key", - ); - }); - } -} -``` - -**Stream Features:** -- Connection ID-based tracking -- Event ring buffer for reconnection replay -- Automatic heartbeat -- Per-stream abort signals - -## Telemetry (OpenTelemetry) - -Enable traces, metrics, and logs: - -```typescript -await createApp({ - plugins: [server(), analytics()], - telemetry: { - traces: true, - metrics: true, - logs: true, - }, -}); -``` - -**Environment Variables:** -- `OTEL_EXPORTER_OTLP_ENDPOINT` - OpenTelemetry collector endpoint - -## Caching - -Configure global cache settings: - -```typescript -await createApp({ - plugins: [...], - cache: { - type: "memory", // or "persistent" - maxSize: 1000, - defaultTTL: 300000, - }, -}); -``` - -## Type Generation - -Generate TypeScript types from SQL queries using the Vite plugin: - -```typescript -// vite.config.ts -import { appKitTypesPlugin } from "@databricks/app-kit"; - -export default { - plugins: [appKitTypesPlugin()], -}; -``` - -## Style Guidelines - -- Always use async/await (never `.then()` chaining) -- Always initialize with `createApp()` before using plugins -- Use ESModules (`import`/`export`), not `require()` -- Store SQL in `config/queries/*.sql`, never inline -- Use parameterized queries for all user input - -## Anti-Patterns - -- Do not access AppKit internals (only use `AppKit[pluginName]`) -- Do not call plugin methods before `createApp()` resolves -- Do not use `.then()` chaining in examples -- Do not hardcode SQL queries in TypeScript files -- Do not bypass `execute()`/`executeStream()` for operations that need interceptors +# CLAUDE.md - @databricks/appkit +## TBD \ No newline at end of file diff --git a/packages/shared/scripts/postinstall.js b/packages/shared/scripts/postinstall.js index bd86acf..3db18ec 100644 --- a/packages/shared/scripts/postinstall.js +++ b/packages/shared/scripts/postinstall.js @@ -1,6 +1,6 @@ #!/usr/bin/env node console.log(""); -console.log("[@databricks/app-kit] To setup AI assistant instructions, run:"); +console.log("[@databricks/appkit] To setup AI assistant instructions, run:"); console.log(""); console.log(" npx appkit-setup --write"); console.log("");