Skip to content
Merged
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
21 changes: 21 additions & 0 deletions apps/dev-playground/.appkit/serving-types-cache.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "1",
"endpoints": {
"default": {
"hash": "e66c960c5d57436d7fc4e21086650a534892a1548ccf2b333db2af6046832471",
"requestType": "{\n messages?: {\n role?: \"user\" | \"assistant\";\n content?: string;\n }[];\n /** @openapi integer, nullable */\n n?: number | null;\n max_tokens?: number;\n /** @openapi double, nullable */\n top_p?: number | null;\n reasoning_effort?: \"low\" | \"medium\" | \"high\" | \"max\" | null;\n /** @openapi double, nullable */\n temperature?: number | null;\n stop?: string | string[] | null;\n /** @openapi integer, nullable */\n top_k?: number | null;\n}",
"responseType": "{\n model?: string;\n choices?: {\n index?: number;\n message?: {\n role?: \"user\" | \"assistant\";\n content?: string;\n };\n finish_reason?: string;\n }[];\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n } | null;\n object?: string;\n id?: string;\n created?: number;\n}",
"chunkType": "{\n model?: string;\n choices?: {\n index?: number;\n delta?: {\n role?: \"user\" | \"assistant\";\n content?: string;\n };\n finish_reason?: string | null;\n }[];\n object?: string;\n id?: string;\n created?: number;\n}",
"requestKeys": [
"messages",
"n",
"max_tokens",
"top_p",
"reasoning_effort",
"temperature",
"stop",
"top_k"
]
}
}
}
207 changes: 207 additions & 0 deletions apps/dev-playground/.appkit/types-cache.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
{
"version": "3",
"queries": {
"app_activity_heatmap": {
"hash": "744281be86b14c2b14ed76b94060e4af",
"type": "{\n name: \"app_activity_heatmap\";\n parameters: {\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n app_name: string;\n /** @sqlType STRING */\n day_of_week: string;\n /** @sqlType DECIMAL(35,2) */\n spend: number;\n }>;\n }",
"retry": false
},
"apps_list": {
"hash": "4b7393b43f8e7beaa52174ed4d918c04",
"type": "{\n name: \"apps_list\";\n parameters: Record<string, never>;\n result: Array<{\n /** @sqlType STRING */\n id: string;\n /** @sqlType STRING */\n name: string;\n /** @sqlType STRING */\n creator: string;\n /** @sqlType STRING */\n tags: string;\n /** @sqlType DECIMAL(38,6) */\n totalSpend: number;\n /** @sqlType DATE */\n createdAt: string;\n }>;\n }",
"retry": false
},
"cost_recommendations": {
"hash": "730c7d8b5e2726981088b5975157b0da",
"type": "{\n name: \"cost_recommendations\";\n parameters: Record<string, never>;\n result: Array<{\n /** @sqlType INT */\n dummy: number;\n }>;\n }",
"retry": false
},
"dashboard_fare_distribution": {
"hash": "d280e96e06e1b5189d8fe734901e6e72",
"type": "{\n name: \"dashboard_fare_distribution\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n fare_bucket: string;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n avg_distance: number;\n }>;\n }",
"retry": false
},
"dashboard_hourly_heatmap": {
"hash": "827ca45db36ccf6f9c86c653c6175d69",
"type": "{\n name: \"dashboard_hourly_heatmap\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType INT */\n day_of_week: number;\n /** @sqlType INT */\n hour_of_day: number;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n }>;\n }",
"retry": false
},
"dashboard_kpi_sparklines": {
"hash": "1b3c14b03e1de455ff78f2d3feaec047",
"type": "{\n name: \"dashboard_kpi_sparklines\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType DATE */\n trip_date: string;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n total_revenue: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n /** @sqlType DOUBLE */\n avg_distance: number;\n }>;\n }",
"retry": false
},
"dashboard_kpis": {
"hash": "24ef19b8b1218edb8f247f6d79c915ff",
"type": "{\n name: \"dashboard_kpis\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType BIGINT */\n total_trips: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n /** @sqlType DOUBLE */\n avg_distance: number;\n /** @sqlType DOUBLE */\n max_fare: number;\n /** @sqlType DOUBLE */\n min_fare: number;\n }>;\n }",
"retry": false
},
"dashboard_top_zone": {
"hash": "fe3ed4ef76e4e7eff329c88072a44fe9",
"type": "{\n name: \"dashboard_top_zone\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType INT */\n pickup_zip: number;\n /** @sqlType BIGINT */\n trip_count: number;\n }>;\n }",
"retry": false
},
"dashboard_top_zones": {
"hash": "363c554c5a060ee2236f4e9fa1cf3c61",
"type": "{\n name: \"dashboard_top_zones\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n pickup_zip: string;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n total_revenue: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n }>;\n }",
"retry": false
},
"dashboard_trips_over_time": {
"hash": "eb8bed65ed02787ed050a016472b72f6",
"type": "{\n name: \"dashboard_trips_over_time\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType DATE */\n trip_date: string;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n /** @sqlType DOUBLE */\n total_revenue: number;\n }>;\n }",
"retry": false
},
"example": {
"hash": "aeb02ed3e8a6c77279099406f8709543",
"type": "{\n name: \"example\";\n parameters: Record<string, never>;\n result: Array<{\n /** @sqlType BOOLEAN */\n \"(1 = 1)\": boolean;\n }>;\n }",
"retry": false
},
"spend_data": {
"hash": "caa0430652fe15eff658e48e6dac2446",
"type": "{\n name: \"spend_data\";\n parameters: {\n /** STRING - use sql.string() */\n groupBy: SQLStringMarker;\n /** STRING - use sql.string() */\n aggregationLevel: SQLStringMarker;\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n /** STRING - use sql.string() */\n appId: SQLStringMarker;\n /** STRING - use sql.string() */\n creator: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n group_key: string;\n /** @sqlType TIMESTAMP */\n aggregation_period: string;\n /** @sqlType DECIMAL(38,6) */\n cost_usd: number;\n }>;\n }",
"retry": false
},
"spend_summary": {
"hash": "bbe188624c3f5904c3a7593cb32982d5",
"type": "{\n name: \"spend_summary\";\n parameters: {\n /** STRING - use sql.string() */\n aggregationLevel: SQLStringMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n };\n result: Array<{\n /** @sqlType DECIMAL(33,0) */\n total: number;\n /** @sqlType DECIMAL(33,0) */\n average: number;\n /** @sqlType DECIMAL(33,0) */\n forecasted: number;\n }>;\n }",
"retry": false
},
"sql_helpers_test": {
"hash": "1322df4ba9c107e8d23e2a04bae860c5",
"type": "{\n name: \"sql_helpers_test\";\n parameters: {\n /** STRING - use sql.string() */\n stringParam: SQLStringMarker;\n /** NUMERIC - use sql.numeric() */\n numberParam: SQLNumberMarker;\n /** BOOLEAN - use sql.boolean() */\n booleanParam: SQLBooleanMarker;\n /** DATE - use sql.date() */\n dateParam: SQLDateMarker;\n /** TIMESTAMP - use sql.timestamp() */\n timestampParam: SQLTimestampMarker;\n /** STRING - use sql.string() */\n binaryParam: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n string_value: string;\n /** @sqlType INT */\n number_value: number;\n /** @sqlType BOOLEAN */\n boolean_value: boolean;\n /** @sqlType STRING */\n date_value: string;\n /** @sqlType STRING */\n timestamp_value: string;\n /** @sqlType BINARY */\n binary_value: string;\n /** @sqlType STRING */\n binary_hex: string;\n /** @sqlType INT */\n binary_length: number;\n }>;\n }",
"retry": false
},
"top_contributors": {
"hash": "2d58759cca2fe31dae06475a23080120",
"type": "{\n name: \"top_contributors\";\n parameters: {\n /** STRING - use sql.string() */\n aggregationLevel: SQLStringMarker;\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n app_name: string;\n /** @sqlType DECIMAL(38,6) */\n total_cost_usd: number;\n }>;\n }",
"retry": false
},
"untagged_apps": {
"hash": "5946262b49710b8ab458d1bf950ff8c9",
"type": "{\n name: \"untagged_apps\";\n parameters: {\n /** STRING - use sql.string() */\n aggregationLevel: SQLStringMarker;\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n app_name: string;\n /** @sqlType STRING */\n creator: string;\n /** @sqlType DECIMAL(38,6) */\n total_cost_usd: number;\n /** @sqlType DECIMAL(38,10) */\n avg_period_cost_usd: number;\n }>;\n }",
"retry": false
}
},
"metrics": {
"customers": {
"hash": "26269cc37c4ea648a8a9cafb66a402dd",
"schema": {
"key": "customers",
"source": "appkit_demo.public.customer_metrics",
"lane": "obo",
"measures": [
{
"name": "active_accounts",
"type": "bigint",
"isMeasure": true,
"displayName": "Active Accounts",
"format": "#,##0"
},
{
"name": "churn_rate",
"type": "decimal",
"isMeasure": true,
"displayName": "Churn Rate"
},
{
"name": "avg_ltv",
"type": "double",
"isMeasure": true,
"displayName": "Average LTV",
"format": "$#,##0.00"
}
],
"dimensions": [
{
"name": "segment",
"type": "string",
"isMeasure": false,
"displayName": "Customer Segment"
},
{
"name": "region",
"type": "string",
"isMeasure": false,
"displayName": "Region"
},
{
"name": "csm_email",
"type": "string",
"isMeasure": false,
"displayName": "CSM Email"
}
]
},
"retry": false
},
"revenue": {
"hash": "8d0592a864778470213ca22517f21fae",
"schema": {
"key": "revenue",
"source": "appkit_demo.public.revenue_metrics",
"lane": "sp",
"measures": [
{
"name": "mrr",
"type": "double",
"isMeasure": true,
"displayName": "Monthly Recurring Revenue",
"format": "$#,##0.00"
},
{
"name": "arr",
"type": "double",
"isMeasure": true,
"description": "Annualized contract value across all active subscriptions",
"displayName": "Annual Recurring Revenue",
"format": "$#,##0.00"
},
{
"name": "new_arr",
"type": "double",
"isMeasure": true,
"displayName": "New ARR",
"format": "$#,##0.00"
},
{
"name": "churned_arr",
"type": "double",
"isMeasure": true,
"displayName": "Churned ARR",
"format": "$#,##0.00"
}
],
"dimensions": [
{
"name": "region",
"type": "string",
"isMeasure": false,
"displayName": "Region"
},
{
"name": "segment",
"type": "string",
"isMeasure": false,
"displayName": "Customer Segment"
},
{
"name": "created_at",
"type": "timestamp_ltz",
"isMeasure": false,
"displayName": "Subscription Start",
"timeGrains": [
"day",
"hour",
"minute",
"month",
"quarter",
"week",
"year"
]
}
]
},
"retry": false
}
}
}
1 change: 1 addition & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"!**/*.d.ts",
"!**/build",
"!**/coverage",
"!**/.appkit",
"!packages/appkit-ui/src/react/ui",
"!**/routeTree.gen.ts",
"!docs/.docusaurus",
Expand Down
6 changes: 6 additions & 0 deletions docs/docs/app-management.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ databricks apps deploy --help
```
:::

:::info Type cache and warehouse-less deploys

The build step runs type generation in blocking mode. If your deploy pipeline cannot reach a SQL warehouse, the build still succeeds **only if the committed `.appkit/` type cache covers every query** — otherwise it fails rather than shipping degraded types. Commit `.appkit/` while the warehouse is still reachable, before removing warehouse access from CI/CD. See [Type generation → Deploying without warehouse access](./development/type-generation.md#deploying-without-warehouse-access).

:::

### Examples

- Deploy to a specific target:
Expand Down
1 change: 1 addition & 0 deletions docs/docs/development/project-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ my-app/
├── config/
│ └── queries/
│ └── my_query.sql
├── .appkit/ # committed type cache (do NOT gitignore)
├── app.yaml
├── package.json
└── tsconfig.json
Expand Down
22 changes: 21 additions & 1 deletion docs/docs/development/type-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,27 @@ Pass `--wait` for CI and production builds, where accurate types must be present
npx @databricks/appkit generate-types --wait
```

