@databricks/appkit 0.33.0 → 0.34.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.
@@ -1 +1 @@
1
- {"version":3,"file":"query-registry.js","names":[],"sources":["../../src/type-generator/query-registry.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 { 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 * Regex breakdown:\n * '(?:[^']|'')*' — matches a SQL string literal, including escaped '' pairs\n * | — alternation: whichever branch matches first at a position wins\n * --[^\\n]* — matches a single-line SQL comment\n *\n * Because the regex engine scans left-to-right, a `'` is consumed as a string\n * literal before any `--` inside it could match as a comment — giving us\n * correct single-pass ordering without a manual state machine.\n *\n * V1: no block-comment support (deferred to next PR).\n */\nconst PROTECTED_RANGE_RE = /'(?:[^']|'')*'|--[^\\n]*/g;\n\n/**\n * Numeric-context patterns for positional type inference.\n * Hoisted to module scope — safe because matchAll() clones the regex internally.\n */\nconst NUMERIC_PATTERNS: RegExp[] = [\n /\\bLIMIT\\s+:([a-zA-Z_]\\w*)/gi,\n /\\bOFFSET\\s+:([a-zA-Z_]\\w*)/gi,\n /\\bTOP\\s+:([a-zA-Z_]\\w*)/gi,\n /\\bFETCH\\s+FIRST\\s+:([a-zA-Z_]\\w*)\\s+ROWS/gi,\n // V1 limitation: arithmetic operators may false-positive for date\n // expressions like `:start_date - INTERVAL '1 day'`. A smarter\n // heuristic (e.g. look-ahead for INTERVAL) is deferred to a future PR.\n /[+\\-*/]\\s*:([a-zA-Z_]\\w*)/g,\n /:([a-zA-Z_]\\w*)\\s*[+\\-*/]/g,\n];\n\nexport function getProtectedRanges(sql: string): Array<[number, number]> {\n const ranges: Array<[number, number]> = [];\n for (const m of sql.matchAll(PROTECTED_RANGE_RE)) {\n ranges.push([m.index, m.index + m[0].length]);\n }\n return ranges;\n}\n\nfunction isInsideProtectedRange(\n offset: number,\n ranges: Array<[number, number]>,\n): boolean {\n return ranges.some(([start, end]) => offset >= start && offset < end);\n}\n\n/**\n * Parse a raw API/SDK error into a structured code + message.\n * Handles Databricks-style JSON bodies embedded in the message string,\n * e.g. `Response from server (Bad Request) {\"error_code\":\"...\",\"message\":\"...\"}`.\n */\nfunction parseError(raw: string): { code?: string; message: string } {\n const jsonMatch = raw.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n if (parsed.error_code || parsed.message) {\n return {\n code: parsed.error_code,\n message: parsed.message || raw,\n };\n }\n } catch {\n // not valid JSON, fall through\n }\n }\n return { message: raw };\n}\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(\n sql: string,\n ranges?: Array<[number, number]>,\n): string[] {\n const protectedRanges = ranges ?? getProtectedRanges(sql);\n const matches = sql.matchAll(/(?<!:):([a-zA-Z_]\\w*)/g);\n const params = new Set<string>();\n for (const match of matches) {\n if (!isInsideProtectedRange(match.index, protectedRanges)) {\n params.add(match[1]);\n }\n }\n return Array.from(params);\n}\n\n// parameters that are injected by the server\nexport const SERVER_INJECTED_PARAMS = [\"workspaceId\"];\n\n/**\n * Generates the TypeScript type literal for query parameters from SQL.\n * Shared by both the success and failure paths.\n */\nfunction formatParametersType(sql: string): string {\n const params = extractParameters(sql).filter(\n (p) => !SERVER_INJECTED_PARAMS.includes(p),\n );\n const paramTypes = extractParameterTypes(sql);\n\n return params.length > 0\n ? `{\\n ${params\n .map((p) => {\n const sqlType = paramTypes[p];\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\nexport function convertToQueryType(\n result: DatabricksStatementExecutionResponse,\n sql: string,\n queryName: string,\n): { type: string; hasResults: boolean } {\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 paramsType = formatParametersType(sql);\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 const hasResults = resultFields.length > 0;\n\n const type = `{\n name: \"${queryName}\";\n parameters: ${paramsType};\n result: ${\n hasResults\n ? `Array<{\n ${resultFields.join(\";\\n \")};\n }>`\n : \"unknown\"\n };\n }`;\n\n return { type, hasResults };\n}\n\n/**\n * Used when DESCRIBE QUERY fails so the query still appears in QueryRegistry.\n * Generates a type with unknown result from SQL alone (no warehouse call).\n */\nfunction generateUnknownResultQuery(sql: string, queryName: string): string {\n const paramsType = formatParametersType(sql);\n\n return `{\n name: \"${queryName}\";\n parameters: ${paramsType};\n result: unknown;\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\nexport function defaultForType(sqlType: string | undefined): string {\n switch (sqlType?.toUpperCase()) {\n case \"NUMERIC\":\n return \"0\";\n case \"STRING\":\n return \"''\";\n case \"BOOLEAN\":\n return \"true\";\n case \"DATE\":\n return \"'2000-01-01'\";\n case \"TIMESTAMP\":\n return \"'2000-01-01T00:00:00Z'\";\n case \"BINARY\":\n return \"X'00'\";\n default:\n return \"''\";\n }\n}\n\n/**\n * Infer parameter types from positional context in SQL.\n * V1 only infers NUMERIC from patterns like LIMIT, OFFSET, TOP,\n * FETCH FIRST ... ROWS, and arithmetic operators.\n * Parameters inside string literals or SQL comments are ignored.\n */\nexport function inferParameterTypes(\n sql: string,\n ranges?: Array<[number, number]>,\n): Record<string, string> {\n const inferred: Record<string, string> = {};\n const protectedRanges = ranges ?? getProtectedRanges(sql);\n\n for (const pattern of NUMERIC_PATTERNS) {\n for (const match of sql.matchAll(pattern)) {\n if (!isInsideProtectedRange(match.index, protectedRanges)) {\n inferred[match[1]] = \"NUMERIC\";\n }\n }\n }\n\n return inferred;\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; concurrency?: number } = {},\n): Promise<QuerySchema[]> {\n const { noCache = false, concurrency: rawConcurrency = 10 } = options;\n const concurrency =\n typeof rawConcurrency === \"number\" && Number.isFinite(rawConcurrency)\n ? Math.max(1, Math.floor(rawConcurrency))\n : 10;\n\n // read all query files and cache in parallel\n const [allFiles, cache] = await Promise.all([\n fs.readdir(queryFolder),\n noCache\n ? ({ version: CACHE_VERSION, queries: {} } as Awaited<\n ReturnType<typeof loadCache>\n >)\n : loadCache(),\n ]);\n\n const queryFiles = allFiles.filter((file) => file.endsWith(\".sql\"));\n logger.debug(\"Found %d SQL queries\", queryFiles.length);\n\n const client = new WorkspaceClient({});\n const spinner = new Spinner();\n\n // Read all SQL files in parallel\n const sqlContents = await Promise.all(\n queryFiles.map((file) => fs.readFile(path.join(queryFolder, file), \"utf8\")),\n );\n\n const startTime = performance.now();\n\n // Phase 1: Check cache, separate cached vs uncached\n const cachedResults: Array<{ index: number; schema: QuerySchema }> = [];\n const uncachedQueries: Array<{\n index: number;\n queryName: string;\n sql: string;\n sqlHash: string;\n cleanedSql: string;\n }> = [];\n const logEntries: Array<{\n queryName: string;\n status: \"HIT\" | \"MISS\";\n failed?: boolean;\n error?: { code?: string; message: string };\n }> = [];\n\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 const sql = sqlContents[i];\n const sqlHash = hashSQL(sql);\n\n const cached = cache.queries[queryName];\n if (cached && cached.hash === sqlHash && !cached.retry) {\n cachedResults.push({\n index: i,\n schema: { name: queryName, type: cached.type },\n });\n logEntries.push({ queryName, status: \"HIT\" });\n } else {\n const protectedRanges = getProtectedRanges(sql);\n const annotatedTypes = extractParameterTypes(sql);\n const inferredTypes = inferParameterTypes(sql, protectedRanges);\n const parameterTypes = { ...inferredTypes, ...annotatedTypes };\n const sqlWithDefaults = sql.replace(\n /(?<!:):([a-zA-Z_]\\w*)/g,\n (original, paramName, offset) => {\n if (isInsideProtectedRange(offset, protectedRanges)) {\n return original;\n }\n return defaultForType(parameterTypes[paramName]);\n },\n );\n\n // Warn about unresolved parameters\n const allParams = extractParameters(sql, protectedRanges);\n for (const param of allParams) {\n if (SERVER_INJECTED_PARAMS.includes(param)) continue;\n if (parameterTypes[param]) continue;\n logger.warn(\n '%s: parameter \":%s\" has no type annotation or inference. Add %s to the query file.',\n queryFiles[i],\n param,\n `-- @param ${param} <TYPE>`,\n );\n }\n\n const cleanedSql = sqlWithDefaults.trim().replace(/;\\s*$/, \"\");\n uncachedQueries.push({ index: i, queryName, sql, sqlHash, cleanedSql });\n }\n }\n\n // Phase 2: Execute all uncached DESCRIBE calls in parallel\n type DescribeResult =\n | {\n status: \"ok\";\n index: number;\n schema: QuerySchema;\n cacheEntry: { hash: string; type: string; retry: boolean };\n }\n | {\n status: \"fail\";\n index: number;\n schema: QuerySchema;\n cacheEntry: { hash: string; type: string; retry: boolean };\n error: { code?: string; message: string };\n };\n\n const freshResults: Array<{ index: number; schema: QuerySchema }> = [];\n\n if (uncachedQueries.length > 0) {\n let completed = 0;\n const total = uncachedQueries.length;\n spinner.start(\n `Describing ${total} ${total === 1 ? \"query\" : \"queries\"} (0/${total})`,\n );\n\n const describeOne = async ({\n index,\n queryName,\n sql,\n sqlHash,\n cleanedSql,\n }: (typeof uncachedQueries)[number]): Promise<DescribeResult> => {\n const result = (await client.statementExecution.executeStatement({\n statement: `DESCRIBE QUERY ${cleanedSql}`,\n warehouse_id: warehouseId,\n })) as DatabricksStatementExecutionResponse;\n\n completed++;\n spinner.update(\n `Describing ${total} ${total === 1 ? \"query\" : \"queries\"} (${completed}/${total})`,\n );\n\n logger.debug(\n \"DESCRIBE result for %s: state=%s, rows=%d\",\n queryName,\n result.status.state,\n result.result?.data_array?.length ?? 0,\n );\n\n if (result.status.state === \"FAILED\") {\n const sqlError =\n result.status.error?.message || \"Query execution failed\";\n logger.warn(\"DESCRIBE failed for %s: %s\", queryName, sqlError);\n const type = generateUnknownResultQuery(sql, queryName);\n return {\n status: \"fail\",\n index,\n schema: { name: queryName, type },\n cacheEntry: { hash: sqlHash, type, retry: true },\n error: parseError(sqlError),\n };\n }\n\n const { type, hasResults } = convertToQueryType(result, sql, queryName);\n return {\n status: \"ok\",\n index,\n schema: { name: queryName, type },\n cacheEntry: { hash: sqlHash, type, retry: !hasResults },\n };\n };\n\n // Process in chunks, saving cache after each chunk\n const processBatchResults = (\n settled: PromiseSettledResult<DescribeResult>[],\n batchOffset: number,\n ) => {\n for (let i = 0; i < settled.length; i++) {\n const entry = settled[i];\n const { queryName } = uncachedQueries[batchOffset + i];\n\n if (entry.status === \"fulfilled\") {\n const res = entry.value;\n freshResults.push({ index: res.index, schema: res.schema });\n cache.queries[queryName] = res.cacheEntry;\n logEntries.push({\n queryName,\n status: \"MISS\",\n failed: res.status === \"fail\",\n error: res.status === \"fail\" ? res.error : undefined,\n });\n } else {\n const { sql, sqlHash, index } = uncachedQueries[batchOffset + i];\n const reason =\n entry.reason instanceof Error\n ? entry.reason.message\n : String(entry.reason);\n logger.warn(\"DESCRIBE rejected for %s: %s\", queryName, reason);\n const type = generateUnknownResultQuery(sql, queryName);\n freshResults.push({ index, schema: { name: queryName, type } });\n cache.queries[queryName] = { hash: sqlHash, type, retry: true };\n logEntries.push({\n queryName,\n status: \"MISS\",\n failed: true,\n error: parseError(reason),\n });\n }\n }\n };\n\n if (uncachedQueries.length > concurrency) {\n for (let b = 0; b < uncachedQueries.length; b += concurrency) {\n const batch = uncachedQueries.slice(b, b + concurrency);\n const batchResults = await Promise.allSettled(batch.map(describeOne));\n processBatchResults(batchResults, b);\n await saveCache(cache);\n }\n } else {\n const settled = await Promise.allSettled(\n uncachedQueries.map(describeOne),\n );\n processBatchResults(settled, 0);\n await saveCache(cache);\n }\n\n spinner.stop(\"\");\n }\n\n const elapsed = ((performance.now() - startTime) / 1000).toFixed(2);\n\n // Print formatted table\n if (logEntries.length > 0) {\n const maxNameLen = Math.max(...logEntries.map((e) => e.queryName.length));\n const separator = pc.dim(\"─\".repeat(50));\n console.log(\"\");\n console.log(\n ` ${pc.bold(\"Typegen Queries\")} ${pc.dim(`(${logEntries.length})`)}`,\n );\n console.log(` ${separator}`);\n for (const entry of logEntries) {\n const tag = entry.failed\n ? pc.bold(pc.red(\"ERROR\"))\n : entry.status === \"HIT\"\n ? `cache ${pc.bold(pc.green(\"HIT \"))}`\n : `cache ${pc.bold(pc.yellow(\"MISS \"))}`;\n const rawName = entry.queryName.padEnd(maxNameLen);\n const name = entry.failed ? pc.dim(pc.strikethrough(rawName)) : rawName;\n const errorCode = entry.error?.message.match(/\\[([^\\]]+)\\]/)?.[1];\n const reason = errorCode ? ` ${pc.dim(errorCode)}` : \"\";\n console.log(` ${tag} ${name}${reason}`);\n }\n const newCount = logEntries.filter(\n (e) => e.status === \"MISS\" && !e.failed,\n ).length;\n const cacheCount = logEntries.filter(\n (e) => e.status === \"HIT\" && !e.failed,\n ).length;\n const errorCount = logEntries.filter((e) => e.failed).length;\n console.log(` ${separator}`);\n const parts = [`${newCount} new`, `${cacheCount} from cache`];\n if (errorCount > 0)\n parts.push(`${errorCount} ${errorCount === 1 ? \"error\" : \"errors\"}`);\n console.log(` ${parts.join(\", \")}. ${pc.dim(`${elapsed}s`)}`);\n console.log(\"\");\n }\n\n // Merge and sort by original file index for deterministic output\n return [...cachedResults, ...freshResults]\n .sort((a, b) => a.index - b.index)\n .map((r) => r.schema);\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 */\nfunction 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
+ {"version":3,"file":"query-registry.js","names":[],"sources":["../../src/type-generator/query-registry.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 { 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 * Regex breakdown:\n * '(?:[^']|'')*' — matches a SQL string literal, including escaped '' pairs\n * | — alternation: whichever branch matches first at a position wins\n * --[^\\n]* — matches a single-line SQL comment\n *\n * Because the regex engine scans left-to-right, a `'` is consumed as a string\n * literal before any `--` inside it could match as a comment — giving us\n * correct single-pass ordering without a manual state machine.\n *\n * V1: no block-comment support (deferred to next PR).\n */\nconst PROTECTED_RANGE_RE = /'(?:[^']|'')*'|--[^\\n]*/g;\n\n/**\n * Numeric-context patterns for positional type inference.\n * Hoisted to module scope — safe because matchAll() clones the regex internally.\n */\nconst NUMERIC_PATTERNS: RegExp[] = [\n /\\bLIMIT\\s+:([a-zA-Z_]\\w*)/gi,\n /\\bOFFSET\\s+:([a-zA-Z_]\\w*)/gi,\n /\\bTOP\\s+:([a-zA-Z_]\\w*)/gi,\n /\\bFETCH\\s+FIRST\\s+:([a-zA-Z_]\\w*)\\s+ROWS/gi,\n // V1 limitation: arithmetic operators may false-positive for date\n // expressions like `:start_date - INTERVAL '1 day'`. A smarter\n // heuristic (e.g. look-ahead for INTERVAL) is deferred to a future PR.\n /[+\\-*/]\\s*:([a-zA-Z_]\\w*)/g,\n /:([a-zA-Z_]\\w*)\\s*[+\\-*/]/g,\n];\n\nexport function getProtectedRanges(sql: string): Array<[number, number]> {\n const ranges: Array<[number, number]> = [];\n for (const m of sql.matchAll(PROTECTED_RANGE_RE)) {\n ranges.push([m.index, m.index + m[0].length]);\n }\n return ranges;\n}\n\nfunction isInsideProtectedRange(\n offset: number,\n ranges: Array<[number, number]>,\n): boolean {\n return ranges.some(([start, end]) => offset >= start && offset < end);\n}\n\n/**\n * Parse a raw API/SDK error into a structured code + message.\n * Handles Databricks-style JSON bodies embedded in the message string,\n * e.g. `Response from server (Bad Request) {\"error_code\":\"...\",\"message\":\"...\"}`.\n */\nfunction parseError(raw: string): { code?: string; message: string } {\n const jsonMatch = raw.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n if (parsed.error_code || parsed.message) {\n return {\n code: parsed.error_code,\n message: parsed.message || raw,\n };\n }\n } catch {\n // not valid JSON, fall through\n }\n }\n return { message: raw };\n}\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(\n sql: string,\n ranges?: Array<[number, number]>,\n): string[] {\n const protectedRanges = ranges ?? getProtectedRanges(sql);\n const matches = sql.matchAll(/(?<!:):([a-zA-Z_]\\w*)/g);\n const params = new Set<string>();\n for (const match of matches) {\n if (!isInsideProtectedRange(match.index, protectedRanges)) {\n params.add(match[1]);\n }\n }\n return Array.from(params);\n}\n\n// parameters that are injected by the server\nexport const SERVER_INJECTED_PARAMS = [\"workspaceId\"];\n\n/**\n * Generates the TypeScript type literal for query parameters from SQL.\n * Shared by both the success and failure paths.\n */\nfunction formatParametersType(sql: string): string {\n const params = extractParameters(sql).filter(\n (p) => !SERVER_INJECTED_PARAMS.includes(p),\n );\n const paramTypes = extractParameterTypes(sql);\n\n return params.length > 0\n ? `{\\n ${params\n .map((p) => {\n const sqlType = paramTypes[p];\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\nexport function convertToQueryType(\n result: DatabricksStatementExecutionResponse,\n sql: string,\n queryName: string,\n): { type: string; hasResults: boolean } {\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 paramsType = formatParametersType(sql);\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 const hasResults = resultFields.length > 0;\n\n const type = `{\n name: \"${queryName}\";\n parameters: ${paramsType};\n result: ${\n hasResults\n ? `Array<{\n ${resultFields.join(\";\\n \")};\n }>`\n : \"unknown\"\n };\n }`;\n\n return { type, hasResults };\n}\n\n/**\n * Used when DESCRIBE QUERY fails so the query still appears in QueryRegistry.\n * Generates a type with unknown result from SQL alone (no warehouse call).\n */\nfunction generateUnknownResultQuery(sql: string, queryName: string): string {\n const paramsType = formatParametersType(sql);\n\n return `{\n name: \"${queryName}\";\n parameters: ${paramsType};\n result: unknown;\n }`;\n}\n\nexport function extractParameterTypes(sql: string): Record<string, string> {\n const paramTypes: Record<string, string> = {};\n // Alternation order matters: TIMESTAMP_NTZ must precede TIMESTAMP so the\n // regex engine doesn't greedy-match TIMESTAMP and leave `_NTZ` unconsumed.\n const regex =\n /--\\s*@param\\s+(\\w+)\\s+(STRING|NUMERIC|DECIMAL|BIGINT|TINYINT|SMALLINT|INT|FLOAT|DOUBLE|BOOLEAN|DATE|TIMESTAMP_NTZ|TIMESTAMP|BINARY)\\b/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\nexport function defaultForType(sqlType: string | undefined): string {\n switch (sqlType?.toUpperCase()) {\n case \"NUMERIC\":\n case \"DECIMAL\":\n case \"BIGINT\":\n case \"TINYINT\":\n case \"SMALLINT\":\n case \"INT\":\n return \"0\";\n case \"FLOAT\":\n case \"DOUBLE\":\n return \"0.0\";\n case \"STRING\":\n return \"''\";\n case \"BOOLEAN\":\n return \"true\";\n case \"DATE\":\n return \"'2000-01-01'\";\n case \"TIMESTAMP\":\n return \"'2000-01-01T00:00:00Z'\";\n case \"TIMESTAMP_NTZ\":\n return \"'2000-01-01T00:00:00'\";\n case \"BINARY\":\n return \"X'00'\";\n default:\n return \"''\";\n }\n}\n\n/**\n * Infer parameter types from positional context in SQL.\n * V1 only infers NUMERIC from patterns like LIMIT, OFFSET, TOP,\n * FETCH FIRST ... ROWS, and arithmetic operators.\n * Parameters inside string literals or SQL comments are ignored.\n */\nexport function inferParameterTypes(\n sql: string,\n ranges?: Array<[number, number]>,\n): Record<string, string> {\n const inferred: Record<string, string> = {};\n const protectedRanges = ranges ?? getProtectedRanges(sql);\n\n for (const pattern of NUMERIC_PATTERNS) {\n for (const match of sql.matchAll(pattern)) {\n if (!isInsideProtectedRange(match.index, protectedRanges)) {\n inferred[match[1]] = \"NUMERIC\";\n }\n }\n }\n\n return inferred;\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; concurrency?: number } = {},\n): Promise<QuerySchema[]> {\n const { noCache = false, concurrency: rawConcurrency = 10 } = options;\n const concurrency =\n typeof rawConcurrency === \"number\" && Number.isFinite(rawConcurrency)\n ? Math.max(1, Math.floor(rawConcurrency))\n : 10;\n\n // read all query files and cache in parallel\n const [allFiles, cache] = await Promise.all([\n fs.readdir(queryFolder),\n noCache\n ? ({ version: CACHE_VERSION, queries: {} } as Awaited<\n ReturnType<typeof loadCache>\n >)\n : loadCache(),\n ]);\n\n const queryFiles = allFiles.filter((file) => file.endsWith(\".sql\"));\n logger.debug(\"Found %d SQL queries\", queryFiles.length);\n\n const client = new WorkspaceClient({});\n const spinner = new Spinner();\n\n // Read all SQL files in parallel\n const sqlContents = await Promise.all(\n queryFiles.map((file) => fs.readFile(path.join(queryFolder, file), \"utf8\")),\n );\n\n const startTime = performance.now();\n\n // Phase 1: Check cache, separate cached vs uncached\n const cachedResults: Array<{ index: number; schema: QuerySchema }> = [];\n const uncachedQueries: Array<{\n index: number;\n queryName: string;\n sql: string;\n sqlHash: string;\n cleanedSql: string;\n }> = [];\n const logEntries: Array<{\n queryName: string;\n status: \"HIT\" | \"MISS\";\n failed?: boolean;\n error?: { code?: string; message: string };\n }> = [];\n\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 const sql = sqlContents[i];\n const sqlHash = hashSQL(sql);\n\n const cached = cache.queries[queryName];\n if (cached && cached.hash === sqlHash && !cached.retry) {\n cachedResults.push({\n index: i,\n schema: { name: queryName, type: cached.type },\n });\n logEntries.push({ queryName, status: \"HIT\" });\n } else {\n const protectedRanges = getProtectedRanges(sql);\n const annotatedTypes = extractParameterTypes(sql);\n const inferredTypes = inferParameterTypes(sql, protectedRanges);\n const parameterTypes = { ...inferredTypes, ...annotatedTypes };\n const sqlWithDefaults = sql.replace(\n /(?<!:):([a-zA-Z_]\\w*)/g,\n (original, paramName, offset) => {\n if (isInsideProtectedRange(offset, protectedRanges)) {\n return original;\n }\n return defaultForType(parameterTypes[paramName]);\n },\n );\n\n // Warn about unresolved parameters\n const allParams = extractParameters(sql, protectedRanges);\n for (const param of allParams) {\n if (SERVER_INJECTED_PARAMS.includes(param)) continue;\n if (parameterTypes[param]) continue;\n logger.warn(\n '%s: parameter \":%s\" has no type annotation or inference. Add %s to the query file.',\n queryFiles[i],\n param,\n `-- @param ${param} <TYPE>`,\n );\n }\n\n const cleanedSql = sqlWithDefaults.trim().replace(/;\\s*$/, \"\");\n uncachedQueries.push({ index: i, queryName, sql, sqlHash, cleanedSql });\n }\n }\n\n // Phase 2: Execute all uncached DESCRIBE calls in parallel\n type DescribeResult =\n | {\n status: \"ok\";\n index: number;\n schema: QuerySchema;\n cacheEntry: { hash: string; type: string; retry: boolean };\n }\n | {\n status: \"fail\";\n index: number;\n schema: QuerySchema;\n cacheEntry: { hash: string; type: string; retry: boolean };\n error: { code?: string; message: string };\n };\n\n const freshResults: Array<{ index: number; schema: QuerySchema }> = [];\n\n if (uncachedQueries.length > 0) {\n let completed = 0;\n const total = uncachedQueries.length;\n spinner.start(\n `Describing ${total} ${total === 1 ? \"query\" : \"queries\"} (0/${total})`,\n );\n\n const describeOne = async ({\n index,\n queryName,\n sql,\n sqlHash,\n cleanedSql,\n }: (typeof uncachedQueries)[number]): Promise<DescribeResult> => {\n const result = (await client.statementExecution.executeStatement({\n statement: `DESCRIBE QUERY ${cleanedSql}`,\n warehouse_id: warehouseId,\n })) as DatabricksStatementExecutionResponse;\n\n completed++;\n spinner.update(\n `Describing ${total} ${total === 1 ? \"query\" : \"queries\"} (${completed}/${total})`,\n );\n\n logger.debug(\n \"DESCRIBE result for %s: state=%s, rows=%d\",\n queryName,\n result.status.state,\n result.result?.data_array?.length ?? 0,\n );\n\n if (result.status.state === \"FAILED\") {\n const sqlError =\n result.status.error?.message || \"Query execution failed\";\n logger.warn(\"DESCRIBE failed for %s: %s\", queryName, sqlError);\n const type = generateUnknownResultQuery(sql, queryName);\n return {\n status: \"fail\",\n index,\n schema: { name: queryName, type },\n cacheEntry: { hash: sqlHash, type, retry: true },\n error: parseError(sqlError),\n };\n }\n\n const { type, hasResults } = convertToQueryType(result, sql, queryName);\n return {\n status: \"ok\",\n index,\n schema: { name: queryName, type },\n cacheEntry: { hash: sqlHash, type, retry: !hasResults },\n };\n };\n\n // Process in chunks, saving cache after each chunk\n const processBatchResults = (\n settled: PromiseSettledResult<DescribeResult>[],\n batchOffset: number,\n ) => {\n for (let i = 0; i < settled.length; i++) {\n const entry = settled[i];\n const { queryName } = uncachedQueries[batchOffset + i];\n\n if (entry.status === \"fulfilled\") {\n const res = entry.value;\n freshResults.push({ index: res.index, schema: res.schema });\n cache.queries[queryName] = res.cacheEntry;\n logEntries.push({\n queryName,\n status: \"MISS\",\n failed: res.status === \"fail\",\n error: res.status === \"fail\" ? res.error : undefined,\n });\n } else {\n const { sql, sqlHash, index } = uncachedQueries[batchOffset + i];\n const reason =\n entry.reason instanceof Error\n ? entry.reason.message\n : String(entry.reason);\n logger.warn(\"DESCRIBE rejected for %s: %s\", queryName, reason);\n const type = generateUnknownResultQuery(sql, queryName);\n freshResults.push({ index, schema: { name: queryName, type } });\n cache.queries[queryName] = { hash: sqlHash, type, retry: true };\n logEntries.push({\n queryName,\n status: \"MISS\",\n failed: true,\n error: parseError(reason),\n });\n }\n }\n };\n\n if (uncachedQueries.length > concurrency) {\n for (let b = 0; b < uncachedQueries.length; b += concurrency) {\n const batch = uncachedQueries.slice(b, b + concurrency);\n const batchResults = await Promise.allSettled(batch.map(describeOne));\n processBatchResults(batchResults, b);\n await saveCache(cache);\n }\n } else {\n const settled = await Promise.allSettled(\n uncachedQueries.map(describeOne),\n );\n processBatchResults(settled, 0);\n await saveCache(cache);\n }\n\n spinner.stop(\"\");\n }\n\n const elapsed = ((performance.now() - startTime) / 1000).toFixed(2);\n\n // Print formatted table\n if (logEntries.length > 0) {\n const maxNameLen = Math.max(...logEntries.map((e) => e.queryName.length));\n const separator = pc.dim(\"─\".repeat(50));\n console.log(\"\");\n console.log(\n ` ${pc.bold(\"Typegen Queries\")} ${pc.dim(`(${logEntries.length})`)}`,\n );\n console.log(` ${separator}`);\n for (const entry of logEntries) {\n const tag = entry.failed\n ? pc.bold(pc.red(\"ERROR\"))\n : entry.status === \"HIT\"\n ? `cache ${pc.bold(pc.green(\"HIT \"))}`\n : `cache ${pc.bold(pc.yellow(\"MISS \"))}`;\n const rawName = entry.queryName.padEnd(maxNameLen);\n const name = entry.failed ? pc.dim(pc.strikethrough(rawName)) : rawName;\n const errorCode = entry.error?.message.match(/\\[([^\\]]+)\\]/)?.[1];\n const reason = errorCode ? ` ${pc.dim(errorCode)}` : \"\";\n console.log(` ${tag} ${name}${reason}`);\n }\n const newCount = logEntries.filter(\n (e) => e.status === \"MISS\" && !e.failed,\n ).length;\n const cacheCount = logEntries.filter(\n (e) => e.status === \"HIT\" && !e.failed,\n ).length;\n const errorCount = logEntries.filter((e) => e.failed).length;\n console.log(` ${separator}`);\n const parts = [`${newCount} new`, `${cacheCount} from cache`];\n if (errorCount > 0)\n parts.push(`${errorCount} ${errorCount === 1 ? \"error\" : \"errors\"}`);\n console.log(` ${parts.join(\", \")}. ${pc.dim(`${elapsed}s`)}`);\n console.log(\"\");\n }\n\n // Merge and sort by original file index for deterministic output\n return [...cachedResults, ...freshResults]\n .sort((a, b) => a.index - b.index)\n .map((r) => r.schema);\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 */\nfunction 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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"}
@@ -27,14 +27,14 @@ const sqlTypeToHelper = {
27
27
  STRING: "sql.string()",
28
28
  BINARY: "sql.binary()",
29
29
  BOOLEAN: "sql.boolean()",
30
- NUMERIC: "sql.number()",
31
- INT: "sql.number()",
32
- BIGINT: "sql.number()",
33
- TINYINT: "sql.number()",
34
- SMALLINT: "sql.number()",
35
- FLOAT: "sql.number()",
36
- DOUBLE: "sql.number()",
37
- DECIMAL: "sql.number()",
30
+ NUMERIC: "sql.numeric()",
31
+ DECIMAL: "sql.numeric()",
32
+ BIGINT: "sql.bigint()",
33
+ INT: "sql.int()",
34
+ TINYINT: "sql.int()",
35
+ SMALLINT: "sql.int()",
36
+ FLOAT: "sql.float()",
37
+ DOUBLE: "sql.double()",
38
38
  DATE: "sql.date()",
39
39
  TIMESTAMP: "sql.timestamp()",
40
40
  TIMESTAMP_NTZ: "sql.timestamp()"
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../src/type-generator/types.ts"],"sourcesContent":["/**\n * Databricks statement execution response interface for DESCRIBE QUERY\n * @property statement_id - the id of the statement\n * @property status - the status of the statement\n * @property result - the result containing column schema as rows [col_name, data_type, comment]\n */\nexport interface DatabricksStatementExecutionResponse {\n statement_id: string;\n status: {\n state: string;\n error?: { error_code?: string; message?: string };\n };\n result?: {\n data_array?: (string | null)[][];\n };\n}\n\n/**\n * Map of SQL types to their corresponding marker types\n * Used to convert SQL types to their corresponding marker types\n */\nexport const sqlTypeToMarker: Record<string, string> = {\n // string\n STRING: \"SQLStringMarker\",\n BINARY: \"SQLBinaryMarker\",\n // boolean\n BOOLEAN: \"SQLBooleanMarker\",\n // numeric\n NUMERIC: \"SQLNumberMarker\",\n INT: \"SQLNumberMarker\",\n BIGINT: \"SQLNumberMarker\",\n TINYINT: \"SQLNumberMarker\",\n SMALLINT: \"SQLNumberMarker\",\n FLOAT: \"SQLNumberMarker\",\n DOUBLE: \"SQLNumberMarker\",\n DECIMAL: \"SQLNumberMarker\",\n // date/time\n DATE: \"SQLDateMarker\",\n TIMESTAMP: \"SQLTimestampMarker\",\n TIMESTAMP_NTZ: \"SQLTimestampMarker\",\n};\n\n/**\n * Map of SQL types to their corresponding helper function names\n * Used to generate JSDoc hints for parameters\n */\nexport const sqlTypeToHelper: Record<string, string> = {\n // string\n STRING: \"sql.string()\",\n BINARY: \"sql.binary()\",\n // boolean\n BOOLEAN: \"sql.boolean()\",\n // numeric\n NUMERIC: \"sql.number()\",\n INT: \"sql.number()\",\n BIGINT: \"sql.number()\",\n TINYINT: \"sql.number()\",\n SMALLINT: \"sql.number()\",\n FLOAT: \"sql.number()\",\n DOUBLE: \"sql.number()\",\n DECIMAL: \"sql.number()\",\n // date/time\n DATE: \"sql.date()\",\n TIMESTAMP: \"sql.timestamp()\",\n TIMESTAMP_NTZ: \"sql.timestamp()\",\n};\n\n/**\n * Query schema interface\n * @property name - the name of the query\n * @property type - the type of the query (string, number, boolean, object, array, etc.)\n */\nexport interface QuerySchema {\n name: string;\n type: string;\n}\n"],"mappings":";;;;;AAqBA,MAAa,kBAA0C;CAErD,QAAQ;CACR,QAAQ;CAER,SAAS;CAET,SAAS;CACT,KAAK;CACL,QAAQ;CACR,SAAS;CACT,UAAU;CACV,OAAO;CACP,QAAQ;CACR,SAAS;CAET,MAAM;CACN,WAAW;CACX,eAAe;CAChB;;;;;AAMD,MAAa,kBAA0C;CAErD,QAAQ;CACR,QAAQ;CAER,SAAS;CAET,SAAS;CACT,KAAK;CACL,QAAQ;CACR,SAAS;CACT,UAAU;CACV,OAAO;CACP,QAAQ;CACR,SAAS;CAET,MAAM;CACN,WAAW;CACX,eAAe;CAChB"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../src/type-generator/types.ts"],"sourcesContent":["/**\n * Databricks statement execution response interface for DESCRIBE QUERY\n * @property statement_id - the id of the statement\n * @property status - the status of the statement\n * @property result - the result containing column schema as rows [col_name, data_type, comment]\n */\nexport interface DatabricksStatementExecutionResponse {\n statement_id: string;\n status: {\n state: string;\n error?: { error_code?: string; message?: string };\n };\n result?: {\n data_array?: (string | null)[][];\n };\n}\n\n/**\n * Map of SQL types to their corresponding marker types\n * Used to convert SQL types to their corresponding marker types\n */\nexport const sqlTypeToMarker: Record<string, string> = {\n // string\n STRING: \"SQLStringMarker\",\n BINARY: \"SQLBinaryMarker\",\n // boolean\n BOOLEAN: \"SQLBooleanMarker\",\n // numeric\n NUMERIC: \"SQLNumberMarker\",\n INT: \"SQLNumberMarker\",\n BIGINT: \"SQLNumberMarker\",\n TINYINT: \"SQLNumberMarker\",\n SMALLINT: \"SQLNumberMarker\",\n FLOAT: \"SQLNumberMarker\",\n DOUBLE: \"SQLNumberMarker\",\n DECIMAL: \"SQLNumberMarker\",\n // date/time\n DATE: \"SQLDateMarker\",\n TIMESTAMP: \"SQLTimestampMarker\",\n TIMESTAMP_NTZ: \"SQLTimestampMarker\",\n};\n\n/**\n * Map of SQL types to their corresponding helper function names\n * Used to generate JSDoc hints for parameters\n */\nexport const sqlTypeToHelper: Record<string, string> = {\n // string\n STRING: \"sql.string()\",\n BINARY: \"sql.binary()\",\n // boolean\n BOOLEAN: \"sql.boolean()\",\n // numeric — route each SQL type to its closest typed helper. INT/BIGINT\n // are critical for LIMIT/OFFSET; FLOAT/DOUBLE preserve precision intent;\n // NUMERIC/DECIMAL route to sql.numeric() for exact-decimal columns.\n NUMERIC: \"sql.numeric()\",\n DECIMAL: \"sql.numeric()\",\n BIGINT: \"sql.bigint()\",\n INT: \"sql.int()\",\n TINYINT: \"sql.int()\",\n SMALLINT: \"sql.int()\",\n FLOAT: \"sql.float()\",\n DOUBLE: \"sql.double()\",\n // date/time\n DATE: \"sql.date()\",\n TIMESTAMP: \"sql.timestamp()\",\n TIMESTAMP_NTZ: \"sql.timestamp()\",\n};\n\n/**\n * Query schema interface\n * @property name - the name of the query\n * @property type - the type of the query (string, number, boolean, object, array, etc.)\n */\nexport interface QuerySchema {\n name: string;\n type: string;\n}\n"],"mappings":";;;;;AAqBA,MAAa,kBAA0C;CAErD,QAAQ;CACR,QAAQ;CAER,SAAS;CAET,SAAS;CACT,KAAK;CACL,QAAQ;CACR,SAAS;CACT,UAAU;CACV,OAAO;CACP,QAAQ;CACR,SAAS;CAET,MAAM;CACN,WAAW;CACX,eAAe;CAChB;;;;;AAMD,MAAa,kBAA0C;CAErD,QAAQ;CACR,QAAQ;CAER,SAAS;CAIT,SAAS;CACT,SAAS;CACT,QAAQ;CACR,KAAK;CACL,SAAS;CACT,UAAU;CACV,OAAO;CACP,QAAQ;CAER,MAAM;CACN,WAAW;CACX,eAAe;CAChB"}
@@ -2,10 +2,25 @@
2
2
 
3
3
  ```ts
4
4
  const sql: {
5
+ bigint: SQLNumberMarker & {
6
+ __sql_type: "BIGINT";
7
+ };
5
8
  binary: SQLBinaryMarker;
6
9
  boolean: SQLBooleanMarker;
7
10
  date: SQLDateMarker;
11
+ double: SQLNumberMarker & {
12
+ __sql_type: "DOUBLE";
13
+ };
14
+ float: SQLNumberMarker & {
15
+ __sql_type: "FLOAT";
16
+ };
17
+ int: SQLNumberMarker & {
18
+ __sql_type: "INT";
19
+ };
8
20
  number: SQLNumberMarker;
21
+ numeric: SQLNumberMarker & {
22
+ __sql_type: "NUMERIC";
23
+ };
9
24
  string: SQLStringMarker;
10
25
  timestamp: SQLTimestampMarker;
11
26
  };
@@ -16,6 +31,40 @@ SQL helper namespace
16
31
 
17
32
  ## Type Declaration[​](#type-declaration "Direct link to Type Declaration")
18
33
 
34
+ ### bigint()[​](#bigint "Direct link to bigint()")
35
+
36
+ ```ts
37
+ bigint(value: string | number | bigint): SQLNumberMarker & {
38
+ __sql_type: "BIGINT";
39
+ };
40
+
41
+ ```
42
+
43
+ Creates a `BIGINT` (64-bit signed integer) parameter. Accepts JS `bigint` so callers can round-trip values outside `Number.MAX_SAFE_INTEGER` without precision loss; for `number` inputs, requires `Number.isSafeInteger(value)`.
44
+
45
+ Rejects values outside the signed 64-bit range `[-2^63, 2^63 - 1]`.
46
+
47
+ #### Parameters[​](#parameters "Direct link to Parameters")
48
+
49
+ | Parameter | Type | Description |
50
+ | --------- | -------------------------------- | ------------------------------------------------ |
51
+ | `value` | `string` \| `number` \| `bigint` | Integer number, bigint, or integer-shaped string |
52
+
53
+ #### Returns[​](#returns "Direct link to Returns")
54
+
55
+ `SQLNumberMarker` & { `__sql_type`: `"BIGINT"`; }
56
+
57
+ Marker pinned to `BIGINT`
58
+
59
+ #### Example[​](#example "Direct link to Example")
60
+
61
+ ```typescript
62
+ sql.bigint(42); // { __sql_type: "BIGINT", value: "42" }
63
+ sql.bigint(9007199254740993n); // { __sql_type: "BIGINT", value: "9007199254740993" }
64
+ sql.bigint("9007199254740993"); // { __sql_type: "BIGINT", value: "9007199254740993" }
65
+
66
+ ```
67
+
19
68
  ### binary()[​](#binary "Direct link to binary()")
20
69
 
21
70
  ```ts
@@ -25,13 +74,13 @@ binary(value: string | Uint8Array | ArrayBuffer): SQLBinaryMarker;
25
74
 
26
75
  Creates a BINARY parameter as hex-encoded STRING Accepts Uint8Array, ArrayBuffer, or hex string Note: Databricks SQL Warehouse doesn't support BINARY as parameter type, so this helper returns a STRING with hex encoding. Use UNHEX(<!-- -->:param<!-- -->) in your SQL.
27
76
 
28
- #### Parameters[​](#parameters "Direct link to Parameters")
77
+ #### Parameters[​](#parameters-1 "Direct link to Parameters")
29
78
 
30
79
  | Parameter | Type | Description |
31
80
  | --------- | ----------------------------------------- | -------------------------------------- |
32
81
  | `value` | `string` \| `Uint8Array` \| `ArrayBuffer` | Uint8Array, ArrayBuffer, or hex string |
33
82
 
34
- #### Returns[​](#returns "Direct link to Returns")
83
+ #### Returns[​](#returns-1 "Direct link to Returns")
35
84
 
36
85
  `SQLBinaryMarker`
37
86
 
@@ -63,13 +112,13 @@ boolean(value: string | number | boolean): SQLBooleanMarker;
63
112
 
64
113
  Create a BOOLEAN type parameter Accepts booleans, strings, or numbers
65
114
 
66
- #### Parameters[​](#parameters-1 "Direct link to Parameters")
115
+ #### Parameters[​](#parameters-2 "Direct link to Parameters")
67
116
 
68
117
  | Parameter | Type | Description |
69
118
  | --------- | --------------------------------- | -------------------------- |
70
119
  | `value` | `string` \| `number` \| `boolean` | Boolean, string, or number |
71
120
 
72
- #### Returns[​](#returns-1 "Direct link to Returns")
121
+ #### Returns[​](#returns-2 "Direct link to Returns")
73
122
 
74
123
  `SQLBooleanMarker`
75
124
 
@@ -116,13 +165,13 @@ date(value: string | Date): SQLDateMarker;
116
165
 
117
166
  Creates a DATE type parameter Accepts Date objects or ISO date strings (YYYY-MM-DD format)
118
167
 
119
- #### Parameters[​](#parameters-2 "Direct link to Parameters")
168
+ #### Parameters[​](#parameters-3 "Direct link to Parameters")
120
169
 
121
170
  | Parameter | Type | Description |
122
171
  | --------- | ------------------ | ------------------------------ |
123
172
  | `value` | `string` \| `Date` | Date object or ISO date string |
124
173
 
125
- #### Returns[​](#returns-2 "Direct link to Returns")
174
+ #### Returns[​](#returns-3 "Direct link to Returns")
126
175
 
127
176
  `SQLDateMarker`
128
177
 
@@ -142,6 +191,99 @@ params = { startDate: "2024-01-01" }
142
191
 
143
192
  ```
144
193
 
194
+ ### double()[​](#double "Direct link to double()")
195
+
196
+ ```ts
197
+ double(value: string | number): SQLNumberMarker & {
198
+ __sql_type: "DOUBLE";
199
+ };
200
+
201
+ ```
202
+
203
+ Creates a `DOUBLE` (double-precision, 64-bit) parameter. Same precision as a JS `number`, so `sql.double(value)` is exact for any JS number.
204
+
205
+ #### Parameters[​](#parameters-4 "Direct link to Parameters")
206
+
207
+ | Parameter | Type | Description |
208
+ | --------- | -------------------- | ------------------------ |
209
+ | `value` | `string` \| `number` | Number or numeric string |
210
+
211
+ #### Returns[​](#returns-4 "Direct link to Returns")
212
+
213
+ `SQLNumberMarker` & { `__sql_type`: `"DOUBLE"`; }
214
+
215
+ Marker pinned to `DOUBLE`
216
+
217
+ #### Example[​](#example-1 "Direct link to Example")
218
+
219
+ ```typescript
220
+ sql.double(3.14); // { __sql_type: "DOUBLE", value: "3.14" }
221
+
222
+ ```
223
+
224
+ ### float()[​](#float "Direct link to float()")
225
+
226
+ ```ts
227
+ float(value: string | number): SQLNumberMarker & {
228
+ __sql_type: "FLOAT";
229
+ };
230
+
231
+ ```
232
+
233
+ Creates a `FLOAT` (single-precision, 32-bit) parameter. Note that JS numbers are 64-bit doubles, so values may be rounded to fit FLOAT precision at bind time.
234
+
235
+ #### Parameters[​](#parameters-5 "Direct link to Parameters")
236
+
237
+ | Parameter | Type | Description |
238
+ | --------- | -------------------- | ------------------------ |
239
+ | `value` | `string` \| `number` | Number or numeric string |
240
+
241
+ #### Returns[​](#returns-5 "Direct link to Returns")
242
+
243
+ `SQLNumberMarker` & { `__sql_type`: `"FLOAT"`; }
244
+
245
+ Marker pinned to `FLOAT`
246
+
247
+ #### Example[​](#example-2 "Direct link to Example")
248
+
249
+ ```typescript
250
+ sql.float(3.14); // { __sql_type: "FLOAT", value: "3.14" }
251
+
252
+ ```
253
+
254
+ ### int()[​](#int "Direct link to int()")
255
+
256
+ ```ts
257
+ int(value: string | number): SQLNumberMarker & {
258
+ __sql_type: "INT";
259
+ };
260
+
261
+ ```
262
+
263
+ Creates an `INT` (32-bit signed integer) parameter. Use when the column or context requires `INT` specifically (e.g. legacy schemas, or to make the wire type explicit).
264
+
265
+ Rejects non-integers, values outside `Number.MAX_SAFE_INTEGER` (for number inputs), and values outside the signed 32-bit range `[-2^31, 2^31 - 1]`.
266
+
267
+ #### Parameters[​](#parameters-6 "Direct link to Parameters")
268
+
269
+ | Parameter | Type | Description |
270
+ | --------- | -------------------- | --------------------------------------- |
271
+ | `value` | `string` \| `number` | Integer number or integer-shaped string |
272
+
273
+ #### Returns[​](#returns-6 "Direct link to Returns")
274
+
275
+ `SQLNumberMarker` & { `__sql_type`: `"INT"`; }
276
+
277
+ Marker pinned to `INT`
278
+
279
+ #### Example[​](#example-3 "Direct link to Example")
280
+
281
+ ```typescript
282
+ sql.int(42); // { __sql_type: "INT", value: "42" }
283
+ sql.int("42"); // { __sql_type: "INT", value: "42" }
284
+
285
+ ```
286
+
145
287
  ### number()[​](#number "Direct link to number()")
146
288
 
147
289
  ```ts
@@ -149,31 +291,71 @@ number(value: string | number): SQLNumberMarker;
149
291
 
150
292
  ```
151
293
 
152
- Creates a NUMERIC type parameter Accepts numbers or numeric strings
294
+ Creates a numeric type parameter. The wire SQL type is inferred from the value so the parameter binds correctly in any context, including `LIMIT` and `OFFSET`:
153
295
 
154
- #### Parameters[​](#parameters-3 "Direct link to Parameters")
296
+ * JS integer in `[-2^31, 2^31 - 1]` → `INT`
297
+ * JS integer outside `INT` but within `Number.MAX_SAFE_INTEGER` → `BIGINT`
298
+ * JS non-integer (`3.14`) → `DOUBLE`
299
+ * integer-shaped string in `INT` range → `INT` (common HTTP-input case)
300
+ * integer-shaped string outside `INT` but within `BIGINT` → `BIGINT`
301
+ * decimal-shaped string (`"123.45"`) → `NUMERIC` (preserves precision)
302
+
303
+ Why default to `INT`? Spark's `LIMIT` and `OFFSET` operators require `IntegerType` specifically — `BIGINT` (`LongType`) is rejected with `INVALID_LIMIT_LIKE_EXPRESSION.DATA_TYPE`. Catalyst auto-widens `INT` to `BIGINT` / `DECIMAL` / `DOUBLE` for wider columns, so `INT` is a strictly better default than `BIGINT`.
304
+
305
+ Throws on `NaN`, `Infinity`, JS integers outside `Number.MAX_SAFE_INTEGER`, integer-shaped strings outside the `BIGINT` range, or non-numeric strings. Reach for `sql.int()`, `sql.bigint()`, `sql.float()`, `sql.double()`, or `sql.numeric()` to override the inferred type.
306
+
307
+ #### Parameters[​](#parameters-7 "Direct link to Parameters")
155
308
 
156
309
  | Parameter | Type | Description |
157
310
  | --------- | -------------------- | ------------------------ |
158
311
  | `value` | `string` \| `number` | Number or numeric string |
159
312
 
160
- #### Returns[​](#returns-3 "Direct link to Returns")
313
+ #### Returns[​](#returns-7 "Direct link to Returns")
161
314
 
162
315
  `SQLNumberMarker`
163
316
 
164
- Marker object for NUMERIC type parameter
317
+ Marker for a numeric SQL parameter
165
318
 
166
- #### Examples[​](#examples-3 "Direct link to Examples")
319
+ #### Example[​](#example-4 "Direct link to Example")
167
320
 
168
321
  ```typescript
169
- const params = { userId: sql.number(123) };
170
- params = { userId: "123" }
322
+ sql.number(123); // { __sql_type: "INT", value: "123" }
323
+ sql.number(3_000_000_000); // { __sql_type: "BIGINT", value: "3000000000" }
324
+ sql.number(0.5); // { __sql_type: "DOUBLE", value: "0.5" }
325
+ sql.number("10"); // { __sql_type: "INT", value: "10" }
326
+ sql.number("123.45"); // { __sql_type: "NUMERIC", value: "123.45" }
327
+
328
+ ```
329
+
330
+ ### numeric()[​](#numeric "Direct link to numeric()")
331
+
332
+ ```ts
333
+ numeric(value: string | number): SQLNumberMarker & {
334
+ __sql_type: "NUMERIC";
335
+ };
171
336
 
172
337
  ```
173
338
 
339
+ Creates a `NUMERIC` (fixed-point DECIMAL) parameter. Use when you need exact decimal arithmetic (currency, percentages) — pass values as strings to avoid JS-number precision loss.
340
+
341
+ Note: passing a JS `number` is accepted but lossy for many values (e.g. `0.1 + 0.2` → `"0.30000000000000004"`). Prefer strings.
342
+
343
+ #### Parameters[​](#parameters-8 "Direct link to Parameters")
344
+
345
+ | Parameter | Type | Description |
346
+ | --------- | -------------------- | ---------------------------------------------------------- |
347
+ | `value` | `string` \| `number` | Number or numeric string (strings preferred for precision) |
348
+
349
+ #### Returns[​](#returns-8 "Direct link to Returns")
350
+
351
+ `SQLNumberMarker` & { `__sql_type`: `"NUMERIC"`; }
352
+
353
+ Marker pinned to `NUMERIC`
354
+
355
+ #### Example[​](#example-5 "Direct link to Example")
356
+
174
357
  ```typescript
175
- const params = { userId: sql.number("123") };
176
- params = { userId: "123" }
358
+ sql.numeric("12345.6789"); // { __sql_type: "NUMERIC", value: "12345.6789" }
177
359
 
178
360
  ```
179
361
 
@@ -186,19 +368,19 @@ string(value: string | number | boolean): SQLStringMarker;
186
368
 
187
369
  Creates a STRING type parameter Accepts strings, numbers, or booleans
188
370
 
189
- #### Parameters[​](#parameters-4 "Direct link to Parameters")
371
+ #### Parameters[​](#parameters-9 "Direct link to Parameters")
190
372
 
191
373
  | Parameter | Type | Description |
192
374
  | --------- | --------------------------------- | -------------------------- |
193
375
  | `value` | `string` \| `number` \| `boolean` | String, number, or boolean |
194
376
 
195
- #### Returns[​](#returns-4 "Direct link to Returns")
377
+ #### Returns[​](#returns-9 "Direct link to Returns")
196
378
 
197
379
  `SQLStringMarker`
198
380
 
199
381
  Marker object for STRING type parameter
200
382
 
201
- #### Examples[​](#examples-4 "Direct link to Examples")
383
+ #### Examples[​](#examples-3 "Direct link to Examples")
202
384
 
203
385
  ```typescript
204
386
  const params = { name: sql.string("John") };
@@ -227,19 +409,19 @@ timestamp(value: string | number | Date): SQLTimestampMarker;
227
409
 
228
410
  Creates a TIMESTAMP type parameter Accepts Date objects, ISO timestamp strings, or Unix timestamp numbers
229
411
 
230
- #### Parameters[​](#parameters-5 "Direct link to Parameters")
412
+ #### Parameters[​](#parameters-10 "Direct link to Parameters")
231
413
 
232
414
  | Parameter | Type | Description |
233
415
  | --------- | ------------------------------ | ----------------------------------------------------------- |
234
416
  | `value` | `string` \| `number` \| `Date` | Date object, ISO timestamp string, or Unix timestamp number |
235
417
 
236
- #### Returns[​](#returns-5 "Direct link to Returns")
418
+ #### Returns[​](#returns-10 "Direct link to Returns")
237
419
 
238
420
  `SQLTimestampMarker`
239
421
 
240
422
  Marker object for TIMESTAMP type parameter
241
423
 
242
- #### Examples[​](#examples-5 "Direct link to Examples")
424
+ #### Examples[​](#examples-4 "Direct link to Examples")
243
425
 
244
426
  ```typescript
245
427
  const params = { createdTime: sql.timestamp(new Date("2024-01-01T12:00:00Z")) };
@@ -59,10 +59,10 @@ databricks apps deploy --help
59
59
 
60
60
  ```
61
61
 
62
- * Skip the build step for faster iteration:
62
+ * Skip validation for faster iteration:
63
63
 
64
64
  ```bash
65
- databricks apps deploy --skip-build
65
+ databricks apps deploy --skip-validation
66
66
 
67
67
  ```
68
68
 
@@ -40,16 +40,21 @@ Use `:paramName` placeholders and optionally annotate parameter types using SQL
40
40
  ```sql
41
41
  -- @param startDate DATE
42
42
  -- @param endDate DATE
43
- -- @param limit NUMERIC
43
+ -- @param limit INT
44
44
  SELECT ...
45
45
  WHERE usage_date BETWEEN :startDate AND :endDate
46
46
  LIMIT :limit
47
47
 
48
48
  ```
49
49
 
50
+ `LIMIT` / `OFFSET` require Spark `IntegerType` specifically — `BIGINT` (`LongType`) is rejected with `INVALID_LIMIT_LIKE_EXPRESSION.DATA_TYPE`. Annotate with `INT`, or use `sql.number()` (auto-infers `INT` for values in `[-2^31, 2^31-1]`, falling back to `BIGINT` for wider values) / `sql.int()` at the call site.
51
+
50
52
  **Supported `-- @param` types** (case-insensitive):
51
53
 
52
- * `STRING`, `NUMERIC`, `BOOLEAN`, `DATE`, `TIMESTAMP`, `BINARY`
54
+ * `STRING`, `BOOLEAN`, `DATE`, `TIMESTAMP`, `BINARY`
55
+ * `INT`, `BIGINT`, `TINYINT`, `SMALLINT` — bind via `sql.int()` / `sql.bigint()`
56
+ * `FLOAT`, `DOUBLE` — bind via `sql.float()` / `sql.double()`
57
+ * `NUMERIC`, `DECIMAL` — bind via `sql.numeric()` (pass strings for precision)
53
58
 
54
59
  ## Server-injected parameters[​](#server-injected-parameters "Direct link to Server-injected parameters")
55
60
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@databricks/appkit",
3
3
  "type": "module",
4
- "version": "0.33.0",
4
+ "version": "0.34.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "packageManager": "pnpm@10.21.0",