@databricks/appkit 0.1.5 → 0.3.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 (127) hide show
  1. package/AGENTS.md +57 -2
  2. package/CLAUDE.md +57 -2
  3. package/NOTICE.md +2 -0
  4. package/README.md +21 -15
  5. package/bin/appkit-lint.js +129 -0
  6. package/dist/analytics/analytics.d.ts.map +1 -1
  7. package/dist/analytics/analytics.js +33 -33
  8. package/dist/analytics/analytics.js.map +1 -1
  9. package/dist/analytics/query.js +8 -2
  10. package/dist/analytics/query.js.map +1 -1
  11. package/dist/app/index.d.ts +5 -1
  12. package/dist/app/index.d.ts.map +1 -1
  13. package/dist/app/index.js +41 -10
  14. package/dist/app/index.js.map +1 -1
  15. package/dist/appkit/package.js +1 -1
  16. package/dist/cache/index.d.ts.map +1 -1
  17. package/dist/cache/index.js +24 -3
  18. package/dist/cache/index.js.map +1 -1
  19. package/dist/cache/storage/persistent.js +12 -6
  20. package/dist/cache/storage/persistent.js.map +1 -1
  21. package/dist/connectors/lakebase/client.js +25 -14
  22. package/dist/connectors/lakebase/client.js.map +1 -1
  23. package/dist/connectors/sql-warehouse/client.js +68 -28
  24. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  25. package/dist/context/service-context.js +13 -8
  26. package/dist/context/service-context.js.map +1 -1
  27. package/dist/errors/authentication.d.ts +38 -0
  28. package/dist/errors/authentication.d.ts.map +1 -0
  29. package/dist/errors/authentication.js +48 -0
  30. package/dist/errors/authentication.js.map +1 -0
  31. package/dist/errors/base.d.ts +58 -0
  32. package/dist/errors/base.d.ts.map +1 -0
  33. package/dist/errors/base.js +70 -0
  34. package/dist/errors/base.js.map +1 -0
  35. package/dist/errors/configuration.d.ts +38 -0
  36. package/dist/errors/configuration.d.ts.map +1 -0
  37. package/dist/errors/configuration.js +45 -0
  38. package/dist/errors/configuration.js.map +1 -0
  39. package/dist/errors/connection.d.ts +42 -0
  40. package/dist/errors/connection.d.ts.map +1 -0
  41. package/dist/errors/connection.js +54 -0
  42. package/dist/errors/connection.js.map +1 -0
  43. package/dist/errors/execution.d.ts +42 -0
  44. package/dist/errors/execution.d.ts.map +1 -0
  45. package/dist/errors/execution.js +51 -0
  46. package/dist/errors/execution.js.map +1 -0
  47. package/dist/errors/index.js +28 -0
  48. package/dist/errors/index.js.map +1 -0
  49. package/dist/errors/initialization.d.ts +34 -0
  50. package/dist/errors/initialization.d.ts.map +1 -0
  51. package/dist/errors/initialization.js +42 -0
  52. package/dist/errors/initialization.js.map +1 -0
  53. package/dist/errors/server.d.ts +38 -0
  54. package/dist/errors/server.d.ts.map +1 -0
  55. package/dist/errors/server.js +45 -0
  56. package/dist/errors/server.js.map +1 -0
  57. package/dist/errors/tunnel.d.ts +38 -0
  58. package/dist/errors/tunnel.d.ts.map +1 -0
  59. package/dist/errors/tunnel.js +51 -0
  60. package/dist/errors/tunnel.js.map +1 -0
  61. package/dist/errors/validation.d.ts +36 -0
  62. package/dist/errors/validation.d.ts.map +1 -0
  63. package/dist/errors/validation.js +45 -0
  64. package/dist/errors/validation.js.map +1 -0
  65. package/dist/index.d.ts +12 -3
  66. package/dist/index.js +18 -3
  67. package/dist/index.js.map +1 -0
  68. package/dist/logging/logger.js +179 -0
  69. package/dist/logging/logger.js.map +1 -0
  70. package/dist/logging/sampling.js +56 -0
  71. package/dist/logging/sampling.js.map +1 -0
  72. package/dist/logging/wide-event-emitter.js +108 -0
  73. package/dist/logging/wide-event-emitter.js.map +1 -0
  74. package/dist/logging/wide-event.js +167 -0
  75. package/dist/logging/wide-event.js.map +1 -0
  76. package/dist/plugin/dev-reader.d.ts.map +1 -1
  77. package/dist/plugin/dev-reader.js +8 -3
  78. package/dist/plugin/dev-reader.js.map +1 -1
  79. package/dist/plugin/interceptors/cache.js.map +1 -1
  80. package/dist/plugin/interceptors/retry.js +10 -2
  81. package/dist/plugin/interceptors/retry.js.map +1 -1
  82. package/dist/plugin/interceptors/telemetry.js +24 -9
  83. package/dist/plugin/interceptors/telemetry.js.map +1 -1
  84. package/dist/plugin/interceptors/timeout.js +4 -0
  85. package/dist/plugin/interceptors/timeout.js.map +1 -1
  86. package/dist/plugin/plugin.d.ts +1 -1
  87. package/dist/plugin/plugin.d.ts.map +1 -1
  88. package/dist/plugin/plugin.js +9 -4
  89. package/dist/plugin/plugin.js.map +1 -1
  90. package/dist/server/index.d.ts.map +1 -1
  91. package/dist/server/index.js +22 -17
  92. package/dist/server/index.js.map +1 -1
  93. package/dist/server/remote-tunnel/remote-tunnel-controller.js +4 -2
  94. package/dist/server/remote-tunnel/remote-tunnel-controller.js.map +1 -1
  95. package/dist/server/remote-tunnel/remote-tunnel-manager.js +10 -8
  96. package/dist/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
  97. package/dist/server/vite-dev-server.js +8 -3
  98. package/dist/server/vite-dev-server.js.map +1 -1
  99. package/dist/stream/arrow-stream-processor.js +13 -6
  100. package/dist/stream/arrow-stream-processor.js.map +1 -1
  101. package/dist/stream/buffers.js +5 -1
  102. package/dist/stream/buffers.js.map +1 -1
  103. package/dist/stream/stream-manager.d.ts.map +1 -1
  104. package/dist/stream/stream-manager.js +47 -36
  105. package/dist/stream/stream-manager.js.map +1 -1
  106. package/dist/stream/types.js.map +1 -1
  107. package/dist/telemetry/index.d.ts +2 -2
  108. package/dist/telemetry/index.js +2 -2
  109. package/dist/telemetry/instrumentations.js +14 -10
  110. package/dist/telemetry/instrumentations.js.map +1 -1
  111. package/dist/telemetry/telemetry-manager.js +8 -6
  112. package/dist/telemetry/telemetry-manager.js.map +1 -1
  113. package/dist/telemetry/trace-sampler.js +33 -0
  114. package/dist/telemetry/trace-sampler.js.map +1 -0
  115. package/dist/type-generator/index.js +4 -2
  116. package/dist/type-generator/index.js.map +1 -1
  117. package/dist/type-generator/query-registry.js +13 -3
  118. package/dist/type-generator/query-registry.js.map +1 -1
  119. package/dist/type-generator/vite-plugin.d.ts.map +1 -1
  120. package/dist/type-generator/vite-plugin.js +5 -3
  121. package/dist/type-generator/vite-plugin.js.map +1 -1
  122. package/dist/utils/env-validator.js +5 -1
  123. package/dist/utils/env-validator.js.map +1 -1
  124. package/dist/utils/path-exclusions.js +66 -0
  125. package/dist/utils/path-exclusions.js.map +1 -0
  126. package/llms.txt +57 -2
  127. package/package.json +4 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/type-generator/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport dotenv from \"dotenv\";\nimport { generateQueriesFromDescribe } from \"./query-registry\";\nimport type { QuerySchema } from \"./types\";\n\ndotenv.config();\n\n/**\n * Generate type declarations for QueryRegistry\n * Create the d.ts file from the plugin routes and query schemas\n * @param querySchemas - the list of query schemas\n * @returns - the type declarations as a string\n */\nfunction generateTypeDeclarations(querySchemas: QuerySchema[] = []): string {\n const queryEntries = querySchemas\n .map(({ name, type }) => {\n const indentedType = type\n .split(\"\\n\")\n .map((line, i) => (i === 0 ? line : ` ${line}`))\n .join(\"\\n\");\n return ` ${name}: ${indentedType}`;\n })\n .join(\";\\n\");\n\n const querySection = queryEntries ? `\\n${queryEntries};\\n ` : \"\";\n\n return `// Auto-generated by AppKit - DO NOT EDIT\n// Generated by 'npx appkit-generate-types' or Vite plugin during build\nimport \"@databricks/appkit-ui/react\";\nimport type { SQLTypeMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLBinaryMarker, SQLDateMarker, SQLTimestampMarker } from \"@databricks/appkit-ui/js\";\n\ndeclare module \"@databricks/appkit-ui/react\" {\n interface QueryRegistry {${querySection}}\n}\n`;\n}\n\n/**\n * Entry point for generating type declarations from all imported files\n * @param options - the options for the generation\n * @param options.entryPoint - the entry point file\n * @param options.outFile - the output file\n * @param options.querySchemaFile - optional path to query schema file (e.g. config/queries/schema.ts)\n */\nexport async function generateFromEntryPoint(options: {\n outFile: string;\n queryFolder?: string;\n warehouseId: string;\n noCache?: boolean;\n}) {\n const { outFile, queryFolder, warehouseId, noCache } = options;\n\n console.log(\"\\n[AppKit] Starting type generation...\\n\");\n\n let queryRegistry: QuerySchema[] = [];\n if (queryFolder)\n queryRegistry = await generateQueriesFromDescribe(\n queryFolder,\n warehouseId,\n {\n noCache,\n },\n );\n\n const typeDeclarations = generateTypeDeclarations(queryRegistry);\n\n fs.writeFileSync(outFile, typeDeclarations, \"utf-8\");\n\n console.log(\"\\n[AppKit] Type generation complete!\\n\");\n}\n"],"mappings":";;;;;AAKA,OAAO,QAAQ;;;;;;;AAQf,SAAS,yBAAyB,eAA8B,EAAE,EAAU;CAC1E,MAAM,eAAe,aAClB,KAAK,EAAE,MAAM,WAAW;AAKvB,SAAO,OAAO,KAAK,IAJE,KAClB,MAAM,KAAK,CACX,KAAK,MAAM,MAAO,MAAM,IAAI,OAAO,OAAO,OAAQ,CAClD,KAAK,KAAK;GAEb,CACD,KAAK,MAAM;AAId,QAAO;;;;;;6BAFc,eAAe,KAAK,aAAa,SAAS,GAQvB;;;;;;;;;;;AAY1C,eAAsB,uBAAuB,SAK1C;CACD,MAAM,EAAE,SAAS,aAAa,aAAa,YAAY;AAEvD,SAAQ,IAAI,2CAA2C;CAEvD,IAAI,gBAA+B,EAAE;AACrC,KAAI,YACF,iBAAgB,MAAM,4BACpB,aACA,aACA,EACE,SACD,CACF;CAEH,MAAM,mBAAmB,yBAAyB,cAAc;AAEhE,IAAG,cAAc,SAAS,kBAAkB,QAAQ;AAEpD,SAAQ,IAAI,yCAAyC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/type-generator/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport dotenv from \"dotenv\";\nimport { createLogger } from \"../logging/logger\";\nimport { generateQueriesFromDescribe } from \"./query-registry\";\nimport type { QuerySchema } from \"./types\";\n\ndotenv.config();\n\nconst logger = createLogger(\"type-generator\");\n\n/**\n * Generate type declarations for QueryRegistry\n * Create the d.ts file from the plugin routes and query schemas\n * @param querySchemas - the list of query schemas\n * @returns - the type declarations as a string\n */\nfunction generateTypeDeclarations(querySchemas: QuerySchema[] = []): string {\n const queryEntries = querySchemas\n .map(({ name, type }) => {\n const indentedType = type\n .split(\"\\n\")\n .map((line, i) => (i === 0 ? line : ` ${line}`))\n .join(\"\\n\");\n return ` ${name}: ${indentedType}`;\n })\n .join(\";\\n\");\n\n const querySection = queryEntries ? `\\n${queryEntries};\\n ` : \"\";\n\n return `// Auto-generated by AppKit - DO NOT EDIT\n// Generated by 'npx appkit-generate-types' or Vite plugin during build\nimport \"@databricks/appkit-ui/react\";\nimport type { SQLTypeMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLBinaryMarker, SQLDateMarker, SQLTimestampMarker } from \"@databricks/appkit-ui/js\";\n\ndeclare module \"@databricks/appkit-ui/react\" {\n interface QueryRegistry {${querySection}}\n}\n`;\n}\n\n/**\n * Entry point for generating type declarations from all imported files\n * @param options - the options for the generation\n * @param options.entryPoint - the entry point file\n * @param options.outFile - the output file\n * @param options.querySchemaFile - optional path to query schema file (e.g. config/queries/schema.ts)\n */\nexport async function generateFromEntryPoint(options: {\n outFile: string;\n queryFolder?: string;\n warehouseId: string;\n noCache?: boolean;\n}) {\n const { outFile, queryFolder, warehouseId, noCache } = options;\n\n logger.debug(\"Starting type generation...\");\n\n let queryRegistry: QuerySchema[] = [];\n if (queryFolder)\n queryRegistry = await generateQueriesFromDescribe(\n queryFolder,\n warehouseId,\n {\n noCache,\n },\n );\n\n const typeDeclarations = generateTypeDeclarations(queryRegistry);\n\n fs.writeFileSync(outFile, typeDeclarations, \"utf-8\");\n\n logger.debug(\"Type generation complete!\");\n}\n"],"mappings":";;;;;;AAMA,OAAO,QAAQ;AAEf,MAAM,SAAS,aAAa,iBAAiB;;;;;;;AAQ7C,SAAS,yBAAyB,eAA8B,EAAE,EAAU;CAC1E,MAAM,eAAe,aAClB,KAAK,EAAE,MAAM,WAAW;AAKvB,SAAO,OAAO,KAAK,IAJE,KAClB,MAAM,KAAK,CACX,KAAK,MAAM,MAAO,MAAM,IAAI,OAAO,OAAO,OAAQ,CAClD,KAAK,KAAK;GAEb,CACD,KAAK,MAAM;AAId,QAAO;;;;;;6BAFc,eAAe,KAAK,aAAa,SAAS,GAQvB;;;;;;;;;;;AAY1C,eAAsB,uBAAuB,SAK1C;CACD,MAAM,EAAE,SAAS,aAAa,aAAa,YAAY;AAEvD,QAAO,MAAM,8BAA8B;CAE3C,IAAI,gBAA+B,EAAE;AACrC,KAAI,YACF,iBAAgB,MAAM,4BACpB,aACA,aACA,EACE,SACD,CACF;CAEH,MAAM,mBAAmB,yBAAyB,cAAc;AAEhE,IAAG,cAAc,SAAS,kBAAkB,QAAQ;AAEpD,QAAO,MAAM,4BAA4B"}
