@databricks/appkit 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CLAUDE.md +11 -0
  2. package/NOTICE.md +1 -0
  3. package/dist/appkit/package.js +1 -1
  4. package/dist/cache/index.js.map +1 -1
  5. package/dist/cli/commands/docs.js +7 -1
  6. package/dist/cli/commands/docs.js.map +1 -1
  7. package/dist/cli/commands/generate-types.js +27 -15
  8. package/dist/cli/commands/generate-types.js.map +1 -1
  9. package/dist/cli/commands/lint.js +3 -1
  10. package/dist/cli/commands/lint.js.map +1 -1
  11. package/dist/cli/commands/plugin/add-resource/add-resource.js +73 -8
  12. package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
  13. package/dist/cli/commands/plugin/create/create.js +164 -20
  14. package/dist/cli/commands/plugin/create/create.js.map +1 -1
  15. package/dist/cli/commands/plugin/create/resource-defaults.js +5 -1
  16. package/dist/cli/commands/plugin/create/resource-defaults.js.map +1 -1
  17. package/dist/cli/commands/plugin/index.js +7 -1
  18. package/dist/cli/commands/plugin/index.js.map +1 -1
  19. package/dist/cli/commands/plugin/list/list.js +7 -1
  20. package/dist/cli/commands/plugin/list/list.js.map +1 -1
  21. package/dist/cli/commands/plugin/sync/sync.js +27 -14
  22. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  23. package/dist/cli/commands/plugin/validate/validate.js +39 -9
  24. package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
  25. package/dist/cli/commands/setup.js +6 -5
  26. package/dist/cli/commands/setup.js.map +1 -1
  27. package/dist/connectors/index.js +1 -0
  28. package/dist/connectors/lakebase/index.js.map +1 -1
  29. package/dist/connectors/lakebase-v1/client.js.map +1 -1
  30. package/dist/connectors/serving/client.js +47 -0
  31. package/dist/connectors/serving/client.js.map +1 -0
  32. package/dist/connectors/vector-search/client.js +9 -0
  33. package/dist/connectors/vector-search/client.js.map +1 -0
  34. package/dist/connectors/vector-search/index.js +3 -0
  35. package/dist/index.d.ts +6 -1
  36. package/dist/index.js +4 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/plugin/dev-reader.js.map +1 -1
  39. package/dist/plugin/execution-result.d.ts +26 -0
  40. package/dist/plugin/execution-result.d.ts.map +1 -0
  41. package/dist/plugin/index.d.ts +1 -0
  42. package/dist/plugin/interceptors/retry.js +1 -1
  43. package/dist/plugin/interceptors/retry.js.map +1 -1
  44. package/dist/plugin/plugin.d.ts +7 -4
  45. package/dist/plugin/plugin.d.ts.map +1 -1
  46. package/dist/plugin/plugin.js +36 -5
  47. package/dist/plugin/plugin.js.map +1 -1
  48. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  49. package/dist/plugins/analytics/analytics.js +2 -3
  50. package/dist/plugins/analytics/analytics.js.map +1 -1
  51. package/dist/plugins/files/plugin.d.ts +1 -0
  52. package/dist/plugins/files/plugin.d.ts.map +1 -1
  53. package/dist/plugins/files/plugin.js +36 -59
  54. package/dist/plugins/files/plugin.js.map +1 -1
  55. package/dist/plugins/index.d.ts +4 -1
  56. package/dist/plugins/index.js +2 -0
  57. package/dist/plugins/server/index.d.ts +1 -1
  58. package/dist/plugins/server/vite-dev-server.js +6 -1
  59. package/dist/plugins/server/vite-dev-server.js.map +1 -1
  60. package/dist/plugins/serving/defaults.js +10 -0
  61. package/dist/plugins/serving/defaults.js.map +1 -0
  62. package/dist/plugins/serving/index.d.ts +2 -0
  63. package/dist/plugins/serving/index.js +3 -0
  64. package/dist/plugins/serving/manifest.js +53 -0
  65. package/dist/plugins/serving/manifest.js.map +1 -0
  66. package/dist/plugins/serving/schema-filter.js +52 -0
  67. package/dist/plugins/serving/schema-filter.js.map +1 -0
  68. package/dist/plugins/serving/serving.d.ts +38 -0
  69. package/dist/plugins/serving/serving.d.ts.map +1 -0
  70. package/dist/plugins/serving/serving.js +227 -0
  71. package/dist/plugins/serving/serving.js.map +1 -0
  72. package/dist/plugins/serving/types.d.ts +59 -0
  73. package/dist/plugins/serving/types.d.ts.map +1 -0
  74. package/dist/shared/src/execute.d.ts +1 -1
  75. package/dist/stream/stream-manager.js +1 -0
  76. package/dist/stream/stream-manager.js.map +1 -1
  77. package/dist/stream/types.js +2 -1
  78. package/dist/stream/types.js.map +1 -1
  79. package/dist/type-generator/cache.js +1 -1
  80. package/dist/type-generator/cache.js.map +1 -1
  81. package/dist/type-generator/index.js +15 -1
  82. package/dist/type-generator/index.js.map +1 -1
  83. package/dist/type-generator/migration.js +155 -0
  84. package/dist/type-generator/migration.js.map +1 -0
  85. package/dist/type-generator/query-registry.js +77 -4
  86. package/dist/type-generator/query-registry.js.map +1 -1
  87. package/dist/type-generator/serving/cache.js +38 -0
  88. package/dist/type-generator/serving/cache.js.map +1 -0
  89. package/dist/type-generator/serving/converter.js +108 -0
  90. package/dist/type-generator/serving/converter.js.map +1 -0
  91. package/dist/type-generator/serving/fetcher.js +54 -0
  92. package/dist/type-generator/serving/fetcher.js.map +1 -0
  93. package/dist/type-generator/serving/generator.js +206 -0
  94. package/dist/type-generator/serving/generator.js.map +1 -0
  95. package/dist/type-generator/serving/server-file-extractor.d.ts +22 -0
  96. package/dist/type-generator/serving/server-file-extractor.d.ts.map +1 -0
  97. package/dist/type-generator/serving/server-file-extractor.js +131 -0
  98. package/dist/type-generator/serving/server-file-extractor.js.map +1 -0
  99. package/dist/type-generator/serving/vite-plugin.d.ts +24 -0
  100. package/dist/type-generator/serving/vite-plugin.d.ts.map +1 -0
  101. package/dist/type-generator/serving/vite-plugin.js +60 -0
  102. package/dist/type-generator/serving/vite-plugin.js.map +1 -0
  103. package/dist/type-generator/vite-plugin.d.ts.map +1 -1
  104. package/dist/type-generator/vite-plugin.js +3 -4
  105. package/dist/type-generator/vite-plugin.js.map +1 -1
  106. package/docs/api/appkit/Class.Plugin.md +8 -3
  107. package/docs/api/appkit/Function.appKitServingTypesPlugin.md +24 -0
  108. package/docs/api/appkit/Function.extractServingEndpoints.md +22 -0
  109. package/docs/api/appkit/Function.findServerFile.md +20 -0
  110. package/docs/api/appkit/Interface.EndpointConfig.md +23 -0
  111. package/docs/api/appkit/Interface.ServingEndpointEntry.md +30 -0
  112. package/docs/api/appkit/Interface.ServingEndpointRegistry.md +3 -0
  113. package/docs/api/appkit/TypeAlias.ExecutionResult.md +36 -0
  114. package/docs/api/appkit/TypeAlias.ServingFactory.md +19 -0
  115. package/docs/api/appkit.md +39 -31
  116. package/docs/development/type-generation.md +6 -5
  117. package/docs/faq.md +66 -0
  118. package/docs/plugins/analytics.md +1 -1
  119. package/docs/plugins/custom-plugins.md +4 -0
  120. package/docs/plugins/plugin-management.md +22 -6
  121. package/docs/plugins/serving.md +223 -0
  122. package/docs/plugins/vector-search.md +247 -0
  123. package/llms.txt +11 -0
  124. package/package.json +2 -2
  125. package/sbom.cdx.json +1 -1
