Skip to content
Open
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
2 changes: 1 addition & 1 deletion apps/dev-playground/client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dist-ssr
*.local

# Auto-generated types (endpoint-specific, varies per developer)
src/appKitServingTypes.d.ts
src/appkit-types/serving.d.ts

# Editor directories and files
.vscode/*
Expand Down
114 changes: 0 additions & 114 deletions apps/dev-playground/client/src/appKitServingTypes.d.ts

This file was deleted.

12 changes: 7 additions & 5 deletions docs/docs/development/type-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ AppKit can automatically generate TypeScript types for your SQL queries, providi

## Goal

Generate `client/src/appKitTypes.d.ts` so query keys, parameters, and result rows are type-safe.
Generate type-safe TypeScript declarations for query keys, parameters, and result rows.

All generated files live in `client/src/appkit-types/`, one per plugin (e.g. `analytics.d.ts`). They use [`declare module`](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) to augment existing interfaces, so the types apply globally — you never need to import them. TypeScript auto-discovers them through `"include": ["src"]` in your tsconfig.

## Vite plugin: `appKitTypesPlugin`

The recommended approach is to use the Vite plugin, which watches your SQL files and regenerates types automatically during development.

### Configuration

- `outFile?: string` - Output file path (default: `src/appKitTypes.d.ts`)
- `outFile?: string` - Output file path (default: `src/appkit-types/analytics.d.ts`)
- `watchFolders?: string[]` - Folders to watch for SQL files (default: `["../config/queries"]`)

### Example
Expand All @@ -31,7 +33,7 @@ export default defineConfig({
plugins: [
react(),
appKitTypesPlugin({
outFile: "src/appKitTypes.d.ts",
outFile: "src/appkit-types/analytics.d.ts",
watchFolders: ["../config/queries"],
}),
],
Expand All @@ -56,13 +58,13 @@ npx @databricks/appkit generate-types [rootDir] [outFile] [warehouseId]
- Generate types using warehouse ID from environment

```bash
npx @databricks/appkit generate-types . client/src/appKitTypes.d.ts
npx @databricks/appkit generate-types . client/src/appkit-types/analytics.d.ts
```

- Generate types using warehouse ID explicitly

```bash
npx @databricks/appkit generate-types . client/src/appKitTypes.d.ts abc123...
npx @databricks/appkit generate-types . client/src/appkit-types/analytics.d.ts abc123...
```

- Force regeneration (skip cache)
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/plugins/analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ function SpendTable() {
Augment the `QueryRegistry` interface to get full type inference on parameters and results:

```ts
// config/appKitTypes.d.ts
// client/src/appkit-types/analytics.d.ts
declare module "@databricks/appkit-ui/react" {
interface QueryRegistry {
spend_summary: {
Expand Down
9 changes: 9 additions & 0 deletions packages/appkit/src/type-generator/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from "node:fs/promises";
import path from "node:path";
import dotenv from "dotenv";
import { createLogger } from "../logging/logger";
import { generateQueriesFromDescribe } from "./query-registry";
Expand Down Expand Up @@ -83,6 +84,7 @@ export async function generateFromEntryPoint(options: {

const typeDeclarations = generateTypeDeclarations(queryRegistry);

await fs.mkdir(path.dirname(outFile), { recursive: true });
await fs.writeFile(outFile, typeDeclarations, "utf-8");

logger.debug("Type generation complete!");
Expand All @@ -92,3 +94,10 @@ export async function generateFromEntryPoint(options: {
// A local binding ensures the serving vite plugin's import keeps this in the dependency graph,
// mirroring how generateFromEntryPoint (also defined here) is preserved via the analytics vite plugin.
export const generateServingTypes = generateServingTypesImpl;

/** Directory name for generated AppKit type declaration files. */
export const TYPES_DIR = "appkit-types";
/** Default filename for analytics query type declarations. */
export const ANALYTICS_TYPES_FILE = "analytics.d.ts";
/** Default filename for serving endpoint type declarations. */
export const SERVING_TYPES_FILE = "serving.d.ts";
2 changes: 2 additions & 0 deletions packages/appkit/src/type-generator/serving/generator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from "node:fs/promises";
import path from "node:path";
import { WorkspaceClient } from "@databricks/sdk-experimental";
import pc from "picocolors";
import { createLogger } from "../../logging/logger";
Expand Down Expand Up @@ -73,6 +74,7 @@ export async function generateServingTypes(
printLogTable(logEntries, startTime);

const output = generateTypeDeclarations(registryEntries);
await fs.mkdir(path.dirname(outFile), { recursive: true });
await fs.writeFile(outFile, output, "utf-8");

if (registryEntries.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe("generateServingTypes", () => {
const outFile = "/tmp/test-serving-types.d.ts";

beforeEach(() => {
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
vi.mocked(fs.writeFile).mockResolvedValue();
process.env.TEST_SERVING_ENDPOINT = "my-endpoint";
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe("appKitServingTypesPlugin", () => {
expect(mockGenerateServingTypes).toHaveBeenCalledWith(
expect.objectContaining({
outFile: expect.stringContaining(
"/app/client/src/appKitServingTypes.d.ts",
"/app/client/src/appkit-types/serving.d.ts",
),
}),
);
Expand Down
4 changes: 2 additions & 2 deletions packages/appkit/src/type-generator/serving/vite-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from "node:path";
import type { Plugin } from "vite";
import { createLogger } from "../../logging/logger";
import type { EndpointConfig } from "../../plugins/serving/types";
import { generateServingTypes } from "../index";
import { generateServingTypes, SERVING_TYPES_FILE, TYPES_DIR } from "../index";
import {
extractServingEndpoints,
findServerFile,
Expand Down Expand Up @@ -96,7 +96,7 @@ export function appKitServingTypesPlugin(
projectRoot = path.resolve(config.root, "..");
outFile = path.resolve(
config.root,
options?.outFile ?? "src/appKitServingTypes.d.ts",
options?.outFile ?? `src/${TYPES_DIR}/${SERVING_TYPES_FILE}`,
);
},

Expand Down
15 changes: 11 additions & 4 deletions packages/appkit/src/type-generator/vite-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { existsSync } from "node:fs";
import path from "node:path";
import type { Plugin } from "vite";
import { createLogger } from "../logging/logger";
import { generateFromEntryPoint } from "./index";
import {
ANALYTICS_TYPES_FILE,
generateFromEntryPoint,
TYPES_DIR,
} from "./index";

const logger = createLogger("type-generator:vite-plugin");

Expand Down Expand Up @@ -71,14 +75,17 @@ export function appKitTypesPlugin(options?: AppKitTypesPluginOptions): Plugin {

configResolved(config) {
root = config.root;
outFile = path.resolve(root, options?.outFile ?? "src/appKitTypes.d.ts");
outFile = path.resolve(
root,
options?.outFile ?? `src/${TYPES_DIR}/${ANALYTICS_TYPES_FILE}`,
);
watchFolders = options?.watchFolders ?? [
path.join(process.cwd(), "config", "queries"),
];
},

buildStart() {
generate();
async buildStart() {
await generate();
},

configureServer(server) {
Expand Down
7 changes: 4 additions & 3 deletions packages/shared/src/cli/commands/generate-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ async function runGenerateTypes(

if (resolvedWarehouseId) {
const resolvedOutFile =
outFile || path.join(process.cwd(), "client/src/appKitTypes.d.ts");
outFile ||
path.join(process.cwd(), "client/src/appkit-types/analytics.d.ts");

const queryFolder = path.join(resolvedRootDir, "config/queries");
if (fs.existsSync(queryFolder)) {
Expand All @@ -38,7 +39,7 @@ async function runGenerateTypes(

// Generate serving endpoint types (no warehouse required)
await typeGen.generateServingTypes({
outFile: path.join(process.cwd(), "client/src/appKitServingTypes.d.ts"),
outFile: path.join(process.cwd(), "client/src/appkit-types/serving.d.ts"),
noCache,
});
} catch (error) {
Expand All @@ -62,7 +63,7 @@ export const generateTypesCommand = new Command("generate-types")
.argument(
"[outFile]",
"Output file path",
path.join(process.cwd(), "client/src/appKitTypes.d.ts"),
path.join(process.cwd(), "client/src/appkit-types/analytics.d.ts"),
)
.argument("[warehouseId]", "Databricks warehouse ID")
.option("--no-cache", "Disable caching for type generation")
Expand Down
Loading