@@ -1,3 +1,4 @@
1
+ import { createLogger } from "../logging/logger.js";
1
2
  import { CACHE_VERSION, hashSQL, loadCache, saveCache } from "./cache.js";
2
3
  import { Spinner } from "./spinner.js";
3
4
  import { sqlTypeToHelper, sqlTypeToMarker } from "./types.js";
@@ -6,6 +7,7 @@ import path from "node:path";
6
7
  import fs from "node:fs";
7
8
 
8
9
  //#region src/type-generator/query-registry.ts
10
+ const logger = createLogger("type-generator:query-registry");
9
11
  /**
10
12
  * Extract parameters from a SQL query
11
13
  * @param sql - the SQL query to extract parameters from
@@ -64,7 +66,7 @@ function extractParameterTypes(sql) {
64
66
  async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {}) {
65
67
  const { noCache = false } = options;
66
68
  const queryFiles = fs.readdirSync(queryFolder).filter((file) => file.endsWith(".sql"));
67
- console.log(` Found ${queryFiles.length} SQL queries\n`);
69
+ logger.debug("Found %d SQL queries", queryFiles.length);
68
70
  const cache = noCache ? {
69
71
  version: CACHE_VERSION,
70
72
  queries: {}
@@ -75,7 +77,7 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
75
77
  const spinner = new Spinner();
76
78
  for (let i = 0; i < queryFiles.length; i++) {
77
79
  const file = queryFiles[i];
78
- const queryName = path.basename(file, ".sql");
80
+ const queryName = normalizeQueryName(path.basename(file, ".sql"));
79
81
  const sql = fs.readFileSync(path.join(queryFolder, file), "utf8");
80
82
  const sqlHash = hashSQL(sql);
81
83
  const cached = cache.queries[queryName];
@@ -123,10 +125,18 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
123
125
  }
124
126
  }
125
127
  saveCache(cache);
126
- if (failedQueries.length > 0) console.warn(` Warning: ${failedQueries.length} queries failed\n`);
128
+ if (failedQueries.length > 0) logger.debug("Warning: %d queries failed", failedQueries.length);
127
129
  return querySchemas;
128
130
  }
129
131
  /**
132
+ * Normalize query name by removing the .obo extension
133
+ * @param queryName - the query name to normalize
134
+ * @returns the normalized query name
135
+ */
136
+ function normalizeQueryName(fileName) {
137
+ return fileName.replace(/\.obo$/, "");
138
+ }
139
+ /**
130
140
  * Normalize SQL type name by removing parameters/generics
131
141
  * Examples:
132
142
  * DECIMAL(38,6) -> DECIMAL
@@ -1 +1 @@
1
- {"version":3,"file":"query-registry.js","names":[],"sources":["../../src/type-generator/query-registry.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport { CACHE_VERSION, hashSQL, loadCache, saveCache } from \"./cache\";\nimport { Spinner } from \"./spinner\";\nimport {\n type DatabricksStatementExecutionResponse,\n type QuerySchema,\n sqlTypeToHelper,\n sqlTypeToMarker,\n} from \"./types\";\n\n/**\n * Extract parameters from a SQL query\n * @param sql - the SQL query to extract parameters from\n * @returns an array of parameter names\n */\nexport function extractParameters(sql: string): string[] {\n const matches = sql.matchAll(/:([a-zA-Z_]\\w*)/g);\n const params = new Set<string>();\n for (const match of matches) {\n params.add(match[1]);\n }\n return Array.from(params);\n}\n\n// parameters that are injected by the server\nexport const SERVER_INJECTED_PARAMS = [\"workspaceId\"];\n\nexport function convertToQueryType(\n result: DatabricksStatementExecutionResponse,\n sql: string,\n queryName: string,\n): string {\n const dataRows = result.result?.data_array || [];\n const columns = dataRows.map((row) => ({\n name: row[0] || \"\",\n type_name: row[1]?.toUpperCase() || \"STRING\",\n comment: row[2] || undefined,\n }));\n\n const params = extractParameters(sql).filter(\n (p) => !SERVER_INJECTED_PARAMS.includes(p),\n );\n\n const paramTypes = extractParameterTypes(sql);\n\n // generate parameters types with JSDoc hints\n const paramsType =\n params.length > 0\n ? `{\\n ${params\n .map((p) => {\n const sqlType = paramTypes[p];\n // if no type annotation, use SQLTypeMarker (union type)\n const markerType = sqlType\n ? sqlTypeToMarker[sqlType]\n : \"SQLTypeMarker\";\n const helper = sqlType ? sqlTypeToHelper[sqlType] : \"sql.*()\";\n return `/** ${sqlType || \"any\"} - use ${helper} */\\n ${p}: ${markerType}`;\n })\n .join(\";\\n \")};\\n }`\n : \"Record<string, never>\";\n\n // generate result fields with JSDoc\n const resultFields = columns.map((column) => {\n const normalizedType = normalizeTypeName(column.type_name);\n const mappedType = typeMap[normalizedType] || \"unknown\";\n // validate column name is a valid identifier\n const name = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(column.name)\n ? column.name\n : `\"${column.name}\"`;\n\n // generate comment for column\n const comment = column.comment\n ? `/** ${column.comment} */\\n `\n : `/** @sqlType ${column.type_name} */\\n `;\n\n return `${comment}${name}: ${mappedType}`;\n });\n\n return `{\n name: \"${queryName}\";\n parameters: ${paramsType};\n result: Array<{\n ${resultFields.join(\";\\n \")};\n }>;\n }`;\n}\n\nexport function extractParameterTypes(sql: string): Record<string, string> {\n const paramTypes: Record<string, string> = {};\n const regex =\n /--\\s*@param\\s+(\\w+)\\s+(STRING|NUMERIC|BOOLEAN|DATE|TIMESTAMP|BINARY)/gi;\n const matches = sql.matchAll(regex);\n for (const match of matches) {\n const [, paramName, paramType] = match;\n paramTypes[paramName] = paramType.toUpperCase();\n }\n\n return paramTypes;\n}\n\n/**\n * Generate query schemas from a folder of SQL files\n * It uses DESCRIBE QUERY to get the schema without executing the query\n * @param queryFolder - the folder containing the SQL files\n * @param warehouseId - the warehouse id to use for schema analysis\n * @param options - options for the query generation\n * @param options.noCache - if true, skip the cache and regenerate all types\n * @returns an array of query schemas\n */\nexport async function generateQueriesFromDescribe(\n queryFolder: string,\n warehouseId: string,\n options: { noCache?: boolean } = {},\n): Promise<QuerySchema[]> {\n const { noCache = false } = options;\n\n // read all query files in the folder\n const queryFiles = fs\n .readdirSync(queryFolder)\n .filter((file) => file.endsWith(\".sql\"));\n\n console.log(` Found ${queryFiles.length} SQL queries\\n`);\n\n // load cache\n const cache = noCache ? { version: CACHE_VERSION, queries: {} } : loadCache();\n\n const client = new WorkspaceClient({});\n const querySchemas: QuerySchema[] = [];\n const failedQueries: { name: string; error: string }[] = [];\n const spinner = new Spinner();\n\n // process each query file\n for (let i = 0; i < queryFiles.length; i++) {\n const file = queryFiles[i];\n const queryName = path.basename(file, \".sql\");\n\n // read query file content\n const sql = fs.readFileSync(path.join(queryFolder, file), \"utf8\");\n const sqlHash = hashSQL(sql);\n\n // check cache\n const cached = cache.queries[queryName];\n if (cached && cached.hash === sqlHash) {\n querySchemas.push({ name: queryName, type: cached.type });\n spinner.start(`Processing ${queryName} (${i + 1}/${queryFiles.length})`);\n spinner.stop(`✓ ${queryName} (cached)`);\n continue;\n }\n\n spinner.start(`Processing ${queryName} (${i + 1}/${queryFiles.length})`);\n\n const sqlWithDefaults = sql.replace(/:([a-zA-Z_]\\w*)/g, \"''\");\n\n // strip trailing semicolon for DESCRIBE QUERY\n const cleanedSql = sqlWithDefaults.trim().replace(/;\\s*$/, \"\");\n\n // execute DESCRIBE QUERY to get schema without running the actual query\n try {\n const result = (await client.statementExecution.executeStatement({\n statement: `DESCRIBE QUERY ${cleanedSql}`,\n warehouse_id: warehouseId,\n })) as DatabricksStatementExecutionResponse;\n\n if (result.status.state === \"FAILED\") {\n spinner.stop(`✗ ${queryName} - failed`);\n failedQueries.push({\n name: queryName,\n error: \"Query execution failed\",\n });\n continue;\n }\n\n // convert result to query schema\n const type = convertToQueryType(result, sql, queryName);\n querySchemas.push({ name: queryName, type });\n\n // update cache\n cache.queries[queryName] = { hash: sqlHash, type };\n\n spinner.stop(`✓ ${queryName}`);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n spinner.stop(`✗ ${queryName} - ${errorMessage}`);\n failedQueries.push({ name: queryName, error: errorMessage });\n }\n }\n\n // save cache\n saveCache(cache);\n\n // log warning if there are failed queries\n if (failedQueries.length > 0) {\n console.warn(` Warning: ${failedQueries.length} queries failed\\n`);\n }\n\n return querySchemas;\n}\n\n/**\n * Normalize SQL type name by removing parameters/generics\n * Examples:\n * DECIMAL(38,6) -> DECIMAL\n * ARRAY<STRING> -> ARRAY\n * MAP<STRING,INT> -> MAP\n * STRUCT<name:STRING> -> STRUCT\n * INTERVAL DAY TO SECOND -> INTERVAL\n * GEOGRAPHY(4326) -> GEOGRAPHY\n */\nexport function normalizeTypeName(typeName: string): string {\n return typeName\n .replace(/\\(.*\\)$/, \"\") // remove (p, s) eg: DECIMAL(38,6) -> DECIMAL\n .replace(/<.*>$/, \"\") // remove <T> eg: ARRAY<STRING> -> ARRAY\n .split(\" \")[0]; // take first word eg: INTERVAL DAY TO SECOND -> INTERVAL\n}\n\n/** Type Map for Databricks data types to JavaScript types */\nconst typeMap: Record<string, string> = {\n // string types\n STRING: \"string\",\n BINARY: \"string\",\n // boolean\n BOOLEAN: \"boolean\",\n // numeric types\n TINYINT: \"number\",\n SMALLINT: \"number\",\n INT: \"number\",\n BIGINT: \"number\",\n FLOAT: \"number\",\n DOUBLE: \"number\",\n DECIMAL: \"number\",\n // date/time types\n DATE: \"string\",\n TIMESTAMP: \"string\",\n TIMESTAMP_NTZ: \"string\",\n INTERVAL: \"string\",\n // complex types\n ARRAY: \"unknown[]\",\n MAP: \"Record<string, unknown>\",\n STRUCT: \"Record<string, unknown>\",\n OBJECT: \"Record<string, unknown>\",\n VARIANT: \"unknown\",\n // spatial types\n GEOGRAPHY: \"unknown\",\n GEOMETRY: \"unknown\",\n // null type\n VOID: \"null\",\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"query-registry.js","names":[],"sources":["../../src/type-generator/query-registry.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport { createLogger } from \"../logging/logger\";\nimport { CACHE_VERSION, hashSQL, loadCache, saveCache } from \"./cache\";\nimport { Spinner } from \"./spinner\";\nimport {\n type DatabricksStatementExecutionResponse,\n type QuerySchema,\n sqlTypeToHelper,\n sqlTypeToMarker,\n} from \"./types\";\n\nconst logger = createLogger(\"type-generator:query-registry\");\n\n/**\n * Extract parameters from a SQL query\n * @param sql - the SQL query to extract parameters from\n * @returns an array of parameter names\n */\nexport function extractParameters(sql: string): string[] {\n const matches = sql.matchAll(/:([a-zA-Z_]\\w*)/g);\n const params = new Set<string>();\n for (const match of matches) {\n params.add(match[1]);\n }\n return Array.from(params);\n}\n\n// parameters that are injected by the server\nexport const SERVER_INJECTED_PARAMS = [\"workspaceId\"];\n\nexport function convertToQueryType(\n result: DatabricksStatementExecutionResponse,\n sql: string,\n queryName: string,\n): string {\n const dataRows = result.result?.data_array || [];\n const columns = dataRows.map((row) => ({\n name: row[0] || \"\",\n type_name: row[1]?.toUpperCase() || \"STRING\",\n comment: row[2] || undefined,\n }));\n\n const params = extractParameters(sql).filter(\n (p) => !SERVER_INJECTED_PARAMS.includes(p),\n );\n\n const paramTypes = extractParameterTypes(sql);\n\n // generate parameters types with JSDoc hints\n const paramsType =\n params.length > 0\n ? `{\\n ${params\n .map((p) => {\n const sqlType = paramTypes[p];\n // if no type annotation, use SQLTypeMarker (union type)\n const markerType = sqlType\n ? sqlTypeToMarker[sqlType]\n : \"SQLTypeMarker\";\n const helper = sqlType ? sqlTypeToHelper[sqlType] : \"sql.*()\";\n return `/** ${sqlType || \"any\"} - use ${helper} */\\n ${p}: ${markerType}`;\n })\n .join(\";\\n \")};\\n }`\n : \"Record<string, never>\";\n\n // generate result fields with JSDoc\n const resultFields = columns.map((column) => {\n const normalizedType = normalizeTypeName(column.type_name);\n const mappedType = typeMap[normalizedType] || \"unknown\";\n // validate column name is a valid identifier\n const name = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(column.name)\n ? column.name\n : `\"${column.name}\"`;\n\n // generate comment for column\n const comment = column.comment\n ? `/** ${column.comment} */\\n `\n : `/** @sqlType ${column.type_name} */\\n `;\n\n return `${comment}${name}: ${mappedType}`;\n });\n\n return `{\n name: \"${queryName}\";\n parameters: ${paramsType};\n result: Array<{\n ${resultFields.join(\";\\n \")};\n }>;\n }`;\n}\n\nexport function extractParameterTypes(sql: string): Record<string, string> {\n const paramTypes: Record<string, string> = {};\n const regex =\n /--\\s*@param\\s+(\\w+)\\s+(STRING|NUMERIC|BOOLEAN|DATE|TIMESTAMP|BINARY)/gi;\n const matches = sql.matchAll(regex);\n for (const match of matches) {\n const [, paramName, paramType] = match;\n paramTypes[paramName] = paramType.toUpperCase();\n }\n\n return paramTypes;\n}\n\n/**\n * Generate query schemas from a folder of SQL files\n * It uses DESCRIBE QUERY to get the schema without executing the query\n * @param queryFolder - the folder containing the SQL files\n * @param warehouseId - the warehouse id to use for schema analysis\n * @param options - options for the query generation\n * @param options.noCache - if true, skip the cache and regenerate all types\n * @returns an array of query schemas\n */\nexport async function generateQueriesFromDescribe(\n queryFolder: string,\n warehouseId: string,\n options: { noCache?: boolean } = {},\n): Promise<QuerySchema[]> {\n const { noCache = false } = options;\n\n // read all query files in the folder\n const queryFiles = fs\n .readdirSync(queryFolder)\n .filter((file) => file.endsWith(\".sql\"));\n\n logger.debug(\"Found %d SQL queries\", queryFiles.length);\n\n // load cache\n const cache = noCache ? { version: CACHE_VERSION, queries: {} } : loadCache();\n\n const client = new WorkspaceClient({});\n const querySchemas: QuerySchema[] = [];\n const failedQueries: { name: string; error: string }[] = [];\n const spinner = new Spinner();\n\n // process each query file\n for (let i = 0; i < queryFiles.length; i++) {\n const file = queryFiles[i];\n const rawName = path.basename(file, \".sql\");\n const queryName = normalizeQueryName(rawName);\n\n // read query file content\n const sql = fs.readFileSync(path.join(queryFolder, file), \"utf8\");\n const sqlHash = hashSQL(sql);\n\n // check cache\n const cached = cache.queries[queryName];\n if (cached && cached.hash === sqlHash) {\n querySchemas.push({ name: queryName, type: cached.type });\n spinner.start(`Processing ${queryName} (${i + 1}/${queryFiles.length})`);\n spinner.stop(`✓ ${queryName} (cached)`);\n continue;\n }\n\n spinner.start(`Processing ${queryName} (${i + 1}/${queryFiles.length})`);\n\n const sqlWithDefaults = sql.replace(/:([a-zA-Z_]\\w*)/g, \"''\");\n\n // strip trailing semicolon for DESCRIBE QUERY\n const cleanedSql = sqlWithDefaults.trim().replace(/;\\s*$/, \"\");\n\n // execute DESCRIBE QUERY to get schema without running the actual query\n try {\n const result = (await client.statementExecution.executeStatement({\n statement: `DESCRIBE QUERY ${cleanedSql}`,\n warehouse_id: warehouseId,\n })) as DatabricksStatementExecutionResponse;\n\n if (result.status.state === \"FAILED\") {\n spinner.stop(`✗ ${queryName} - failed`);\n failedQueries.push({\n name: queryName,\n error: \"Query execution failed\",\n });\n continue;\n }\n\n // convert result to query schema\n const type = convertToQueryType(result, sql, queryName);\n querySchemas.push({ name: queryName, type });\n\n // update cache\n cache.queries[queryName] = { hash: sqlHash, type };\n\n spinner.stop(`✓ ${queryName}`);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n spinner.stop(`✗ ${queryName} - ${errorMessage}`);\n failedQueries.push({ name: queryName, error: errorMessage });\n }\n }\n\n // save cache\n saveCache(cache);\n\n // log warning if there are failed queries\n if (failedQueries.length > 0) {\n logger.debug(\"Warning: %d queries failed\", failedQueries.length);\n }\n\n return querySchemas;\n}\n\n/**\n * Normalize query name by removing the .obo extension\n * @param queryName - the query name to normalize\n * @returns the normalized query name\n */\nexport function normalizeQueryName(fileName: string): string {\n return fileName.replace(/\\.obo$/, \"\");\n}\n\n/**\n * Normalize SQL type name by removing parameters/generics\n * Examples:\n * DECIMAL(38,6) -> DECIMAL\n * ARRAY<STRING> -> ARRAY\n * MAP<STRING,INT> -> MAP\n * STRUCT<name:STRING> -> STRUCT\n * INTERVAL DAY TO SECOND -> INTERVAL\n * GEOGRAPHY(4326) -> GEOGRAPHY\n */\nexport function normalizeTypeName(typeName: string): string {\n return typeName\n .replace(/\\(.*\\)$/, \"\") // remove (p, s) eg: DECIMAL(38,6) -> DECIMAL\n .replace(/<.*>$/, \"\") // remove <T> eg: ARRAY<STRING> -> ARRAY\n .split(\" \")[0]; // take first word eg: INTERVAL DAY TO SECOND -> INTERVAL\n}\n\n/** Type Map for Databricks data types to JavaScript types */\nconst typeMap: Record<string, string> = {\n // string types\n STRING: \"string\",\n BINARY: \"string\",\n // boolean\n BOOLEAN: \"boolean\",\n // numeric types\n TINYINT: \"number\",\n SMALLINT: \"number\",\n INT: \"number\",\n BIGINT: \"number\",\n FLOAT: \"number\",\n DOUBLE: \"number\",\n DECIMAL: \"number\",\n // date/time types\n DATE: \"string\",\n TIMESTAMP: \"string\",\n TIMESTAMP_NTZ: \"string\",\n INTERVAL: \"string\",\n // complex types\n ARRAY: \"unknown[]\",\n MAP: \"Record<string, unknown>\",\n STRUCT: \"Record<string, unknown>\",\n OBJECT: \"Record<string, unknown>\",\n VARIANT: \"unknown\",\n // spatial types\n GEOGRAPHY: \"unknown\",\n GEOMETRY: \"unknown\",\n // null type\n VOID: \"null\",\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.d.ts","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"sourcesContent":[],"mappings":";;;;;;AACmC;AAoBnC,UAbU,wBAAA,CAauB;EAAA,OAAA,CAAA,EAAA,MAAA;;cAAsC,CAAA,EAAA,MAAA,EAAA;;;;;;;;iBAAvD,iBAAA,WAA4B,2BAA2B"}
1
+ {"version":3,"file":"vite-plugin.d.ts","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"sourcesContent":[],"mappings":";;;;;;AAEmC;AAsBnC,UAbU,wBAAA,CAauB;EAAA,OAAA,CAAA,EAAA,MAAA;;cAAsC,CAAA,EAAA,MAAA,EAAA;;;;;;;;iBAAvD,iBAAA,WAA4B,2BAA2B"}
@@ -1,8 +1,10 @@
1
+ import { createLogger } from "../logging/logger.js";
1
2
  import { generateFromEntryPoint } from "./index.js";
2
3
  import path from "node:path";
3
4
  import fs from "node:fs";
4
5
 
5
6
  //#region src/type-generator/vite-plugin.ts
7
+ const logger = createLogger("type-generator:vite-plugin");
6
8
  /**
7
9
  * Vite plugin to generate types for AppKit queries.
8
10
  * Calls generateFromEntryPoint under the hood.
@@ -17,7 +19,7 @@ function appKitTypesPlugin(options) {
17
19
  try {
18
20
  const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || "";
19
21
  if (!warehouseId) {
20
- console.warn("[AppKit] Warehouse ID not found. Skipping type generation.");
22
+ logger.debug("Warehouse ID not found. Skipping type generation.");
21
23
  return;
22
24
  }
23
25
  await generateFromEntryPoint({
@@ -28,14 +30,14 @@ function appKitTypesPlugin(options) {
28
30
  });
29
31
  } catch (error) {
30
32
  if (process.env.NODE_ENV === "production") throw error;
31
- console.error("[AppKit] Error generating types:", error);
33
+ logger.error("Error generating types: %O", error);
32
34
  }
33
35
  }
34
36
  return {
35
37
  name: "appkit-types",
36
38
  apply() {
37
39
  if (!(process.env.DATABRICKS_WAREHOUSE_ID || "")) {
38
- console.warn("[AppKit] Warehouse ID not found. Skipping type generation.");
40
+ logger.debug("Warehouse ID not found. Skipping type generation.");
39
41
  return false;
40
42
  }
41
43
  if (!fs.existsSync(path.join(process.cwd(), "config", "queries"))) return false;
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.js","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport fs from \"node:fs\";\nimport { generateFromEntryPoint } from \"./index\";\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 console.warn(\n \"[AppKit] Warehouse ID not found. Skipping type generation.\",\n );\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 console.error(\"[AppKit] Error generating types:\", 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 console.warn(\n \"[AppKit] Warehouse ID not found. Skipping type generation.\",\n );\n return false;\n }\n\n if (!fs.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":";;;;;;;;;;;AAqBA,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,YAAQ,KACN,6DACD;AACD;;AAGF,SAAM,uBAAuB;IAC3B;IACA,aAAa,aAAa;IAC1B;IACA,SAAS;IACV,CAAC;WACK,OAAO;AAEd,OAAI,QAAQ,IAAI,aAAa,aAC3B,OAAM;AAER,WAAQ,MAAM,oCAAoC,MAAM;;;AAI5D,QAAO;EACL,MAAM;EAEN,QAAQ;AAGN,OAAI,EAFgB,QAAQ,IAAI,2BAA2B,KAEzC;AAChB,YAAQ,KACN,6DACD;AACD,WAAO;;AAGT,OAAI,CAAC,GAAG,WAAW,KAAK,KAAK,QAAQ,KAAK,EAAE,UAAU,UAAU,CAAC,CAC/D,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 fs 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 (!fs.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,GAAG,WAAW,KAAK,KAAK,QAAQ,KAAK,EAAE,UAAU,UAAU,CAAC,CAC/D,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,8 +1,12 @@
1
+ import { ValidationError } from "../errors/validation.js";
2
+ import { init_errors } from "../errors/index.js";
3
+
1
4
  //#region src/utils/env-validator.ts
5
+ init_errors();
2
6
  function validateEnv(envVars) {
3
7
  const missingVars = [];
4
8
  for (const envVar of envVars) if (!process.env[envVar]) missingVars.push(envVar);
5
- if (missingVars.length > 0) throw new Error(`Missing required environment variables: ${missingVars.join(", ")}`);
9
+ if (missingVars.length > 0) throw ValidationError.missingEnvVars(missingVars);
6
10
  }
7
11
 
8
12
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"env-validator.js","names":[],"sources":["../../src/utils/env-validator.ts"],"sourcesContent":["export function validateEnv(envVars: string[]) {\n const missingVars = [];\n\n for (const envVar of envVars) {\n if (!process.env[envVar]) {\n missingVars.push(envVar);\n }\n }\n\n if (missingVars.length > 0) {\n throw new Error(\n `Missing required environment variables: ${missingVars.join(\", \")}`,\n );\n }\n}\n"],"mappings":";AAAA,SAAgB,YAAY,SAAmB;CAC7C,MAAM,cAAc,EAAE;AAEtB,MAAK,MAAM,UAAU,QACnB,KAAI,CAAC,QAAQ,IAAI,QACf,aAAY,KAAK,OAAO;AAI5B,KAAI,YAAY,SAAS,EACvB,OAAM,IAAI,MACR,2CAA2C,YAAY,KAAK,KAAK,GAClE"}
1
+ {"version":3,"file":"env-validator.js","names":[],"sources":["../../src/utils/env-validator.ts"],"sourcesContent":["import { ValidationError } from \"../errors\";\n\nexport function validateEnv(envVars: string[]) {\n const missingVars = [];\n\n for (const envVar of envVars) {\n if (!process.env[envVar]) {\n missingVars.push(envVar);\n }\n }\n\n if (missingVars.length > 0) {\n throw ValidationError.missingEnvVars(missingVars);\n }\n}\n"],"mappings":";;;;aAA4C;AAE5C,SAAgB,YAAY,SAAmB;CAC7C,MAAM,cAAc,EAAE;AAEtB,MAAK,MAAM,UAAU,QACnB,KAAI,CAAC,QAAQ,IAAI,QACf,aAAY,KAAK,OAAO;AAI5B,KAAI,YAAY,SAAS,EACvB,OAAM,gBAAgB,eAAe,YAAY"}
@@ -0,0 +1,66 @@
1
+ //#region src/utils/path-exclusions.ts
2
+ /**
3
+ * Paths and patterns to exclude from tracing and logging.
4
+ * Requests matching these will not create spans or WideEvents.
5
+ */
6
+ const EXCLUDED_PATH_PREFIXES = [
7
+ "/@fs/",
8
+ "/@vite/",
9
+ "/@id/",
10
+ "/@react-refresh",
11
+ "/src/",
12
+ "/node_modules/",
13
+ "/favicon.ico",
14
+ "/_next/",
15
+ "/static/",
16
+ "/health",
17
+ "/metrics"
18
+ ];
19
+ /**
20
+ * File extensions to exclude from tracing.
21
+ * These are typically static assets that don't need tracing.
22
+ */
23
+ const EXCLUDED_EXTENSIONS = [
24
+ ".svg",
25
+ ".png",
26
+ ".jpg",
27
+ ".jpeg",
28
+ ".gif",
29
+ ".webp",
30
+ ".ico",
31
+ ".css",
32
+ ".woff",
33
+ ".woff2",
34
+ ".ttf",
35
+ ".eot",
36
+ ".map",
37
+ ".js"
38
+ ];
39
+ /**
40
+ * Check if a request should be ignored for tracing.
41
+ * This is the primary filter used by HttpInstrumentation.
42
+ */
43
+ function shouldIgnoreRequest(request) {
44
+ const url = request.url;
45
+ if (!url) return false;
46
+ const path = url.split("?")[0];
47
+ return shouldExcludePath(path);
48
+ }
49
+ /**
50
+ * Check if a path should be excluded from tracing/logging.
51
+ * Returns true if path should be excluded, false otherwise.
52
+ */
53
+ function shouldExcludePath(path) {
54
+ if (typeof path !== "string") return false;
55
+ const cleanPath = path.split("?")[0];
56
+ const lowerPath = cleanPath.toLowerCase();
57
+ for (const prefix of EXCLUDED_PATH_PREFIXES) if (cleanPath.includes(prefix)) return true;
58
+ if (!cleanPath.startsWith("/api/")) {
59
+ for (const ext of EXCLUDED_EXTENSIONS) if (lowerPath.endsWith(ext)) return true;
60
+ }
61
+ return false;
62
+ }
63
+
64
+ //#endregion
65
+ export { shouldExcludePath, shouldIgnoreRequest };
66
+ //# sourceMappingURL=path-exclusions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-exclusions.js","names":[],"sources":["../../src/utils/path-exclusions.ts"],"sourcesContent":["import type { IncomingMessage } from \"node:http\";\n\n/**\n * Paths and patterns to exclude from tracing and logging.\n * Requests matching these will not create spans or WideEvents.\n */\nexport const EXCLUDED_PATH_PREFIXES = [\n // Vite dev server internals\n \"/@fs/\",\n \"/@vite/\",\n \"/@id/\",\n \"/@react-refresh\",\n \"/src/\", // Vite HMR source files\n \"/node_modules/\",\n\n // Static assets and common paths\n \"/favicon.ico\",\n \"/_next/\",\n \"/static/\",\n\n // Health checks\n \"/health\",\n \"/metrics\",\n];\n\n/**\n * File extensions to exclude from tracing.\n * These are typically static assets that don't need tracing.\n */\nexport const EXCLUDED_EXTENSIONS = [\n \".svg\",\n \".png\",\n \".jpg\",\n \".jpeg\",\n \".gif\",\n \".webp\",\n \".ico\",\n \".css\",\n \".woff\",\n \".woff2\",\n \".ttf\",\n \".eot\",\n \".map\", // Source maps\n \".js\", // Static JS files (not API endpoints)\n];\n\n/**\n * Check if a request should be ignored for tracing.\n * This is the primary filter used by HttpInstrumentation.\n */\nexport function shouldIgnoreRequest(request: IncomingMessage): boolean {\n const url = request.url;\n if (!url) return false;\n\n // Remove query string for path matching\n const path = url.split(\"?\")[0];\n\n return shouldExcludePath(path);\n}\n\n/**\n * Check if a path should be excluded from tracing/logging.\n * Returns true if path should be excluded, false otherwise.\n */\nexport function shouldExcludePath(path: string | undefined): boolean {\n if (typeof path !== \"string\") return false;\n\n // Remove query string\n const cleanPath = path.split(\"?\")[0];\n const lowerPath = cleanPath.toLowerCase();\n\n // Check path prefixes\n for (const prefix of EXCLUDED_PATH_PREFIXES) {\n if (cleanPath.includes(prefix)) {\n return true;\n }\n }\n\n // Check file extensions (but not for /api/ routes)\n if (!cleanPath.startsWith(\"/api/\")) {\n for (const ext of EXCLUDED_EXTENSIONS) {\n if (lowerPath.endsWith(ext)) {\n return true;\n }\n }\n }\n\n return false;\n}\n"],"mappings":";;;;;AAMA,MAAa,yBAAyB;CAEpC;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACD;;;;;AAMD,MAAa,sBAAsB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;AAMD,SAAgB,oBAAoB,SAAmC;CACrE,MAAM,MAAM,QAAQ;AACpB,KAAI,CAAC,IAAK,QAAO;CAGjB,MAAM,OAAO,IAAI,MAAM,IAAI,CAAC;AAE5B,QAAO,kBAAkB,KAAK;;;;;;AAOhC,SAAgB,kBAAkB,MAAmC;AACnE,KAAI,OAAO,SAAS,SAAU,QAAO;CAGrC,MAAM,YAAY,KAAK,MAAM,IAAI,CAAC;CAClC,MAAM,YAAY,UAAU,aAAa;AAGzC,MAAK,MAAM,UAAU,uBACnB,KAAI,UAAU,SAAS,OAAO,CAC5B,QAAO;AAKX,KAAI,CAAC,UAAU,WAAW,QAAQ,EAChC;OAAK,MAAM,OAAO,oBAChB,KAAI,UAAU,SAAS,IAAI,CACzB,QAAO;;AAKb,QAAO"}
package/llms.txt CHANGED
@@ -431,9 +431,12 @@ WHERE workspace_id = :workspaceId
431
431
  HTTP endpoints exposed (mounted under `/api/analytics`):
432
432
 
433
433
  - `POST /api/analytics/query/:query_key`
434
- - `POST /api/analytics/users/me/query/:query_key`
435
434
  - `GET /api/analytics/arrow-result/:jobId`
436
- - `GET /api/analytics/users/me/arrow-result/:jobId`
435
+
436
+ **Query file naming convention determines execution context:**
437
+
438
+ - `config/queries/<query_key>.sql` - Executes as service principal (shared cache)
439
+ - `config/queries/<query_key>.obo.sql` - Executes as user (OBO = On-Behalf-Of, per-user cache)
437
440
 
438
441
  Formats:
439
442
 
@@ -668,6 +671,56 @@ export function SpendChart() {
668
671
  }
669
672
  ```
670
673
 
674
+ **Chart props reference (important):**
675
+
676
+ Charts are **self-contained ECharts components**. Configure via props, NOT children:
677
+
678
+ ```tsx
679
+ // ✅ Correct: use props for customization
680
+ <BarChart
681
+ queryKey="sales_by_region"
682
+ parameters={{}}
683
+ xKey="region" // X-axis field
684
+ yKey={["revenue", "expenses"]} // Y-axis field(s) - string or string[]
685
+ colors={['#40d1f5', '#4462c9']} // Custom colors
686
+ stacked // Stack bars (BarChart, AreaChart)
687
+ orientation="horizontal" // "vertical" (default) | "horizontal"
688
+ showLegend // Show legend
689
+ height={400} // Height in pixels (default: 300)
690
+ />
691
+
692
+ <LineChart
693
+ queryKey="trend_data"
694
+ parameters={{}}
695
+ xKey="date"
696
+ yKey="value"
697
+ smooth // Smooth curves (default: true)
698
+ showSymbol={false} // Hide data point markers
699
+ />
700
+ ```
701
+
702
+ **❌ CRITICAL: Charts do NOT accept Recharts children**
703
+
704
+ ```tsx
705
+ // ❌ WRONG - AppKit charts are NOT Recharts wrappers
706
+ import { BarChart } from "@databricks/appkit-ui/react";
707
+ import { Bar, XAxis, YAxis, CartesianGrid } from "recharts";
708
+
709
+ <BarChart queryKey="data" parameters={{}}>
710
+ <CartesianGrid /> // ❌ This will cause TypeScript errors
711
+ <XAxis dataKey="x" /> // ❌ Not supported
712
+ <Bar dataKey="y" /> // ❌ Not supported
713
+ </BarChart>
714
+
715
+ // ✅ CORRECT - use props instead
716
+ <BarChart
717
+ queryKey="data"
718
+ parameters={{}}
719
+ xKey="x"
720
+ yKey="y"
721
+ />
722
+ ```
723
+
671
724
  ### SQL helpers (`sql.*`)
672
725
 
673
726
  Use these to build typed parameters (they return marker objects: `{ __sql_type, value }`):
@@ -1169,6 +1222,7 @@ env:
1169
1222
  - `useMemo` wraps parameters objects
1170
1223
  - Loading/error/empty states are explicit
1171
1224
  - Charts use `format="auto"` unless you have a reason to force `"json"`/`"arrow"`
1225
+ - Charts use props (`xKey`, `yKey`, `colors`) NOT children (they're ECharts-based, not Recharts)
1172
1226
  - If using tooltips: root is wrapped with `<TooltipProvider>`
1173
1227
 
1174
1228
  - **Never**
@@ -1176,4 +1230,5 @@ env:
1176
1230
  - Don't pass untyped raw params for annotated queries
1177
1231
  - Don't ignore `createApp()`'s promise
1178
1232
  - Don't invent UI components not listed in this file
1233
+ - Don't pass Recharts children (`<Bar>`, `<XAxis>`, etc.) to AppKit chart components
1179
1234
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@databricks/appkit",
3
3
  "type": "module",
4
- "version": "0.1.5",
4
+ "version": "0.3.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "packageManager": "pnpm@10.21.0",
@@ -26,6 +26,7 @@
26
26
  },
27
27
  "bin": {
28
28
  "appkit-generate-types": "./bin/generate-types.js",
29
+ "appkit-lint": "./bin/appkit-lint.js",
29
30
  "appkit-setup": "./bin/setup-claude.js"
30
31
  },
31
32
  "scripts": {
@@ -39,6 +40,7 @@
39
40
  "postinstall": "node scripts/postinstall.js"
40
41
  },
41
42
  "dependencies": {
43
+ "@ast-grep/napi": "^0.37.0",
42
44
  "@databricks/sdk-experimental": "^0.15.0",
43
45
  "@opentelemetry/api": "^1.9.0",
44
46
  "@opentelemetry/api-logs": "^0.208.0",
@@ -57,6 +59,7 @@
57
59
  "@types/semver": "^7.7.1",
58
60
  "dotenv": "^16.6.1",
59
61
  "express": "^4.22.0",
62
+ "obug": "^2.1.1",
60
63
  "pg": "^8.16.3",
61
64
  "semver": "^7.7.3",
62
65
  "vite": "npm:rolldown-vite@7.1.14",