@@ -0,0 +1,54 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+ import { ApiError } from "@databricks/sdk-experimental";
3
+
4
+ //#region src/type-generator/serving/fetcher.ts
5
+ const logger = createLogger("type-generator:serving:fetcher");
6
+ /**
7
+ * Fetches the OpenAPI schema for a serving endpoint using the SDK.
8
+ * Returns null if the endpoint is not found or access is denied.
9
+ */
10
+ async function fetchOpenApiSchema(client, endpointName, servedModel) {
11
+ try {
12
+ const response = await client.servingEndpoints.getOpenApi({ name: endpointName });
13
+ if (!response.contents) {
14
+ logger.warn("Empty OpenAPI response for '%s', skipping type generation", endpointName);
15
+ return null;
16
+ }
17
+ const text = await new Response(response.contents).text();
18
+ const rawSpec = JSON.parse(text);
19
+ if (typeof rawSpec !== "object" || rawSpec === null || !("paths" in rawSpec) || typeof rawSpec.paths !== "object") {
20
+ logger.warn("Invalid OpenAPI schema structure for '%s', skipping", endpointName);
21
+ return null;
22
+ }
23
+ const spec = rawSpec;
24
+ const pathKeys = Object.keys(spec.paths ?? {});
25
+ if (pathKeys.length === 0) {
26
+ logger.warn("No paths in OpenAPI schema for '%s'", endpointName);
27
+ return null;
28
+ }
29
+ let pathKey;
30
+ if (servedModel) {
31
+ const match = pathKeys.find((k) => k.includes(`/${servedModel}/`));
32
+ if (!match) {
33
+ logger.warn("Served model '%s' not found in schema for '%s', using first path", servedModel, endpointName);
34
+ pathKey = pathKeys[0];
35
+ } else pathKey = match;
36
+ } else pathKey = pathKeys[0];
37
+ return {
38
+ spec,
39
+ pathKey
40
+ };
41
+ } catch (err) {
42
+ if (err instanceof ApiError) {
43
+ const status = err.statusCode ?? 0;
44
+ if (status === 404) logger.warn("Endpoint '%s' not found, skipping type generation", endpointName);
45
+ else if (status === 403) logger.warn("Access denied to endpoint '%s' schema, skipping type generation", endpointName);
46
+ else logger.warn("Failed to fetch schema for '%s' (HTTP %d), skipping: %s", endpointName, status, err.message);
47
+ } else logger.warn("Error fetching schema for '%s': %s", endpointName, err.message);
48
+ return null;
49
+ }
50
+ }
51
+
52
+ //#endregion
53
+ export { fetchOpenApiSchema };
54
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.js","names":[],"sources":["../../../src/type-generator/serving/fetcher.ts"],"sourcesContent":["import { ApiError, type WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport { createLogger } from \"../../logging/logger\";\n\nconst logger = createLogger(\"type-generator:serving:fetcher\");\n\ninterface OpenApiSpec {\n openapi: string;\n info: { title: string; version: string };\n paths: Record<string, Record<string, OpenApiOperation>>;\n}\n\nexport interface OpenApiOperation {\n requestBody?: {\n content: {\n \"application/json\": {\n schema: OpenApiSchema;\n };\n };\n };\n responses?: Record<\n string,\n {\n content?: {\n \"application/json\": {\n schema: OpenApiSchema;\n };\n };\n }\n >;\n}\n\nexport interface OpenApiSchema {\n type?: string;\n properties?: Record<string, OpenApiSchema>;\n required?: string[];\n items?: OpenApiSchema;\n enum?: string[];\n nullable?: boolean;\n oneOf?: OpenApiSchema[];\n format?: string;\n}\n\n/**\n * Fetches the OpenAPI schema for a serving endpoint using the SDK.\n * Returns null if the endpoint is not found or access is denied.\n */\nexport async function fetchOpenApiSchema(\n client: WorkspaceClient,\n endpointName: string,\n servedModel?: string,\n): Promise<{ spec: OpenApiSpec; pathKey: string } | null> {\n try {\n const response = await client.servingEndpoints.getOpenApi({\n name: endpointName,\n });\n\n if (!response.contents) {\n logger.warn(\n \"Empty OpenAPI response for '%s', skipping type generation\",\n endpointName,\n );\n return null;\n }\n\n const text = await new Response(response.contents).text();\n const rawSpec: unknown = JSON.parse(text);\n\n if (\n typeof rawSpec !== \"object\" ||\n rawSpec === null ||\n !(\"paths\" in rawSpec) ||\n typeof (rawSpec as OpenApiSpec).paths !== \"object\"\n ) {\n logger.warn(\n \"Invalid OpenAPI schema structure for '%s', skipping\",\n endpointName,\n );\n return null;\n }\n const spec = rawSpec as OpenApiSpec;\n\n // Find the right path key\n const pathKeys = Object.keys(spec.paths ?? {});\n if (pathKeys.length === 0) {\n logger.warn(\"No paths in OpenAPI schema for '%s'\", endpointName);\n return null;\n }\n\n let pathKey: string;\n if (servedModel) {\n const match = pathKeys.find((k) => k.includes(`/${servedModel}/`));\n if (!match) {\n logger.warn(\n \"Served model '%s' not found in schema for '%s', using first path\",\n servedModel,\n endpointName,\n );\n pathKey = pathKeys[0];\n } else {\n pathKey = match;\n }\n } else {\n pathKey = pathKeys[0];\n }\n\n return { spec, pathKey };\n } catch (err) {\n if (err instanceof ApiError) {\n const status = err.statusCode ?? 0;\n if (status === 404) {\n logger.warn(\n \"Endpoint '%s' not found, skipping type generation\",\n endpointName,\n );\n } else if (status === 403) {\n logger.warn(\n \"Access denied to endpoint '%s' schema, skipping type generation\",\n endpointName,\n );\n } else {\n logger.warn(\n \"Failed to fetch schema for '%s' (HTTP %d), skipping: %s\",\n endpointName,\n status,\n err.message,\n );\n }\n } else {\n logger.warn(\n \"Error fetching schema for '%s': %s\",\n endpointName,\n (err as Error).message,\n );\n }\n return null;\n }\n}\n"],"mappings":";;;;AAGA,MAAM,SAAS,aAAa,iCAAiC;;;;;AA2C7D,eAAsB,mBACpB,QACA,cACA,aACwD;AACxD,KAAI;EACF,MAAM,WAAW,MAAM,OAAO,iBAAiB,WAAW,EACxD,MAAM,cACP,CAAC;AAEF,MAAI,CAAC,SAAS,UAAU;AACtB,UAAO,KACL,6DACA,aACD;AACD,UAAO;;EAGT,MAAM,OAAO,MAAM,IAAI,SAAS,SAAS,SAAS,CAAC,MAAM;EACzD,MAAM,UAAmB,KAAK,MAAM,KAAK;AAEzC,MACE,OAAO,YAAY,YACnB,YAAY,QACZ,EAAE,WAAW,YACb,OAAQ,QAAwB,UAAU,UAC1C;AACA,UAAO,KACL,uDACA,aACD;AACD,UAAO;;EAET,MAAM,OAAO;EAGb,MAAM,WAAW,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;AAC9C,MAAI,SAAS,WAAW,GAAG;AACzB,UAAO,KAAK,uCAAuC,aAAa;AAChE,UAAO;;EAGT,IAAI;AACJ,MAAI,aAAa;GACf,MAAM,QAAQ,SAAS,MAAM,MAAM,EAAE,SAAS,IAAI,YAAY,GAAG,CAAC;AAClE,OAAI,CAAC,OAAO;AACV,WAAO,KACL,oEACA,aACA,aACD;AACD,cAAU,SAAS;SAEnB,WAAU;QAGZ,WAAU,SAAS;AAGrB,SAAO;GAAE;GAAM;GAAS;UACjB,KAAK;AACZ,MAAI,eAAe,UAAU;GAC3B,MAAM,SAAS,IAAI,cAAc;AACjC,OAAI,WAAW,IACb,QAAO,KACL,qDACA,aACD;YACQ,WAAW,IACpB,QAAO,KACL,mEACA,aACD;OAED,QAAO,KACL,2DACA,cACA,QACA,IAAI,QACL;QAGH,QAAO,KACL,sCACA,cACC,IAAc,QAChB;AAEH,SAAO"}
@@ -0,0 +1,206 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+ import { migrateProjectConfig, removeOldGeneratedTypes, resolveProjectRoot } from "../migration.js";
3
+ import { CACHE_VERSION, hashSchema, loadServingCache, saveServingCache } from "./cache.js";
4
+ import { convertRequestSchema, convertResponseSchema, deriveChunkType, extractRequestKeys } from "./converter.js";
5
+ import { fetchOpenApiSchema } from "./fetcher.js";
6
+ import { extractServingEndpoints, findServerFile } from "./server-file-extractor.js";
7
+ import { WorkspaceClient } from "@databricks/sdk-experimental";
8
+ import fs from "node:fs/promises";
9
+ import path from "node:path";
10
+ import pc from "picocolors";
11
+
12
+ //#region src/type-generator/serving/generator.ts
13
+ const logger = createLogger("type-generator:serving");
14
+ const GENERIC_REQUEST = "Record<string, unknown>";
15
+ const GENERIC_RESPONSE = "unknown";
16
+ const GENERIC_CHUNK = "unknown";
17
+ /**
18
+ * Generates TypeScript type declarations for serving endpoints
19
+ * by fetching their OpenAPI schemas and converting to TypeScript.
20
+ *
21
+ * Endpoint discovery order (when `endpoints` is not provided):
22
+ * 1. AST extraction from server file (server/index.ts or server/server.ts)
23
+ * 2. DATABRICKS_SERVING_ENDPOINT_NAME env var (single default endpoint)
24
+ */
25
+ async function generateServingTypes(options) {
26
+ const { outFile, noCache } = options;
27
+ const projectRoot = resolveProjectRoot(outFile);
28
+ const endpoints = options.endpoints ?? resolveEndpointsFromServerFile() ?? resolveDefaultEndpoints();
29
+ if (Object.keys(endpoints).length === 0) {
30
+ logger.debug("No serving endpoints configured, skipping type generation");
31
+ return;
32
+ }
33
+ const startTime = performance.now();
34
+ const cache = noCache ? {
35
+ version: CACHE_VERSION,
36
+ endpoints: {}
37
+ } : await loadServingCache();
38
+ let client;
39
+ let updated = false;
40
+ const registryEntries = [];
41
+ const logEntries = [];
42
+ for (const [alias, config] of Object.entries(endpoints)) {
43
+ client ??= new WorkspaceClient({});
44
+ const result = await processEndpoint(alias, config, client, cache);
45
+ if (result.cacheUpdated) updated = true;
46
+ registryEntries.push(result.entry);
47
+ logEntries.push(result.log);
48
+ }
49
+ printLogTable(logEntries, startTime);
50
+ const output = generateTypeDeclarations(registryEntries);
51
+ await fs.mkdir(path.dirname(outFile), { recursive: true });
52
+ await fs.writeFile(outFile, output, "utf-8");
53
+ await removeOldGeneratedTypes(projectRoot, "appKitServingTypes.d.ts");
54
+ await migrateProjectConfig(projectRoot);
55
+ if (registryEntries.length === 0) logger.debug("Wrote empty serving types to %s (no endpoints resolved)", outFile);
56
+ else logger.debug("Wrote serving types to %s", outFile);
57
+ if (updated) await saveServingCache(cache);
58
+ }
59
+ function genericEntry(alias) {
60
+ return buildRegistryEntry(alias, GENERIC_REQUEST, GENERIC_RESPONSE, GENERIC_CHUNK);
61
+ }
62
+ async function processEndpoint(alias, config, client, cache) {
63
+ const endpointName = process.env[config.env];
64
+ if (!endpointName) return {
65
+ entry: genericEntry(alias),
66
+ log: {
67
+ alias,
68
+ status: "MISS",
69
+ error: `env ${config.env} not set`
70
+ },
71
+ cacheUpdated: false
72
+ };
73
+ const result = await fetchOpenApiSchema(client, endpointName, config.servedModel);
74
+ if (!result) return {
75
+ entry: genericEntry(alias),
76
+ log: {
77
+ alias,
78
+ status: "MISS",
79
+ error: "schema fetch failed"
80
+ },
81
+ cacheUpdated: false
82
+ };
83
+ const { spec, pathKey } = result;
84
+ const hash = hashSchema(JSON.stringify(spec));
85
+ const cached = cache.endpoints[alias];
86
+ if (cached && cached.hash === hash) return {
87
+ entry: buildRegistryEntry(alias, cached.requestType, cached.responseType, cached.chunkType),
88
+ log: {
89
+ alias,
90
+ status: "HIT"
91
+ },
92
+ cacheUpdated: false
93
+ };
94
+ const operation = spec.paths[pathKey]?.post;
95
+ if (!operation) return {
96
+ entry: genericEntry(alias),
97
+ log: {
98
+ alias,
99
+ status: "MISS",
100
+ error: "no POST operation"
101
+ },
102
+ cacheUpdated: false
103
+ };
104
+ try {
105
+ const requestType = convertRequestSchema(operation);
106
+ const responseType = convertResponseSchema(operation);
107
+ const chunkType = deriveChunkType(operation);
108
+ const requestKeys = extractRequestKeys(operation);
109
+ cache.endpoints[alias] = {
110
+ hash,
111
+ requestType,
112
+ responseType,
113
+ chunkType,
114
+ requestKeys
115
+ };
116
+ return {
117
+ entry: buildRegistryEntry(alias, requestType, responseType, chunkType),
118
+ log: {
119
+ alias,
120
+ status: "MISS"
121
+ },
122
+ cacheUpdated: true
123
+ };
124
+ } catch (convErr) {
125
+ logger.warn("Schema conversion failed for '%s': %s", alias, convErr.message);
126
+ return {
127
+ entry: genericEntry(alias),
128
+ log: {
129
+ alias,
130
+ status: "MISS",
131
+ error: "schema conversion failed"
132
+ },
133
+ cacheUpdated: false
134
+ };
135
+ }
136
+ }
137
+ function printLogTable(logEntries, startTime) {
138
+ if (logEntries.length === 0) return;
139
+ const maxNameLen = Math.max(...logEntries.map((e) => e.alias.length));
140
+ const separator = pc.dim("─".repeat(50));
141
+ console.log("");
142
+ console.log(` ${pc.bold("Typegen Serving")} ${pc.dim(`(${logEntries.length})`)}`);
143
+ console.log(` ${separator}`);
144
+ for (const entry of logEntries) {
145
+ const tag = entry.status === "HIT" ? `cache ${pc.bold(pc.green("HIT "))}` : `cache ${pc.bold(pc.yellow("MISS "))}`;
146
+ const rawName = entry.alias.padEnd(maxNameLen);
147
+ const reason = entry.error ? ` ${pc.dim(entry.error)}` : "";
148
+ console.log(` ${tag} ${rawName}${reason}`);
149
+ }
150
+ const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
151
+ const newCount = logEntries.filter((e) => e.status === "MISS").length;
152
+ const cacheCount = logEntries.filter((e) => e.status === "HIT").length;
153
+ console.log(` ${separator}`);
154
+ console.log(` ${newCount} new, ${cacheCount} from cache. ${pc.dim(`${elapsed}s`)}`);
155
+ console.log("");
156
+ }
157
+ function resolveEndpointsFromServerFile() {
158
+ try {
159
+ const serverFile = findServerFile(process.cwd());
160
+ if (!serverFile) return void 0;
161
+ return extractServingEndpoints(serverFile) ?? void 0;
162
+ } catch (error) {
163
+ logger.debug("Failed to extract endpoints from server file: %s", error.message);
164
+ return;
165
+ }
166
+ }
167
+ function resolveDefaultEndpoints() {
168
+ if (process.env.DATABRICKS_SERVING_ENDPOINT_NAME) return { default: { env: "DATABRICKS_SERVING_ENDPOINT_NAME" } };
169
+ return {};
170
+ }
171
+ function buildRegistryEntry(alias, requestType, responseType, chunkType) {
172
+ const indent = " ";
173
+ const chunkEntry = chunkType ? chunkType : "unknown";
174
+ return ` ${alias}: {
175
+ ${indent}request: ${indentType(requestType, indent)};
176
+ ${indent}response: ${indentType(responseType, indent)};
177
+ ${indent}chunk: ${indentType(chunkEntry, indent)};
178
+ };`;
179
+ }
180
+ function indentType(typeStr, baseIndent) {
181
+ if (!typeStr.includes("\n")) return typeStr;
182
+ return typeStr.split("\n").map((line, i) => i === 0 ? line : `${baseIndent}${line}`).join("\n");
183
+ }
184
+ function generateTypeDeclarations(entries) {
185
+ return `// Auto-generated by AppKit - DO NOT EDIT
186
+ // Generated from serving endpoint OpenAPI schemas
187
+ import "@databricks/appkit";
188
+ import "@databricks/appkit-ui/react";
189
+
190
+ declare module "@databricks/appkit" {
191
+ interface ServingEndpointRegistry {
192
+ ${entries.join("\n")}
193
+ }
194
+ }
195
+
196
+ declare module "@databricks/appkit-ui/react" {
197
+ interface ServingEndpointRegistry {
198
+ ${entries.join("\n")}
199
+ }
200
+ }
201
+ `;
202
+ }
203
+
204
+ //#endregion
205
+ export { generateServingTypes };
206
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","names":[],"sources":["../../../src/type-generator/serving/generator.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport pc from \"picocolors\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { EndpointConfig } from \"../../plugins/serving/types\";\nimport {\n migrateProjectConfig,\n removeOldGeneratedTypes,\n resolveProjectRoot,\n} from \"../migration\";\nimport {\n CACHE_VERSION,\n hashSchema,\n loadServingCache,\n type ServingCache,\n saveServingCache,\n} from \"./cache\";\nimport {\n convertRequestSchema,\n convertResponseSchema,\n deriveChunkType,\n extractRequestKeys,\n} from \"./converter\";\nimport { fetchOpenApiSchema } from \"./fetcher\";\nimport {\n extractServingEndpoints,\n findServerFile,\n} from \"./server-file-extractor\";\n\nconst logger = createLogger(\"type-generator:serving\");\n\nconst GENERIC_REQUEST = \"Record<string, unknown>\";\nconst GENERIC_RESPONSE = \"unknown\";\nconst GENERIC_CHUNK = \"unknown\";\n\ninterface GenerateServingTypesOptions {\n outFile: string;\n endpoints?: Record<string, EndpointConfig>;\n noCache?: boolean;\n}\n\n/**\n * Generates TypeScript type declarations for serving endpoints\n * by fetching their OpenAPI schemas and converting to TypeScript.\n *\n * Endpoint discovery order (when `endpoints` is not provided):\n * 1. AST extraction from server file (server/index.ts or server/server.ts)\n * 2. DATABRICKS_SERVING_ENDPOINT_NAME env var (single default endpoint)\n */\nexport async function generateServingTypes(\n options: GenerateServingTypesOptions,\n): Promise<void> {\n const { outFile, noCache } = options;\n const projectRoot = resolveProjectRoot(outFile);\n\n // Resolve endpoints: explicit > AST extraction from server file > env var fallback\n const endpoints =\n options.endpoints ??\n resolveEndpointsFromServerFile() ??\n resolveDefaultEndpoints();\n if (Object.keys(endpoints).length === 0) {\n logger.debug(\"No serving endpoints configured, skipping type generation\");\n return;\n }\n\n const startTime = performance.now();\n\n const cache = noCache\n ? { version: CACHE_VERSION, endpoints: {} }\n : await loadServingCache();\n\n let client: WorkspaceClient | undefined;\n let updated = false;\n\n const registryEntries: string[] = [];\n const logEntries: Array<{\n alias: string;\n status: \"HIT\" | \"MISS\";\n error?: string;\n }> = [];\n\n for (const [alias, config] of Object.entries(endpoints)) {\n client ??= new WorkspaceClient({});\n const result = await processEndpoint(alias, config, client, cache);\n if (result.cacheUpdated) updated = true;\n registryEntries.push(result.entry);\n logEntries.push(result.log);\n }\n\n printLogTable(logEntries, startTime);\n\n const output = generateTypeDeclarations(registryEntries);\n await fs.mkdir(path.dirname(outFile), { recursive: true });\n await fs.writeFile(outFile, output, \"utf-8\");\n\n // One-time migration: remove old generated file and patch project configs\n await removeOldGeneratedTypes(projectRoot, \"appKitServingTypes.d.ts\");\n await migrateProjectConfig(projectRoot);\n\n if (registryEntries.length === 0) {\n logger.debug(\n \"Wrote empty serving types to %s (no endpoints resolved)\",\n outFile,\n );\n } else {\n logger.debug(\"Wrote serving types to %s\", outFile);\n }\n\n if (updated) {\n await saveServingCache(cache as ServingCache);\n }\n}\n\ninterface EndpointResult {\n entry: string;\n log: { alias: string; status: \"HIT\" | \"MISS\"; error?: string };\n cacheUpdated: boolean;\n}\n\nfunction genericEntry(alias: string): string {\n return buildRegistryEntry(\n alias,\n GENERIC_REQUEST,\n GENERIC_RESPONSE,\n GENERIC_CHUNK,\n );\n}\n\nasync function processEndpoint(\n alias: string,\n config: EndpointConfig,\n client: WorkspaceClient,\n cache: { endpoints: Record<string, any> },\n): Promise<EndpointResult> {\n const endpointName = process.env[config.env];\n if (!endpointName) {\n return {\n entry: genericEntry(alias),\n log: { alias, status: \"MISS\", error: `env ${config.env} not set` },\n cacheUpdated: false,\n };\n }\n\n const result = await fetchOpenApiSchema(\n client,\n endpointName,\n config.servedModel,\n );\n if (!result) {\n return {\n entry: genericEntry(alias),\n log: { alias, status: \"MISS\", error: \"schema fetch failed\" },\n cacheUpdated: false,\n };\n }\n\n const { spec, pathKey } = result;\n const hash = hashSchema(JSON.stringify(spec));\n\n // Cache hit\n const cached = cache.endpoints[alias];\n if (cached && cached.hash === hash) {\n return {\n entry: buildRegistryEntry(\n alias,\n cached.requestType,\n cached.responseType,\n cached.chunkType,\n ),\n log: { alias, status: \"HIT\" },\n cacheUpdated: false,\n };\n }\n\n // Cache miss — convert schema to types\n const operation = spec.paths[pathKey]?.post;\n if (!operation) {\n return {\n entry: genericEntry(alias),\n log: { alias, status: \"MISS\", error: \"no POST operation\" },\n cacheUpdated: false,\n };\n }\n\n try {\n const requestType = convertRequestSchema(operation);\n const responseType = convertResponseSchema(operation);\n const chunkType = deriveChunkType(operation);\n const requestKeys = extractRequestKeys(operation);\n\n cache.endpoints[alias] = {\n hash,\n requestType,\n responseType,\n chunkType,\n requestKeys,\n };\n\n return {\n entry: buildRegistryEntry(alias, requestType, responseType, chunkType),\n log: { alias, status: \"MISS\" },\n cacheUpdated: true,\n };\n } catch (convErr) {\n logger.warn(\n \"Schema conversion failed for '%s': %s\",\n alias,\n (convErr as Error).message,\n );\n return {\n entry: genericEntry(alias),\n log: { alias, status: \"MISS\", error: \"schema conversion failed\" },\n cacheUpdated: false,\n };\n }\n}\n\nfunction printLogTable(\n logEntries: Array<{ alias: string; status: \"HIT\" | \"MISS\"; error?: string }>,\n startTime: number,\n): void {\n if (logEntries.length === 0) return;\n\n const maxNameLen = Math.max(...logEntries.map((e) => e.alias.length));\n const separator = pc.dim(\"─\".repeat(50));\n console.log(\"\");\n console.log(\n ` ${pc.bold(\"Typegen Serving\")} ${pc.dim(`(${logEntries.length})`)}`,\n );\n console.log(` ${separator}`);\n for (const entry of logEntries) {\n const tag =\n entry.status === \"HIT\"\n ? `cache ${pc.bold(pc.green(\"HIT \"))}`\n : `cache ${pc.bold(pc.yellow(\"MISS \"))}`;\n const rawName = entry.alias.padEnd(maxNameLen);\n const reason = entry.error ? ` ${pc.dim(entry.error)}` : \"\";\n console.log(` ${tag} ${rawName}${reason}`);\n }\n const elapsed = ((performance.now() - startTime) / 1000).toFixed(2);\n const newCount = logEntries.filter((e) => e.status === \"MISS\").length;\n const cacheCount = logEntries.filter((e) => e.status === \"HIT\").length;\n console.log(` ${separator}`);\n console.log(\n ` ${newCount} new, ${cacheCount} from cache. ${pc.dim(`${elapsed}s`)}`,\n );\n console.log(\"\");\n}\n\nfunction resolveEndpointsFromServerFile():\n | Record<string, EndpointConfig>\n | undefined {\n try {\n const serverFile = findServerFile(process.cwd());\n if (!serverFile) return undefined;\n return extractServingEndpoints(serverFile) ?? undefined;\n } catch (error) {\n logger.debug(\n \"Failed to extract endpoints from server file: %s\",\n (error as Error).message,\n );\n return undefined;\n }\n}\n\nfunction resolveDefaultEndpoints(): Record<string, EndpointConfig> {\n if (process.env.DATABRICKS_SERVING_ENDPOINT_NAME) {\n return { default: { env: \"DATABRICKS_SERVING_ENDPOINT_NAME\" } };\n }\n return {};\n}\n\nfunction buildRegistryEntry(\n alias: string,\n requestType: string,\n responseType: string,\n chunkType: string | null,\n): string {\n const indent = \" \";\n const chunkEntry = chunkType ? chunkType : \"unknown\";\n return ` ${alias}: {\n${indent}request: ${indentType(requestType, indent)};\n${indent}response: ${indentType(responseType, indent)};\n${indent}chunk: ${indentType(chunkEntry, indent)};\n };`;\n}\n\nfunction indentType(typeStr: string, baseIndent: string): string {\n if (!typeStr.includes(\"\\n\")) return typeStr;\n return typeStr\n .split(\"\\n\")\n .map((line, i) => (i === 0 ? line : `${baseIndent}${line}`))\n .join(\"\\n\");\n}\n\nfunction generateTypeDeclarations(entries: string[]): string {\n return `// Auto-generated by AppKit - DO NOT EDIT\n// Generated from serving endpoint OpenAPI schemas\nimport \"@databricks/appkit\";\nimport \"@databricks/appkit-ui/react\";\n\ndeclare module \"@databricks/appkit\" {\n interface ServingEndpointRegistry {\n${entries.join(\"\\n\")}\n }\n}\n\ndeclare module \"@databricks/appkit-ui/react\" {\n interface ServingEndpointRegistry {\n${entries.join(\"\\n\")}\n }\n}\n`;\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { EndpointConfig } from "../../plugins/serving/types.js";
2
+
3
+ //#region src/type-generator/serving/server-file-extractor.d.ts
4
+ /**
5
+ * Find the server entry file by checking candidate paths in order.
6
+ *
7
+ * @param basePath - Project root directory to search from
8
+ * @returns Absolute path to the server file, or null if none found
9
+ */
10
+ declare function findServerFile(basePath: string): string | null;
11
+ /**
12
+ * Extract serving endpoint config from a server file by AST-parsing it.
13
+ * Looks for `serving({ endpoints: { alias: { env: "..." }, ... } })` calls
14
+ * and extracts the endpoint alias names and their environment variable mappings.
15
+ *
16
+ * @param serverFilePath - Absolute path to the server entry file
17
+ * @returns Extracted endpoint config, or null if not found or not extractable
18
+ */
19
+ declare function extractServingEndpoints(serverFilePath: string): Record<string, EndpointConfig> | null;
20
+ //#endregion
21
+ export { extractServingEndpoints, findServerFile };
22
+ //# sourceMappingURL=server-file-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-file-extractor.d.ts","names":[],"sources":["../../../src/type-generator/serving/server-file-extractor.ts"],"mappings":";;;;;AAqBA;;;;iBAAgB,cAAA,CAAe,QAAA;AAkB/B;;;;;;;;AAAA,iBAAgB,uBAAA,CACd,cAAA,WACC,MAAA,SAAe,cAAA"}
@@ -0,0 +1,131 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import { Lang, parse } from "@ast-grep/napi";
5
+
6
+ //#region src/type-generator/serving/server-file-extractor.ts
7
+ const logger = createLogger("type-generator:serving:extractor");
8
+ /**
9
+ * Candidate paths for the server entry file, relative to the project root.
10
+ * Checked in order; the first that exists is used.
11
+ * Same convention as plugin sync (sync.ts SERVER_FILE_CANDIDATES).
12
+ */
13
+ const SERVER_FILE_CANDIDATES = ["server/index.ts", "server/server.ts"];
14
+ /**
15
+ * Find the server entry file by checking candidate paths in order.
16
+ *
17
+ * @param basePath - Project root directory to search from
18
+ * @returns Absolute path to the server file, or null if none found
19
+ */
20
+ function findServerFile(basePath) {
21
+ for (const candidate of SERVER_FILE_CANDIDATES) {
22
+ const fullPath = path.join(basePath, candidate);
23
+ if (fs.existsSync(fullPath)) return fullPath;
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Extract serving endpoint config from a server file by AST-parsing it.
29
+ * Looks for `serving({ endpoints: { alias: { env: "..." }, ... } })` calls
30
+ * and extracts the endpoint alias names and their environment variable mappings.
31
+ *
32
+ * @param serverFilePath - Absolute path to the server entry file
33
+ * @returns Extracted endpoint config, or null if not found or not extractable
34
+ */
35
+ function extractServingEndpoints(serverFilePath) {
36
+ let content;
37
+ try {
38
+ content = fs.readFileSync(serverFilePath, "utf-8");
39
+ } catch {
40
+ logger.debug("Could not read server file: %s", serverFilePath);
41
+ return null;
42
+ }
43
+ const servingCall = findServingCall(parse(serverFilePath.endsWith(".tsx") ? Lang.Tsx : Lang.TypeScript, content).root());
44
+ if (!servingCall) {
45
+ logger.debug("No serving() call found in %s", serverFilePath);
46
+ return null;
47
+ }
48
+ const args = servingCall.field("arguments");
49
+ if (!args) return null;
50
+ const configArg = args.children().find((child) => child.kind() === "object");
51
+ if (!configArg) return null;
52
+ const endpointsPair = findProperty(configArg, "endpoints");
53
+ if (!endpointsPair) return null;
54
+ const endpointsValue = getPropertyValue(endpointsPair);
55
+ if (!endpointsValue || endpointsValue.kind() !== "object") {
56
+ logger.debug("serving() endpoints is not an inline object literal in %s. Pass endpoints explicitly via appKitServingTypesPlugin({ endpoints }) in vite.config.ts.", serverFilePath);
57
+ return null;
58
+ }
59
+ const endpoints = {};
60
+ const pairs = endpointsValue.children().filter((child) => child.kind() === "pair");
61
+ for (const pair of pairs) {
62
+ const entry = extractEndpointEntry(pair);
63
+ if (entry) endpoints[entry.alias] = entry.config;
64
+ }
65
+ if (Object.keys(endpoints).length === 0) return null;
66
+ logger.debug("Extracted %d endpoint(s) from %s: %s", Object.keys(endpoints).length, serverFilePath, Object.keys(endpoints).join(", "));
67
+ return endpoints;
68
+ }
69
+ /**
70
+ * Find the serving() call expression in the AST.
71
+ * Looks for call expressions where the callee identifier is "serving".
72
+ */
73
+ function findServingCall(root) {
74
+ const callExpressions = root.findAll({ rule: { kind: "call_expression" } });
75
+ for (const call of callExpressions) {
76
+ const callee = call.children()[0];
77
+ if (callee?.kind() === "identifier" && callee.text() === "serving") return call;
78
+ }
79
+ return null;
80
+ }
81
+ /**
82
+ * Find a property (pair node) with the given key name in an object expression.
83
+ */
84
+ function findProperty(objectNode, propertyName) {
85
+ const pairs = objectNode.children().filter((child) => child.kind() === "pair");
86
+ for (const pair of pairs) {
87
+ const key = pair.children()[0];
88
+ if (!key) continue;
89
+ if ((key.kind() === "property_identifier" ? key.text() : key.kind() === "string" ? key.text().replace(/^['"]|['"]$/g, "") : null) === propertyName) return pair;
90
+ }
91
+ return null;
92
+ }
93
+ /**
94
+ * Get the value node from a pair (property: value).
95
+ * The value is typically the last meaningful child after the colon.
96
+ */
97
+ function getPropertyValue(pairNode) {
98
+ const children = pairNode.children();
99
+ return children.length >= 3 ? children[children.length - 1] : null;
100
+ }
101
+ /**
102
+ * Extract a single endpoint entry from a pair node like:
103
+ * `demo: { env: "DATABRICKS_SERVING_ENDPOINT_NAME", servedModel: "my-model" }`
104
+ */
105
+ function extractEndpointEntry(pair) {
106
+ const children = pair.children();
107
+ if (children.length < 3) return null;
108
+ const keyNode = children[0];
109
+ const alias = keyNode.kind() === "property_identifier" ? keyNode.text() : keyNode.kind() === "string" ? keyNode.text().replace(/^['"]|['"]$/g, "") : null;
110
+ if (!alias) return null;
111
+ const valueNode = children[children.length - 1];
112
+ if (valueNode.kind() !== "object") return null;
113
+ const envPair = findProperty(valueNode, "env");
114
+ if (!envPair) return null;
115
+ const envValue = getPropertyValue(envPair);
116
+ if (!envValue || envValue.kind() !== "string") return null;
117
+ const config = { env: envValue.text().replace(/^['"]|['"]$/g, "") };
118
+ const servedModelPair = findProperty(valueNode, "servedModel");
119
+ if (servedModelPair) {
120
+ const servedModelValue = getPropertyValue(servedModelPair);
121
+ if (servedModelValue?.kind() === "string") config.servedModel = servedModelValue.text().replace(/^['"]|['"]$/g, "");
122
+ }
123
+ return {
124
+ alias,
125
+ config
126
+ };
127
+ }
128
+
129
+ //#endregion
130
+ export { extractServingEndpoints, findServerFile };
131
+ //# sourceMappingURL=server-file-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-file-extractor.js","names":[],"sources":["../../../src/type-generator/serving/server-file-extractor.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Lang, parse, type SgNode } from \"@ast-grep/napi\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { EndpointConfig } from \"../../plugins/serving/types\";\n\nconst logger = createLogger(\"type-generator:serving:extractor\");\n\n/**\n * Candidate paths for the server entry file, relative to the project root.\n * Checked in order; the first that exists is used.\n * Same convention as plugin sync (sync.ts SERVER_FILE_CANDIDATES).\n */\nconst SERVER_FILE_CANDIDATES = [\"server/index.ts\", \"server/server.ts\"];\n\n/**\n * Find the server entry file by checking candidate paths in order.\n *\n * @param basePath - Project root directory to search from\n * @returns Absolute path to the server file, or null if none found\n */\nexport function findServerFile(basePath: string): string | null {\n for (const candidate of SERVER_FILE_CANDIDATES) {\n const fullPath = path.join(basePath, candidate);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n}\n\n/**\n * Extract serving endpoint config from a server file by AST-parsing it.\n * Looks for `serving({ endpoints: { alias: { env: \"...\" }, ... } })` calls\n * and extracts the endpoint alias names and their environment variable mappings.\n *\n * @param serverFilePath - Absolute path to the server entry file\n * @returns Extracted endpoint config, or null if not found or not extractable\n */\nexport function extractServingEndpoints(\n serverFilePath: string,\n): Record<string, EndpointConfig> | null {\n let content: string;\n try {\n content = fs.readFileSync(serverFilePath, \"utf-8\");\n } catch {\n logger.debug(\"Could not read server file: %s\", serverFilePath);\n return null;\n }\n\n const lang = serverFilePath.endsWith(\".tsx\") ? Lang.Tsx : Lang.TypeScript;\n const ast = parse(lang, content);\n const root = ast.root();\n\n // Find serving(...) call expressions\n const servingCall = findServingCall(root);\n if (!servingCall) {\n logger.debug(\"No serving() call found in %s\", serverFilePath);\n return null;\n }\n\n // Get the first argument (the config object)\n const args = servingCall.field(\"arguments\");\n if (!args) {\n return null;\n }\n\n const configArg = args.children().find((child) => child.kind() === \"object\");\n if (!configArg) {\n // serving() called with no args or non-object arg\n return null;\n }\n\n // Find the \"endpoints\" property in the config object\n const endpointsPair = findProperty(configArg, \"endpoints\");\n if (!endpointsPair) {\n // Config object has no \"endpoints\" property (e.g. serving({ timeout: 5000 }))\n return null;\n }\n\n // Get the value of the endpoints property\n const endpointsValue = getPropertyValue(endpointsPair);\n if (!endpointsValue || endpointsValue.kind() !== \"object\") {\n // endpoints is a variable reference, not an inline object\n logger.debug(\n \"serving() endpoints is not an inline object literal in %s. \" +\n \"Pass endpoints explicitly via appKitServingTypesPlugin({ endpoints }) in vite.config.ts.\",\n serverFilePath,\n );\n return null;\n }\n\n // Extract each endpoint entry\n const endpoints: Record<string, EndpointConfig> = {};\n const pairs = endpointsValue\n .children()\n .filter((child) => child.kind() === \"pair\");\n\n for (const pair of pairs) {\n const entry = extractEndpointEntry(pair);\n if (entry) {\n endpoints[entry.alias] = entry.config;\n }\n }\n\n if (Object.keys(endpoints).length === 0) {\n return null;\n }\n\n logger.debug(\n \"Extracted %d endpoint(s) from %s: %s\",\n Object.keys(endpoints).length,\n serverFilePath,\n Object.keys(endpoints).join(\", \"),\n );\n\n return endpoints;\n}\n\n/**\n * Find the serving() call expression in the AST.\n * Looks for call expressions where the callee identifier is \"serving\".\n */\nfunction findServingCall(root: SgNode): SgNode | null {\n const callExpressions = root.findAll({\n rule: { kind: \"call_expression\" },\n });\n\n for (const call of callExpressions) {\n const callee = call.children()[0];\n if (callee?.kind() === \"identifier\" && callee.text() === \"serving\") {\n return call;\n }\n }\n\n return null;\n}\n\n/**\n * Find a property (pair node) with the given key name in an object expression.\n */\nfunction findProperty(objectNode: SgNode, propertyName: string): SgNode | null {\n const pairs = objectNode\n .children()\n .filter((child) => child.kind() === \"pair\");\n\n for (const pair of pairs) {\n const key = pair.children()[0];\n if (!key) continue;\n\n const keyText =\n key.kind() === \"property_identifier\"\n ? key.text()\n : key.kind() === \"string\"\n ? key.text().replace(/^['\"]|['\"]$/g, \"\")\n : null;\n\n if (keyText === propertyName) {\n return pair;\n }\n }\n\n return null;\n}\n\n/**\n * Get the value node from a pair (property: value).\n * The value is typically the last meaningful child after the colon.\n */\nfunction getPropertyValue(pairNode: SgNode): SgNode | null {\n const children = pairNode.children();\n // pair children: [key, \":\", value]\n return children.length >= 3 ? children[children.length - 1] : null;\n}\n\n/**\n * Extract a single endpoint entry from a pair node like:\n * `demo: { env: \"DATABRICKS_SERVING_ENDPOINT_NAME\", servedModel: \"my-model\" }`\n */\nfunction extractEndpointEntry(\n pair: SgNode,\n): { alias: string; config: EndpointConfig } | null {\n const children = pair.children();\n if (children.length < 3) return null;\n\n // Get alias name (the key)\n const keyNode = children[0];\n const alias =\n keyNode.kind() === \"property_identifier\"\n ? keyNode.text()\n : keyNode.kind() === \"string\"\n ? keyNode.text().replace(/^['\"]|['\"]$/g, \"\")\n : null;\n\n if (!alias) return null;\n\n // Get the value (should be an object like { env: \"...\" })\n const valueNode = children[children.length - 1];\n if (valueNode.kind() !== \"object\") return null;\n\n // Extract env field\n const envPair = findProperty(valueNode, \"env\");\n if (!envPair) return null;\n\n const envValue = getPropertyValue(envPair);\n if (!envValue || envValue.kind() !== \"string\") return null;\n\n const env = envValue.text().replace(/^['\"]|['\"]$/g, \"\");\n\n // Extract optional servedModel field\n const config: EndpointConfig = { env };\n const servedModelPair = findProperty(valueNode, \"servedModel\");\n if (servedModelPair) {\n const servedModelValue = getPropertyValue(servedModelPair);\n if (servedModelValue?.kind() === \"string\") {\n config.servedModel = servedModelValue.text().replace(/^['\"]|['\"]$/g, \"\");\n }\n }\n\n return { alias, config };\n}\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { EndpointConfig } from "../../plugins/serving/types.js";
2
+ import { Plugin } from "vite";
3
+
4
+ //#region src/type-generator/serving/vite-plugin.d.ts
5
+ interface AppKitServingTypesPluginOptions {
6
+ /** Path to the output .d.ts file (relative to project root). */
7
+ outFile?: string;
8
+ /** Endpoint config override. If omitted, auto-discovers from the server file or falls back to DATABRICKS_SERVING_ENDPOINT_NAME env var. */
9
+ endpoints?: Record<string, EndpointConfig>;
10
+ }
11
+ /**
12
+ * Vite plugin to generate TypeScript types for AppKit serving endpoints.
13
+ * Fetches OpenAPI schemas from Databricks and generates a .d.ts with
14
+ * ServingEndpointRegistry module augmentation.
15
+ *
16
+ * Endpoint discovery order:
17
+ * 1. Explicit `endpoints` option (override)
18
+ * 2. AST extraction from server file (server/index.ts or server/server.ts)
19
+ * 3. DATABRICKS_SERVING_ENDPOINT_NAME env var (single default endpoint)
20
+ */
21
+ declare function appKitServingTypesPlugin(options?: AppKitServingTypesPluginOptions): Plugin;
22
+ //#endregion
23
+ export { appKitServingTypesPlugin };
24
+ //# sourceMappingURL=vite-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin.d.ts","names":[],"sources":["../../../src/type-generator/serving/vite-plugin.ts"],"mappings":";;;;UAYU,+BAAA;;EAER,OAAA;EAFuC;EAIvC,SAAA,GAAY,MAAA,SAAe,cAAA;AAAA;;;;;;;AAa7B;;;;iBAAgB,wBAAA,CACd,OAAA,GAAU,+BAAA,GACT,MAAA"}
@@ -0,0 +1,60 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+ import { extractServingEndpoints, findServerFile } from "./server-file-extractor.js";
3
+ import { SERVING_TYPES_FILE, TYPES_DIR, generateServingTypes } from "../index.js";
4
+ import path from "node:path";
5
+
6
+ //#region src/type-generator/serving/vite-plugin.ts
7
+ const logger = createLogger("type-generator:serving:vite-plugin");
8
+ /**
9
+ * Vite plugin to generate TypeScript types for AppKit serving endpoints.
10
+ * Fetches OpenAPI schemas from Databricks and generates a .d.ts with
11
+ * ServingEndpointRegistry module augmentation.
12
+ *
13
+ * Endpoint discovery order:
14
+ * 1. Explicit `endpoints` option (override)
15
+ * 2. AST extraction from server file (server/index.ts or server/server.ts)
16
+ * 3. DATABRICKS_SERVING_ENDPOINT_NAME env var (single default endpoint)
17
+ */
18
+ function appKitServingTypesPlugin(options) {
19
+ let outFile;
20
+ let projectRoot;
21
+ async function generate() {
22
+ try {
23
+ let endpoints = options?.endpoints;
24
+ if (!endpoints) {
25
+ const serverFile = findServerFile(projectRoot);
26
+ if (serverFile) endpoints = extractServingEndpoints(serverFile) ?? void 0;
27
+ }
28
+ await generateServingTypes({
29
+ outFile,
30
+ endpoints,
31
+ noCache: false
32
+ });
33
+ } catch (error) {
34
+ if (process.env.NODE_ENV === "production") throw error;
35
+ logger.error("Error generating serving types: %O", error);
36
+ }
37
+ }
38
+ return {
39
+ name: "appkit-serving-types",
40
+ apply() {
41
+ if (options?.endpoints && Object.keys(options.endpoints).length > 0) return true;
42
+ if (process.env.DATABRICKS_SERVING_ENDPOINT_NAME) return true;
43
+ if (findServerFile(process.cwd())) return true;
44
+ if (findServerFile(path.resolve(process.cwd(), ".."))) return true;
45
+ logger.debug("No serving endpoints configured. Skipping type generation.");
46
+ return false;
47
+ },
48
+ configResolved(config) {
49
+ projectRoot = path.resolve(config.root, "..");
50
+ outFile = path.resolve(projectRoot, options?.outFile ?? `shared/${TYPES_DIR}/${SERVING_TYPES_FILE}`);
51
+ },
52
+ async buildStart() {
53
+ await generate();
54
+ }
55
+ };
56
+ }
57
+
58
+ //#endregion
59
+ export { appKitServingTypesPlugin };
60
+ //# sourceMappingURL=vite-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin.js","names":[],"sources":["../../../src/type-generator/serving/vite-plugin.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { EndpointConfig } from \"../../plugins/serving/types\";\nimport { generateServingTypes, SERVING_TYPES_FILE, TYPES_DIR } from \"../index\";\nimport {\n extractServingEndpoints,\n findServerFile,\n} from \"./server-file-extractor\";\n\nconst logger = createLogger(\"type-generator:serving:vite-plugin\");\n\ninterface AppKitServingTypesPluginOptions {\n /** Path to the output .d.ts file (relative to project root). */\n outFile?: string;\n /** Endpoint config override. If omitted, auto-discovers from the server file or falls back to DATABRICKS_SERVING_ENDPOINT_NAME env var. */\n endpoints?: Record<string, EndpointConfig>;\n}\n\n/**\n * Vite plugin to generate TypeScript types for AppKit serving endpoints.\n * Fetches OpenAPI schemas from Databricks and generates a .d.ts with\n * ServingEndpointRegistry module augmentation.\n *\n * Endpoint discovery order:\n * 1. Explicit `endpoints` option (override)\n * 2. AST extraction from server file (server/index.ts or server/server.ts)\n * 3. DATABRICKS_SERVING_ENDPOINT_NAME env var (single default endpoint)\n */\nexport function appKitServingTypesPlugin(\n options?: AppKitServingTypesPluginOptions,\n): Plugin {\n let outFile: string;\n let projectRoot: string;\n\n async function generate() {\n try {\n // Resolve endpoints: explicit option > server file AST > env var fallback (handled by generator)\n let endpoints = options?.endpoints;\n if (!endpoints) {\n const serverFile = findServerFile(projectRoot);\n if (serverFile) {\n endpoints = extractServingEndpoints(serverFile) ?? undefined;\n }\n }\n\n await generateServingTypes({\n outFile,\n endpoints,\n noCache: false,\n });\n } catch (error) {\n if (process.env.NODE_ENV === \"production\") {\n throw error;\n }\n logger.error(\"Error generating serving types: %O\", error);\n }\n }\n\n return {\n name: \"appkit-serving-types\",\n\n apply() {\n // Fast checks — no AST parsing here\n if (options?.endpoints && Object.keys(options.endpoints).length > 0) {\n return true;\n }\n\n if (process.env.DATABRICKS_SERVING_ENDPOINT_NAME) {\n return true;\n }\n\n // Check if a server file exists (may contain serving() config)\n // Use process.cwd() for apply() since configResolved hasn't run yet\n if (findServerFile(process.cwd())) {\n return true;\n }\n\n // Also check parent dir (for when cwd is client/)\n const parentDir = path.resolve(process.cwd(), \"..\");\n if (findServerFile(parentDir)) {\n return true;\n }\n\n logger.debug(\n \"No serving endpoints configured. Skipping type generation.\",\n );\n return false;\n },\n\n configResolved(config) {\n // Resolve project root: go up one level from Vite root (client dir)\n // This handles both:\n // - pnpm dev: process.cwd() is app root, config.root is client/\n // - pnpm build: process.cwd() is client/ (cd client && vite build), config.root is client/\n projectRoot = path.resolve(config.root, \"..\");\n outFile = path.resolve(\n projectRoot,\n options?.outFile ?? `shared/${TYPES_DIR}/${SERVING_TYPES_FILE}`,\n );\n },\n\n async buildStart() {\n await generate();\n },\n\n // No configureServer / watcher — schemas change on endpoint redeploy, not on file edit\n };\n}\n"],"mappings":";;;;;;AAUA,MAAM,SAAS,aAAa,qCAAqC;;;;;;;;;;;AAmBjE,SAAgB,yBACd,SACQ;CACR,IAAI;CACJ,IAAI;CAEJ,eAAe,WAAW;AACxB,MAAI;GAEF,IAAI,YAAY,SAAS;AACzB,OAAI,CAAC,WAAW;IACd,MAAM,aAAa,eAAe,YAAY;AAC9C,QAAI,WACF,aAAY,wBAAwB,WAAW,IAAI;;AAIvD,SAAM,qBAAqB;IACzB;IACA;IACA,SAAS;IACV,CAAC;WACK,OAAO;AACd,OAAI,QAAQ,IAAI,aAAa,aAC3B,OAAM;AAER,UAAO,MAAM,sCAAsC,MAAM;;;AAI7D,QAAO;EACL,MAAM;EAEN,QAAQ;AAEN,OAAI,SAAS,aAAa,OAAO,KAAK,QAAQ,UAAU,CAAC,SAAS,EAChE,QAAO;AAGT,OAAI,QAAQ,IAAI,iCACd,QAAO;AAKT,OAAI,eAAe,QAAQ,KAAK,CAAC,CAC/B,QAAO;AAKT,OAAI,eADc,KAAK,QAAQ,QAAQ,KAAK,EAAE,KAAK,CACtB,CAC3B,QAAO;AAGT,UAAO,MACL,6DACD;AACD,UAAO;;EAGT,eAAe,QAAQ;AAKrB,iBAAc,KAAK,QAAQ,OAAO,MAAM,KAAK;AAC7C,aAAU,KAAK,QACb,aACA,SAAS,WAAW,UAAU,UAAU,GAAG,qBAC5C;;EAGH,MAAM,aAAa;AACjB,SAAM,UAAU;;EAInB"}
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.d.ts","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"mappings":";;;;;AAEmC;UASzB,wBAAA;EAER,OAAA;EAAA;EAEA,YAAA;AAAA;;;;;;;iBASc,iBAAA,CAAkB,OAAA,GAAU,wBAAA,GAA2B,MAAA"}
1
+ {"version":3,"file":"vite-plugin.d.ts","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"mappings":";;;;;AAEmC;UAazB,wBAAA;EAER,OAAA;EAAA;EAEA,YAAA;AAAA;;;;;;;iBASc,iBAAA,CAAkB,OAAA,GAAU,wBAAA,GAA2B,MAAA"}
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "../logging/logger.js";
2
- import { generateFromEntryPoint } from "./index.js";
2
+ import { ANALYTICS_TYPES_FILE, TYPES_DIR, generateFromEntryPoint } from "./index.js";
3
3
  import path from "node:path";
4
4
  import { existsSync } from "node:fs";
5
5
 
@@ -12,7 +12,6 @@ const logger = createLogger("type-generator:vite-plugin");
12
12
  * @returns Vite plugin to generate types for AppKit queries.
13
13
  */
14
14
  function appKitTypesPlugin(options) {
15
- let root;
16
15
  let outFile;
17
16
  let watchFolders;
18
17
  async function generate() {
@@ -44,8 +43,8 @@ function appKitTypesPlugin(options) {
44
43
  return true;
45
44
  },
46
45
  configResolved(config) {
47
- root = config.root;
48
- outFile = path.resolve(root, options?.outFile ?? "src/appKitTypes.d.ts");
46
+ const projectRoot = path.resolve(config.root, "..");
47
+ outFile = path.resolve(projectRoot, options?.outFile ?? `shared/${TYPES_DIR}/${ANALYTICS_TYPES_FILE}`);
49
48
  watchFolders = options?.watchFolders ?? [path.join(process.cwd(), "config", "queries")];
50
49
  },
51
50
  buildStart() {
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.js","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { createLogger } from \"../logging/logger\";\nimport { generateFromEntryPoint } from \"./index\";\n\nconst logger = createLogger(\"type-generator:vite-plugin\");\n\n/**\n * Options for the AppKit types plugin.\n */\ninterface AppKitTypesPluginOptions {\n /* Path to the output d.ts file (relative to client folder). */\n outFile?: string;\n /** Folders to watch for changes. */\n watchFolders?: string[];\n}\n\n/**\n * Vite plugin to generate types for AppKit queries.\n * Calls generateFromEntryPoint under the hood.\n * @param options - Options to override default values.\n * @returns Vite plugin to generate types for AppKit queries.\n */\nexport function appKitTypesPlugin(options?: AppKitTypesPluginOptions): Plugin {\n let root: string;\n let outFile: string;\n let watchFolders: string[];\n\n async function generate() {\n try {\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n\n if (!warehouseId) {\n logger.debug(\"Warehouse ID not found. Skipping type generation.\");\n return;\n }\n\n await generateFromEntryPoint({\n outFile,\n queryFolder: watchFolders[0],\n warehouseId,\n noCache: false,\n });\n } catch (error) {\n // throw in production to fail the build\n if (process.env.NODE_ENV === \"production\") {\n throw error;\n }\n logger.error(\"Error generating types: %O\", error);\n }\n }\n\n return {\n name: \"appkit-types\",\n\n apply() {\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n\n if (!warehouseId) {\n logger.debug(\"Warehouse ID not found. Skipping type generation.\");\n return false;\n }\n\n if (!existsSync(path.join(process.cwd(), \"config\", \"queries\"))) {\n return false;\n }\n\n return true;\n },\n\n configResolved(config) {\n root = config.root;\n outFile = path.resolve(root, options?.outFile ?? \"src/appKitTypes.d.ts\");\n watchFolders = options?.watchFolders ?? [\n path.join(process.cwd(), \"config\", \"queries\"),\n ];\n },\n\n buildStart() {\n generate();\n },\n\n configureServer(server) {\n server.watcher.add(watchFolders);\n\n server.watcher.on(\"change\", (changedFile) => {\n const isWatchedFile = watchFolders.some((folder) =>\n changedFile.startsWith(folder),\n );\n\n if (isWatchedFile && changedFile.endsWith(\".sql\")) {\n generate();\n }\n });\n },\n };\n}\n"],"mappings":";;;;;;AAMA,MAAM,SAAS,aAAa,6BAA6B;;;;;;;AAkBzD,SAAgB,kBAAkB,SAA4C;CAC5E,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,eAAe,WAAW;AACxB,MAAI;GACF,MAAM,cAAc,QAAQ,IAAI,2BAA2B;AAE3D,OAAI,CAAC,aAAa;AAChB,WAAO,MAAM,oDAAoD;AACjE;;AAGF,SAAM,uBAAuB;IAC3B;IACA,aAAa,aAAa;IAC1B;IACA,SAAS;IACV,CAAC;WACK,OAAO;AAEd,OAAI,QAAQ,IAAI,aAAa,aAC3B,OAAM;AAER,UAAO,MAAM,8BAA8B,MAAM;;;AAIrD,QAAO;EACL,MAAM;EAEN,QAAQ;AAGN,OAAI,EAFgB,QAAQ,IAAI,2BAA2B,KAEzC;AAChB,WAAO,MAAM,oDAAoD;AACjE,WAAO;;AAGT,OAAI,CAAC,WAAW,KAAK,KAAK,QAAQ,KAAK,EAAE,UAAU,UAAU,CAAC,CAC5D,QAAO;AAGT,UAAO;;EAGT,eAAe,QAAQ;AACrB,UAAO,OAAO;AACd,aAAU,KAAK,QAAQ,MAAM,SAAS,WAAW,uBAAuB;AACxE,kBAAe,SAAS,gBAAgB,CACtC,KAAK,KAAK,QAAQ,KAAK,EAAE,UAAU,UAAU,CAC9C;;EAGH,aAAa;AACX,aAAU;;EAGZ,gBAAgB,QAAQ;AACtB,UAAO,QAAQ,IAAI,aAAa;AAEhC,UAAO,QAAQ,GAAG,WAAW,gBAAgB;AAK3C,QAJsB,aAAa,MAAM,WACvC,YAAY,WAAW,OAAO,CAC/B,IAEoB,YAAY,SAAS,OAAO,CAC/C,WAAU;KAEZ;;EAEL"}
1
+ {"version":3,"file":"vite-plugin.js","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { createLogger } from \"../logging/logger\";\nimport {\n ANALYTICS_TYPES_FILE,\n generateFromEntryPoint,\n TYPES_DIR,\n} from \"./index\";\n\nconst logger = createLogger(\"type-generator:vite-plugin\");\n\n/**\n * Options for the AppKit types plugin.\n */\ninterface AppKitTypesPluginOptions {\n /* Path to the output d.ts file (relative to client folder). */\n outFile?: string;\n /** Folders to watch for changes. */\n watchFolders?: string[];\n}\n\n/**\n * Vite plugin to generate types for AppKit queries.\n * Calls generateFromEntryPoint under the hood.\n * @param options - Options to override default values.\n * @returns Vite plugin to generate types for AppKit queries.\n */\nexport function appKitTypesPlugin(options?: AppKitTypesPluginOptions): Plugin {\n let outFile: string;\n let watchFolders: string[];\n\n async function generate() {\n try {\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n\n if (!warehouseId) {\n logger.debug(\"Warehouse ID not found. Skipping type generation.\");\n return;\n }\n\n await generateFromEntryPoint({\n outFile,\n queryFolder: watchFolders[0],\n warehouseId,\n noCache: false,\n });\n } catch (error) {\n // throw in production to fail the build\n if (process.env.NODE_ENV === \"production\") {\n throw error;\n }\n logger.error(\"Error generating types: %O\", error);\n }\n }\n\n return {\n name: \"appkit-types\",\n\n apply() {\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n\n if (!warehouseId) {\n logger.debug(\"Warehouse ID not found. Skipping type generation.\");\n return false;\n }\n\n if (!existsSync(path.join(process.cwd(), \"config\", \"queries\"))) {\n return false;\n }\n\n return true;\n },\n\n configResolved(config) {\n const projectRoot = path.resolve(config.root, \"..\");\n outFile = path.resolve(\n projectRoot,\n options?.outFile ?? `shared/${TYPES_DIR}/${ANALYTICS_TYPES_FILE}`,\n );\n watchFolders = options?.watchFolders ?? [\n path.join(process.cwd(), \"config\", \"queries\"),\n ];\n },\n\n buildStart() {\n generate();\n },\n\n configureServer(server) {\n server.watcher.add(watchFolders);\n\n server.watcher.on(\"change\", (changedFile) => {\n const isWatchedFile = watchFolders.some((folder) =>\n changedFile.startsWith(folder),\n );\n\n if (isWatchedFile && changedFile.endsWith(\".sql\")) {\n generate();\n }\n });\n },\n };\n}\n"],"mappings":";;;;;;AAUA,MAAM,SAAS,aAAa,6BAA6B;;;;;;;AAkBzD,SAAgB,kBAAkB,SAA4C;CAC5E,IAAI;CACJ,IAAI;CAEJ,eAAe,WAAW;AACxB,MAAI;GACF,MAAM,cAAc,QAAQ,IAAI,2BAA2B;AAE3D,OAAI,CAAC,aAAa;AAChB,WAAO,MAAM,oDAAoD;AACjE;;AAGF,SAAM,uBAAuB;IAC3B;IACA,aAAa,aAAa;IAC1B;IACA,SAAS;IACV,CAAC;WACK,OAAO;AAEd,OAAI,QAAQ,IAAI,aAAa,aAC3B,OAAM;AAER,UAAO,MAAM,8BAA8B,MAAM;;;AAIrD,QAAO;EACL,MAAM;EAEN,QAAQ;AAGN,OAAI,EAFgB,QAAQ,IAAI,2BAA2B,KAEzC;AAChB,WAAO,MAAM,oDAAoD;AACjE,WAAO;;AAGT,OAAI,CAAC,WAAW,KAAK,KAAK,QAAQ,KAAK,EAAE,UAAU,UAAU,CAAC,CAC5D,QAAO;AAGT,UAAO;;EAGT,eAAe,QAAQ;GACrB,MAAM,cAAc,KAAK,QAAQ,OAAO,MAAM,KAAK;AACnD,aAAU,KAAK,QACb,aACA,SAAS,WAAW,UAAU,UAAU,GAAG,uBAC5C;AACD,kBAAe,SAAS,gBAAgB,CACtC,KAAK,KAAK,QAAQ,KAAK,EAAE,UAAU,UAAU,CAC9C;;EAGH,aAAa;AACX,aAAU;;EAGZ,gBAAgB,QAAQ;AACtB,UAAO,QAAQ,IAAI,aAAa;AAEhC,UAAO,QAAQ,GAAG,WAAW,gBAAgB;AAK3C,QAJsB,aAAa,MAAM,WACvC,YAAY,WAAW,OAAO,CAC/B,IAEoB,YAAY,SAAS,OAAO,CAC/C,WAAU;KAEZ;;EAEL"}