Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"ignoreDependencies": ["tailwindcss", "tw-animate-css"]
}
},
"ignoreDependencies": ["react-docgen-typescript"],
"ignoreDependencies": ["react-docgen-typescript", "esbuild", "ora", "zod"],
"ignore": [
"**/*.generated.ts",
"**/*.example.tsx",
Expand All @@ -51,6 +51,7 @@
"packages/appkit/src/core/agent/load-agents.ts",
"packages/appkit/src/connectors/mcp/index.ts",
"packages/appkit/src/typedoc.entry.ts",
"packages/appkit/bin/appkit.js",
"template/**",
"tools/**",
"docs/**",
Expand Down
4 changes: 4 additions & 0 deletions packages/appkit/src/plugins/lakebase/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
},
"scaffolding": {
"rules": {
"must": [
"Use withLakebaseTraceContext for direct Lakebase SQL to attach active request trace context at query execution",
"Keep SQL values in separate query parameters"
],
"should": [
"After init, run any database migrations for your chosen ORM before first request",
"After init, verify Lakebase connectivity with 'psql $PGHOST -c \"select 1\"'"
Expand Down
19 changes: 19 additions & 0 deletions template/README.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,25 @@ DATABRICKS_APP_PORT=8000
#### Lakebase Configuration

The Lakebase plugin requires additional environment variables for PostgreSQL connectivity. To learn how to configure the Lakebase plugin, see the [Lakebase plugin documentation](https://developers.databricks.com/docs/appkit/v0/plugins/lakebase).

#### Correlate app requests with Lakebase query plans

The sample routes execute Lakebase SQL through `withLakebaseTraceContext`. At query execution time, the wrapper reads the active AppKit request span and prepends its W3C `traceparent` plus a sanitized service label as a SQL comment. The label comes from `OTEL_SERVICE_NAME`, then `DATABRICKS_APP_NAME`, then `databricks-app`. If you configure a different AppKit telemetry service name, pass that name as the wrapper's second argument. The wrapper does not create a span, interpolate parameters, cache annotated SQL, or store request state on the connection pool. Without a valid active span, it sends the original SQL unchanged.

When Databricks Apps provides `OTEL_EXPORTER_OTLP_ENDPOINT`, AppKit initializes request instrumentation and OTLP export. Configure an Apps OpenTelemetry destination to have the runtime inject that endpoint and make sampled application spans available in Unity Catalog:

```yaml
resources:
apps:
app:
telemetry_export_destinations:
- unity_catalog:
logs_table: <catalog>.<schema>.<app>_otel_logs
metrics_table: <catalog>.<schema>.<app>_otel_metrics
traces_table: <catalog>.<schema>.<app>_otel_spans
```

When Lakebase captures a query plan, it parses the SQL comment into `system.lakebase.plan_history`; it does not export the application span. For sampled requests, query the configured traces table for the app-side span and look for a captured plan with matching `trace_id` or `span_id`. Plan capture filters and retention still apply. Code that bypasses the wrapper (for example, a raw pool, checked-out client, ORM, or the opt-in Lakebase agent tool) is not covered by this template helper.
{{- end}}

{{- if .plugins.database}}
Expand Down
4 changes: 4 additions & 0 deletions template/appkit.plugins.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions template/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"dependencies": {
"@databricks/appkit": "0.94.0",
"@databricks/appkit-ui": "0.94.0",
"@opentelemetry/api": "1.9.0",
"clsx": "2.1.1",
"embla-carousel-react": "8.6.0",
"lucide-react": "0.546.0",
Expand All @@ -49,6 +50,8 @@
},
"devDependencies": {
"@ast-grep/napi": "0.37.0",
"@opentelemetry/context-async-hooks": "2.12.0",
"@opentelemetry/sdk-trace-base": "2.12.0",
"@tailwindcss/postcss": "4.1.17",
"@tailwindcss/vite": "4.1.17",
"@types/express": "5.0.5",
Expand Down
9 changes: 9 additions & 0 deletions template/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 10 additions & 7 deletions template/server/routes/lakebase/todo-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { z } from 'zod';
import { Application } from 'express';
import { withLakebaseTraceContext } from './trace-context';

interface AppKitWithLakebase {
lakebase: {
Expand Down Expand Up @@ -33,13 +34,15 @@ const CREATE_TABLE_SQL = `
const CreateTodoBody = z.object({ title: z.string().min(1) });

export async function setupSampleLakebaseRoutes(appkit: AppKitWithLakebase) {
const lakebase = withLakebaseTraceContext(appkit.lakebase);

try {
const { rows } = await appkit.lakebase.query(TABLE_EXISTS_SQL);
const { rows } = await lakebase.query(TABLE_EXISTS_SQL);
if (rows.length > 0) {
console.log('[lakebase] Table app.todos already exists, skipping setup');
} else {
await appkit.lakebase.query(SETUP_SCHEMA_SQL);
await appkit.lakebase.query(CREATE_TABLE_SQL);
await lakebase.query(SETUP_SCHEMA_SQL);
await lakebase.query(CREATE_TABLE_SQL);
console.log('[lakebase] Created schema and table app.todos');
}
} catch (err) {
Expand All @@ -51,7 +54,7 @@ export async function setupSampleLakebaseRoutes(appkit: AppKitWithLakebase) {
appkit.server.extend((app) => {
app.get('/api/lakebase/todos', async (_req, res) => {
try {
const result = await appkit.lakebase.query(
const result = await lakebase.query(
'SELECT id, title, completed, created_at FROM app.todos ORDER BY created_at DESC',
);
res.json(result.rows);
Expand All @@ -68,7 +71,7 @@ export async function setupSampleLakebaseRoutes(appkit: AppKitWithLakebase) {
res.status(400).json({ error: 'title is required' });
return;
}
const result = await appkit.lakebase.query(
const result = await lakebase.query(
'INSERT INTO app.todos (title) VALUES ($1) RETURNING id, title, completed, created_at',
[parsed.data.title.trim()],
);
Expand All @@ -86,7 +89,7 @@ export async function setupSampleLakebaseRoutes(appkit: AppKitWithLakebase) {
res.status(400).json({ error: 'Invalid id' });
return;
}
const result = await appkit.lakebase.query(
const result = await lakebase.query(
'UPDATE app.todos SET completed = NOT completed WHERE id = $1 RETURNING id, title, completed, created_at',
[id],
);
Expand All @@ -108,7 +111,7 @@ export async function setupSampleLakebaseRoutes(appkit: AppKitWithLakebase) {
res.status(400).json({ error: 'Invalid id' });
return;
}
const result = await appkit.lakebase.query(
const result = await lakebase.query(
'DELETE FROM app.todos WHERE id = $1 RETURNING id',
[id],
);
Expand Down
Loading