In blocking mode the generator starts a stopped warehouse, waits (bounded) for it to reach `RUNNING`, and then describes your queries. It fails only when the configured warehouse no longer exists (deleted/deleting), so a transient outage or a cold warehouse degrades gracefully rather than breaking the build. The app template wires this up for you: `postinstall` and `predev` run the non-blocking default, while `prebuild` runs `--wait`.
In blocking mode the generator skips the warehouse entirely for any query whose types are already in the [type cache](#type-cache) (a cache **hit** makes zero warehouse round-trips). Only a cache **miss** contacts the warehouse: it starts a stopped warehouse, waits (bounded) for it to reach `RUNNING`, and then describes the query. A miss that cannot be resolved to a real type — a deleted warehouse, or (see below) a CI environment with no warehouse access — **fails the build** rather than shipping `result: unknown`. A transient connectivity blip still degrades gracefully. The app template wires this up for you: `postinstall` and `predev` run the non-blocking default, while `prebuild` runs `--wait`.

## Type cache

Type generation is backed by a per-app cache so that unchanged queries never pay for a repeat `DESCRIBE`. The cache lives in a committed `.appkit/` directory at your app root:

- `.appkit/types-cache.json` — query and metric-view DESCRIBE results
- `.appkit/serving-types-cache.json` — model-serving endpoint schemas

**`.appkit/` is a committed build artifact — check it into git; do not add it to `.gitignore`.** Committing it is what lets a blocking (`--wait`) build succeed with **no warehouse access**: on a fresh `git checkout` + `pnpm install`, every query is already a cache hit, so the build makes zero warehouse round-trips. (Ephemeral coordination state — a background-worker lock and the UI-variant choices file — stays under `node_modules/.databricks/appkit/` and is *not* committed.)

The cache serializes deterministically (top-level keys sorted), so a no-op regeneration produces a no-op diff and unrelated query changes don't collide in merges.

### Deploying without warehouse access

If your CI/CD environment cannot reach a SQL warehouse (for example, an authorization boundary removes warehouse access from the deploy pipeline), a blocking build still works **as long as the committed `.appkit/` cache covers every query** — all hits, no warehouse needed.

This imposes a rollout ordering: **produce and commit `.appkit/` while the warehouse is still reachable, then remove warehouse access.** A build that runs after warehouse access is gone with an uninitialized or incomplete cache fails loudly (never silently degrades to `unknown`), and the error message tells you which case you hit:

- **`No committed type cache found`** (no `.appkit/` yet) — run `generate-types --wait` against a warehouse and commit `.appkit/`.
- **`The committed .appkit/ cache is missing or stale`** (a query was added or changed since the cache was built, or its entry is missing) — regenerate against a warehouse and commit the updated `.appkit/`.

## Metric-view types

Expand Down
Loading
Loading