diff --git a/apps/dev-playground/client/.gitignore b/apps/dev-playground/client/.gitignore index 267b28f3..19067f12 100644 --- a/apps/dev-playground/client/.gitignore +++ b/apps/dev-playground/client/.gitignore @@ -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/* diff --git a/apps/dev-playground/client/src/appKitServingTypes.d.ts b/apps/dev-playground/client/src/appKitServingTypes.d.ts deleted file mode 100644 index 28f610b4..00000000 --- a/apps/dev-playground/client/src/appKitServingTypes.d.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Auto-generated by AppKit - DO NOT EDIT -// Generated from serving endpoint OpenAPI schemas -import "@databricks/appkit"; -import "@databricks/appkit-ui/react"; - -declare module "@databricks/appkit" { - interface ServingEndpointRegistry { - default: { - request: { - messages?: { - role?: "user" | "assistant"; - content?: string; - }[]; - /** @openapi integer, nullable */ - n?: number | null; - max_tokens?: number; - /** @openapi double, nullable */ - top_p?: number | null; - reasoning_effort?: "low" | "medium" | "high" | null; - /** @openapi double, nullable */ - temperature?: number | null; - stop?: string | string[] | null; - }; - response: { - model?: string; - choices?: { - index?: number; - message?: { - role?: "user" | "assistant"; - content?: string; - }; - finish_reason?: string; - }[]; - usage?: { - prompt_tokens?: number; - completion_tokens?: number; - total_tokens?: number; - } | null; - object?: string; - id?: string; - created?: number; - }; - chunk: { - model?: string; - choices?: { - index?: number; - delta?: { - role?: "user" | "assistant"; - content?: string; - }; - finish_reason?: string | null; - }[]; - object?: string; - id?: string; - created?: number; - }; - }; - } -} - -declare module "@databricks/appkit-ui/react" { - interface ServingEndpointRegistry { - default: { - request: { - messages?: { - role?: "user" | "assistant"; - content?: string; - }[]; - /** @openapi integer, nullable */ - n?: number | null; - max_tokens?: number; - /** @openapi double, nullable */ - top_p?: number | null; - reasoning_effort?: "low" | "medium" | "high" | null; - /** @openapi double, nullable */ - temperature?: number | null; - stop?: string | string[] | null; - }; - response: { - model?: string; - choices?: { - index?: number; - message?: { - role?: "user" | "assistant"; - content?: string; - }; - finish_reason?: string; - }[]; - usage?: { - prompt_tokens?: number; - completion_tokens?: number; - total_tokens?: number; - } | null; - object?: string; - id?: string; - created?: number; - }; - chunk: { - model?: string; - choices?: { - index?: number; - delta?: { - role?: "user" | "assistant"; - content?: string; - }; - finish_reason?: string | null; - }[]; - object?: string; - id?: string; - created?: number; - }; - }; - } -} diff --git a/apps/dev-playground/client/src/appKitTypes.d.ts b/apps/dev-playground/client/src/appkit-types/analytics.d.ts similarity index 100% rename from apps/dev-playground/client/src/appKitTypes.d.ts rename to apps/dev-playground/client/src/appkit-types/analytics.d.ts diff --git a/docs/docs/development/type-generation.md b/docs/docs/development/type-generation.md index 4e2e1fdc..fd71808a 100644 --- a/docs/docs/development/type-generation.md +++ b/docs/docs/development/type-generation.md @@ -8,7 +8,9 @@ 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` @@ -16,7 +18,7 @@ The recommended approach is to use the Vite plugin, which watches your SQL files ### 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 @@ -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"], }), ], @@ -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) diff --git a/docs/docs/plugins/analytics.md b/docs/docs/plugins/analytics.md index 97bc0a8e..187bf434 100644 --- a/docs/docs/plugins/analytics.md +++ b/docs/docs/plugins/analytics.md @@ -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: { diff --git a/packages/appkit/src/type-generator/index.ts b/packages/appkit/src/type-generator/index.ts index 8361e2cf..a1e04025 100644 --- a/packages/appkit/src/type-generator/index.ts +++ b/packages/appkit/src/type-generator/index.ts @@ -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"; @@ -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!"); @@ -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"; diff --git a/packages/appkit/src/type-generator/serving/generator.ts b/packages/appkit/src/type-generator/serving/generator.ts index bcf4fd50..6adf4a28 100644 --- a/packages/appkit/src/type-generator/serving/generator.ts +++ b/packages/appkit/src/type-generator/serving/generator.ts @@ -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"; @@ -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) { diff --git a/packages/appkit/src/type-generator/serving/tests/generator.test.ts b/packages/appkit/src/type-generator/serving/tests/generator.test.ts index 8761519b..edd32590 100644 --- a/packages/appkit/src/type-generator/serving/tests/generator.test.ts +++ b/packages/appkit/src/type-generator/serving/tests/generator.test.ts @@ -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"; }); diff --git a/packages/appkit/src/type-generator/serving/tests/vite-plugin.test.ts b/packages/appkit/src/type-generator/serving/tests/vite-plugin.test.ts index 074d3d44..889bee5b 100644 --- a/packages/appkit/src/type-generator/serving/tests/vite-plugin.test.ts +++ b/packages/appkit/src/type-generator/serving/tests/vite-plugin.test.ts @@ -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", ), }), ); diff --git a/packages/appkit/src/type-generator/serving/vite-plugin.ts b/packages/appkit/src/type-generator/serving/vite-plugin.ts index f94306c4..3e3e2d8f 100644 --- a/packages/appkit/src/type-generator/serving/vite-plugin.ts +++ b/packages/appkit/src/type-generator/serving/vite-plugin.ts @@ -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, @@ -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}`, ); }, diff --git a/packages/appkit/src/type-generator/vite-plugin.ts b/packages/appkit/src/type-generator/vite-plugin.ts index 741308f2..0240f578 100644 --- a/packages/appkit/src/type-generator/vite-plugin.ts +++ b/packages/appkit/src/type-generator/vite-plugin.ts @@ -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"); @@ -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) { diff --git a/packages/shared/src/cli/commands/generate-types.ts b/packages/shared/src/cli/commands/generate-types.ts index 3be45091..8bfdc181 100644 --- a/packages/shared/src/cli/commands/generate-types.ts +++ b/packages/shared/src/cli/commands/generate-types.ts @@ -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)) { @@ -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) { @@ -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")