@databricks/appkit 0.52.0 → 0.53.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.
@@ -2,7 +2,7 @@ import { createLogger } from "../logging/logger.js";
2
2
  import { createWorkspaceClient } from "../workspace-client/factory.js";
3
3
  import "../workspace-client/index.js";
4
4
  import { CACHE_VERSION, hashSQL, loadCache, saveCache } from "./cache.js";
5
- import { getErrorDiagnostic, isConnectivityError } from "./errors.js";
5
+ import { classifyBlockingFailure, classifyEnvironmentalCause, getErrorDiagnostic, isConnectivityError } from "./errors.js";
6
6
  import { describeAdaptive } from "./statement-result.js";
7
7
  import { decidePreflight } from "./preflight.js";
8
8
  import { Spinner } from "./spinner.js";
@@ -169,7 +169,10 @@ function generateUnknownResultQuery(sql, queryName) {
169
169
  */
170
170
  function degradedType(cache, queryName, sql, sqlHash) {
171
171
  const prior = cache.queries[queryName];
172
- return prior?.hash === sqlHash && !prior.retry ? prior.type : generateUnknownResultQuery(sql, queryName);
172
+ return prior?.hash === sqlHash && !prior.retry ? { type: prior.type } : {
173
+ type: generateUnknownResultQuery(sql, queryName),
174
+ degraded: true
175
+ };
173
176
  }
174
177
  const PARAM_TYPE_ALTERNATION = "STRING|NUMERIC|DECIMAL|BIGINT|TINYINT|SMALLINT|INT|FLOAT|DOUBLE|BOOLEAN|DATE|TIMESTAMP_NTZ|TIMESTAMP|BINARY";
175
178
  function extractParameterTypes(sql) {
@@ -404,14 +407,21 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
404
407
  const freshResults = [];
405
408
  const syntaxErrors = [];
406
409
  const fatalErrors = [];
410
+ let hadEnvironmentalFailure = false;
411
+ let environmentalCause;
407
412
  if (uncachedQueries.length > 0) {
408
413
  let decision = "proceed";
409
414
  let fatalMessage = "";
415
+ let isEnvironmental = false;
410
416
  if (mode === "non-blocking") decision = "degradeAll";
411
417
  else try {
412
418
  const state = await getWarehouseState(client, warehouseId);
413
419
  decision = decidePreflight(state, mode);
414
- if (decision === "fatal") fatalMessage = `warehouse ${warehouseId} is ${state}`;
420
+ if (decision === "fatal") {
421
+ fatalMessage = `warehouse ${warehouseId} is ${state}`;
422
+ isEnvironmental = true;
423
+ environmentalCause = "unavailable";
424
+ }
415
425
  if (decision === "startWaitProceed") {
416
426
  await startWarehouse(client, warehouseId);
417
427
  const final = await waitUntilRunning(client, warehouseId, {
@@ -422,6 +432,8 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
422
432
  else {
423
433
  decision = "fatal";
424
434
  fatalMessage = `warehouse ${warehouseId} did not reach RUNNING (now ${final})`;
435
+ isEnvironmental = true;
436
+ environmentalCause = "unavailable";
425
437
  }
426
438
  }
427
439
  if (decision === "waitThenProceed") {
@@ -430,26 +442,38 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
430
442
  else {
431
443
  decision = "fatal";
432
444
  fatalMessage = `warehouse ${warehouseId} did not reach RUNNING (now ${final})`;
445
+ isEnvironmental = true;
446
+ environmentalCause = "unavailable";
433
447
  }
434
448
  }
435
449
  } catch (err) {
436
- if (isConnectivityError(err)) decision = "degradeAll";
437
- else {
450
+ if (isConnectivityError(err)) {
451
+ decision = "degradeAll";
452
+ isEnvironmental = true;
453
+ environmentalCause = "unreachable";
454
+ } else if (classifyBlockingFailure(err) === "deterministic") {
438
455
  decision = "fatal";
439
456
  fatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;
457
+ isEnvironmental = false;
458
+ } else {
459
+ decision = "degradeAll";
460
+ isEnvironmental = true;
461
+ environmentalCause = classifyEnvironmentalCause(err);
462
+ fatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;
440
463
  }
441
464
  }
465
+ if (mode === "blocking" && (decision === "degradeAll" && isEnvironmental || decision === "fatal" && isEnvironmental)) hadEnvironmentalFailure = true;
442
466
  if (decision !== "proceed") {
443
- const kind = decision === "fatal" ? "fatal" : "connectivity";
467
+ const kind = decision === "fatal" && !isEnvironmental ? "fatal" : "connectivity";
444
468
  for (const { index, queryName, sql, sqlHash } of uncachedQueries) {
445
469
  freshResults.push({
446
470
  index,
447
471
  schema: {
448
472
  name: queryName,
449
- type: degradedType(cache, queryName, sql, sqlHash)
473
+ ...degradedType(cache, queryName, sql, sqlHash)
450
474
  }
451
475
  });
452
- if (decision === "fatal") {
476
+ if (decision === "fatal" && !isEnvironmental) {
453
477
  fatalErrors.push({
454
478
  name: queryName,
455
479
  message: fatalMessage
@@ -483,7 +507,8 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
483
507
  index,
484
508
  schema: {
485
509
  name: queryName,
486
- type: generateUnknownResultQuery(sql, queryName)
510
+ type: generateUnknownResultQuery(sql, queryName),
511
+ degraded: true
487
512
  },
488
513
  error: withIdentifierHint(parseError(sqlError), sql)
489
514
  };
@@ -493,7 +518,7 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
493
518
  index,
494
519
  schema: {
495
520
  name: queryName,
496
- type: degradedType(cache, queryName, sql, sqlHash)
521
+ ...degradedType(cache, queryName, sql, sqlHash)
497
522
  }
498
523
  };
499
524
  const { type, hasResults } = convertToQueryType(result, sql, queryName);
@@ -502,7 +527,8 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
502
527
  index,
503
528
  schema: {
504
529
  name: queryName,
505
- type
530
+ type,
531
+ degraded: true
506
532
  }
507
533
  };
508
534
  return {
@@ -551,11 +577,17 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
551
577
  status: "MISS",
552
578
  kind: "empty"
553
579
  });
554
- else logEntries.push({
555
- queryName,
556
- status: "MISS",
557
- kind: "connectivity"
558
- });
580
+ else {
581
+ if (mode === "blocking") {
582
+ hadEnvironmentalFailure = true;
583
+ environmentalCause = environmentalCause ?? "unavailable";
584
+ }
585
+ logEntries.push({
586
+ queryName,
587
+ status: "MISS",
588
+ kind: "connectivity"
589
+ });
590
+ }
559
591
  } else {
560
592
  completed++;
561
593
  spinner.update(`Describing ${total} ${total === 1 ? "query" : "queries"} (${completed}/${total})`);
@@ -564,12 +596,12 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
564
596
  const error = parseError(reason);
565
597
  const priorEntry = cache.queries[queryName];
566
598
  const canReusePrior = priorEntry?.hash === sqlHash && !priorEntry.retry;
567
- const type = degradedType(cache, queryName, sql, sqlHash);
599
+ const degraded = degradedType(cache, queryName, sql, sqlHash);
568
600
  freshResults.push({
569
601
  index,
570
602
  schema: {
571
603
  name: queryName,
572
- type
604
+ ...degraded
573
605
  }
574
606
  });
575
607
  if (!isConnectivityError(entry.reason)) {
@@ -585,6 +617,10 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
585
617
  });
586
618
  continue;
587
619
  }
620
+ if (mode === "blocking") {
621
+ hadEnvironmentalFailure = true;
622
+ environmentalCause = environmentalCause ?? "unreachable";
623
+ }
588
624
  logger.warn("DESCRIBE unreachable for %s: %s — %s", queryName, reason, canReusePrior ? "reusing last cached type" : "emitting unknown (no matching cache)");
589
625
  logEntries.push({
590
626
  queryName,
@@ -655,7 +691,9 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
655
691
  return {
656
692
  schemas: [...cachedResults, ...freshResults].sort((a, b) => a.index - b.index).map((r) => r.schema),
657
693
  syntaxErrors,
658
- fatalErrors
694
+ fatalErrors,
695
+ hadEnvironmentalFailure,
696
+ environmentalCause: hadEnvironmentalFailure ? environmentalCause : void 0
659
697
  };
660
698
  }
661
699
  /**
@@ -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 { tableFromIPC } from \"apache-arrow\";\nimport pc from \"picocolors\";\nimport { createLogger } from \"../logging/logger\";\nimport { createWorkspaceClient } from \"../workspace-client\";\nimport { CACHE_VERSION, hashSQL, loadCache, saveCache } from \"./cache\";\nimport { getErrorDiagnostic, isConnectivityError } from \"./errors\";\nimport { decidePreflight, type PreflightMode } from \"./preflight\";\nimport { Spinner } from \"./spinner\";\nimport { type DescribeFormatMemo, describeAdaptive } from \"./statement-result\";\nimport {\n type DatabricksStatementExecutionResponse,\n type QueryFatalError,\n type QueryGenerationResult,\n type QuerySchema,\n type QuerySyntaxError,\n sqlTypeToHelper,\n sqlTypeToMarker,\n} from \"./types\";\nimport {\n getWarehouseState,\n startWarehouse,\n waitUntilRunning,\n} from \"./warehouse-status\";\n\nconst logger = createLogger(\"type-generator:query-registry\");\n\n/**\n * Upper bound on how long a `blocking`-mode preflight will wait for a starting\n * warehouse to reach RUNNING before giving up (~5 min). Generous enough to ride\n * out a cold start without hanging an interactive CLI invocation indefinitely.\n */\nconst PREFLIGHT_WAIT_MAX_MS = 300_000;\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\n/**\n * Decode a base64 Arrow IPC attachment from a DESCRIBE QUERY response and\n * extract column metadata. Returns the same shape as rows parsed from the\n * legacy data_array path.\n *\n * IMPORTANT: a DESCRIBE QUERY response is itself a result *table* with rows\n * shaped like `(col_name, data_type, comment)` describing the user query's\n * output schema. We must read those rows — NOT `table.schema.fields`, which\n * would describe DESCRIBE QUERY's own output (`col_name`, `data_type`,\n * `comment`) and yield bogus types for every query.\n */\nfunction columnsFromArrowAttachment(\n attachment: string,\n): Array<{ name: string; type_name: string; comment: string | undefined }> {\n const buf = Buffer.from(attachment, \"base64\");\n const table = tableFromIPC(buf);\n return table.toArray().map((row) => {\n const obj = row.toJSON() as {\n col_name?: unknown;\n data_type?: unknown;\n comment?: unknown;\n };\n return {\n name: typeof obj.col_name === \"string\" ? obj.col_name : \"\",\n type_name:\n typeof obj.data_type === \"string\"\n ? obj.data_type.toUpperCase()\n : \"STRING\",\n comment:\n typeof obj.comment === \"string\" && obj.comment !== \"\"\n ? obj.comment\n : undefined,\n };\n });\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 let columns = dataRows.map((row) => ({\n name: row[0] || \"\",\n type_name: row[1]?.toUpperCase() || \"STRING\",\n comment: row[2] || undefined,\n }));\n\n // Fallback: serverless warehouses return ARROW_STREAM format with an inline\n // base64 attachment instead of data_array. Decode the Arrow IPC rows (the\n // DESCRIBE QUERY result table) to extract column names and types.\n if (columns.length === 0 && result.result?.attachment) {\n logger.debug(\"data_array empty, decoding Arrow IPC attachment for schema\");\n try {\n columns = columnsFromArrowAttachment(result.result.attachment);\n } catch (err) {\n logger.warn(\n \"Failed to decode Arrow IPC attachment: %s\",\n err instanceof Error ? err.message : String(err),\n );\n }\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.replace(/\\*\\//g, \"* /\")} */\\n `\n : `/** @sqlType ${column.type_name.replace(/\\*\\//g, \"* /\")} */\\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\n/**\n * Degrade gracefully when DESCRIBE can't produce a fresh schema (transient\n * connectivity outage, or a warehouse that's reachable but not ready). Reuse\n * the last-good cached type when the SQL hash is unchanged, otherwise emit\n * `unknown` from SQL alone. Never persists `result: unknown`.\n */\nfunction degradedType(\n cache: Awaited<ReturnType<typeof loadCache>>,\n queryName: string,\n sql: string,\n sqlHash: string,\n): string {\n const prior = cache.queries[queryName];\n const canReusePrior = prior?.hash === sqlHash && !prior.retry;\n return canReusePrior\n ? prior.type\n : generateUnknownResultQuery(sql, queryName);\n}\n\n// Single source of truth for the `@param` type alternation, shared by\n// extractParameterTypes and extractParameterDefaults so the two can't drift.\n// Alternation order matters: TIMESTAMP_NTZ must precede TIMESTAMP so the regex\n// engine doesn't greedy-match TIMESTAMP and leave `_NTZ` unconsumed.\nconst PARAM_TYPE_ALTERNATION =\n \"STRING|NUMERIC|DECIMAL|BIGINT|TINYINT|SMALLINT|INT|FLOAT|DOUBLE|BOOLEAN|DATE|TIMESTAMP_NTZ|TIMESTAMP|BINARY\";\n\nexport function extractParameterTypes(sql: string): Record<string, string> {\n const paramTypes: Record<string, string> = {};\n const regex = new RegExp(\n `--\\\\s*@param\\\\s+(\\\\w+)\\\\s+(${PARAM_TYPE_ALTERNATION})\\\\b`,\n \"gi\",\n );\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 * True when `raw` is already a single, well-formed SQL single-quoted string\n * literal — i.e. it opens and closes with `'` and every interior quote is part\n * of an escaped `''` pair. `'2024-01-01'` and `'O''Brien'` qualify;\n * `'a' OR 1=1 OR 'b'` does not (it has lone interior quotes), so it is treated\n * as raw content and re-escaped rather than trusted. A backslash also\n * disqualifies it: Databricks/Spark treats `\\` as an escape inside literals (so\n * `'a\\'` is unterminated), and we never trust such input — it is re-escaped.\n */\nfunction isWellFormedStringLiteral(raw: string): boolean {\n if (raw.length < 2 || !raw.startsWith(\"'\") || !raw.endsWith(\"'\")) {\n return false;\n }\n const inner = raw.slice(1, -1);\n if (inner.includes(\"\\\\\")) return false;\n return !inner.replace(/''/g, \"\").includes(\"'\");\n}\n\n/**\n * Format a user-supplied sample value as a SQL literal for substitution into\n * the build-time DESCRIBE statement. Returns `null` when the value isn't valid\n * for its type, so the caller falls back to the safe type-based placeholder\n * instead of substituting attacker-controllable text.\n *\n * The value comes from a `.sql` file that may be shared via a template or\n * dependency, so it must not be able to inject SQL into `DESCRIBE QUERY`:\n * - string-like types are always emitted as one well-formed, fully-escaped\n * single-quoted literal (a pre-quoted literal is kept as-is, anything else is\n * quoted with both `\\` and `'` doubled so neither can terminate the literal),\n * so the value can never break out of the string;\n * - numeric / boolean / binary values must match a strict literal shape and are\n * rejected (`null`) otherwise, rather than being passed through verbatim.\n */\nfunction formatSampleValue(\n sqlType: string | undefined,\n raw: string,\n): string | null {\n switch (sqlType?.toUpperCase()) {\n case \"STRING\":\n case \"DATE\":\n case \"TIMESTAMP\":\n case \"TIMESTAMP_NTZ\":\n return isWellFormedStringLiteral(raw)\n ? raw\n : `'${raw.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"''\")}'`;\n case \"NUMERIC\":\n case \"DECIMAL\":\n case \"BIGINT\":\n case \"TINYINT\":\n case \"SMALLINT\":\n case \"INT\":\n case \"FLOAT\":\n case \"DOUBLE\":\n return /^[+-]?\\d+(\\.\\d+)?$/.test(raw) ? raw : null;\n case \"BOOLEAN\":\n return /^(?:true|false)$/i.test(raw) ? raw.toLowerCase() : null;\n case \"BINARY\":\n return /^X'[0-9a-fA-F]*'$/i.test(raw) ? raw : null;\n default:\n return null;\n }\n}\n\n/**\n * Parse optional describe-time sample values from `@param` annotations, e.g.\n * `-- @param target_catalog STRING = main`. The value is substituted into the\n * SQL **only during DESCRIBE QUERY** so type generation can resolve queries\n * whose shape depends on a parameter value — most notably dynamic table names\n * via `IDENTIFIER(:target_catalog || '.schema.table')`, where the empty-string\n * default would otherwise produce malformed SQL. Runtime binding is unaffected:\n * the analytics plugin still binds the real parameter at execution time, so the\n * query stays portable across environments.\n *\n * Returns a map of parameter name to the formatted SQL literal to substitute.\n */\nexport function extractParameterDefaults(sql: string): Record<string, string> {\n const defaults: Record<string, string> = {};\n // Reuses PARAM_TYPE_ALTERNATION, then requires `= <value>` on the same line.\n // All inter-token whitespace is horizontal-only (`[^\\S\\r\\n]`, not `\\s`): `\\s`\n // matches newlines, so a value-less line like `-- @param x STRING =` would let\n // `\\s*=\\s*(.+?)` swallow the *next* line as the sample value. Restricting to\n // same-line whitespace makes such a line simply not match, so it correctly\n // falls back to the type placeholder.\n const regex = new RegExp(\n `--[^\\\\S\\\\r\\\\n]*@param[^\\\\S\\\\r\\\\n]+(\\\\w+)[^\\\\S\\\\r\\\\n]+(${PARAM_TYPE_ALTERNATION})[^\\\\S\\\\r\\\\n]*=[^\\\\S\\\\r\\\\n]*(.+?)[^\\\\S\\\\r\\\\n]*$`,\n \"gim\",\n );\n for (const match of sql.matchAll(regex)) {\n const [, paramName, paramType, rawValue] = match;\n const formatted = formatSampleValue(paramType, rawValue);\n // A value that fails type validation is dropped, not substituted: the param\n // then falls back to the safe type-based placeholder during DESCRIBE.\n if (formatted !== null) {\n defaults[paramName] = formatted;\n }\n }\n return defaults;\n}\n\n/**\n * Replace `:param` placeholders with describe-time literals so `DESCRIBE QUERY`\n * can run without bound parameters. Resolution order per parameter:\n * 1. An explicit `-- @param name TYPE = value` sample value (wins), which lets\n * dynamic table names via `IDENTIFIER(...)` resolve to a real table.\n * 2. Otherwise a placeholder default derived from the annotated/inferred type.\n * Placeholders inside string literals or comments are left untouched.\n */\nexport function substituteParametersForDescribe(sql: string): string {\n const protectedRanges = getProtectedRanges(sql);\n const annotatedTypes = extractParameterTypes(sql);\n const inferredTypes = inferParameterTypes(sql, protectedRanges);\n const parameterTypes = { ...inferredTypes, ...annotatedTypes };\n const parameterDefaults = extractParameterDefaults(sql);\n return sql.replace(\n /(?<!:):([a-zA-Z_]\\w*)/g,\n (original, paramName, offset) => {\n if (isInsideProtectedRange(offset, protectedRanges)) {\n return original;\n }\n const sampleValue = parameterDefaults[paramName];\n if (sampleValue !== undefined) {\n return sampleValue;\n }\n return defaultForType(parameterTypes[paramName]);\n },\n );\n}\n\n/**\n * Append a remediation hint when a DESCRIBE failure looks like a dynamic\n * identifier that couldn't be resolved: the query calls `IDENTIFIER(...)` and\n * has at least one parameter without a describe-time sample value. These fail\n * because typegen substitutes a placeholder default (e.g. `''`) that yields a\n * malformed or non-existent table name. Steering the user to the `= value`\n * annotation turns the fatal error into a one-line fix.\n */\nfunction withIdentifierHint(\n error: { code?: string; message: string },\n sql: string,\n): { code?: string; message: string } {\n if (!/\\bIDENTIFIER\\s*\\(/i.test(sql)) {\n return error;\n }\n const protectedRanges = getProtectedRanges(sql);\n const params = extractParameters(sql, protectedRanges);\n const defaults = extractParameterDefaults(sql);\n const unresolved = params.filter(\n (p) => !SERVER_INJECTED_PARAMS.includes(p) && defaults[p] === undefined,\n );\n if (unresolved.length === 0) {\n return error;\n }\n const example = unresolved[0];\n return {\n ...error,\n message: `${error.message}\\n Hint: this query uses IDENTIFIER() with parameter(s) ${unresolved\n .map((p) => `:${p}`)\n .join(\n \", \",\n )}. Give type generation a sample value so it can resolve the table, e.g. \\`-- @param ${example} STRING = my_catalog\\`. The runtime query still binds the real parameter.`,\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 * @param options.mode - preflight policy: \"non-blocking\" never probes the\n * warehouse and never describes (emits cached/`unknown` types and returns\n * immediately), \"blocking\" waits for a starting warehouse and starts (then\n * waits for) a stopped one, treating only a deleted/deleting warehouse as\n * fatal. Defaults to \"non-blocking\".\n * @returns an array of query schemas\n */\nexport async function generateQueriesFromDescribe(\n queryFolder: string,\n warehouseId: string,\n options: {\n noCache?: boolean;\n concurrency?: number;\n mode?: PreflightMode;\n } = {},\n): Promise<QueryGenerationResult> {\n const {\n noCache = false,\n concurrency: rawConcurrency = 10,\n mode = \"non-blocking\",\n } = 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 = createWorkspaceClient();\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 // Absent for clean hits/misses. \"syntax\" = bad SQL on a reachable warehouse;\n // \"connectivity\" = warehouse unreachable; \"empty\" = described but no columns;\n // \"fatal\" = non-SQL setup/request failure surfaced after .d.ts emission.\n kind?: \"syntax\" | \"connectivity\" | \"empty\" | \"fatal\";\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 // substituteParametersForDescribe applies `-- @param name TYPE = value`\n // sample values (e.g. for IDENTIFIER table names) ahead of type defaults;\n // parameterDefaults is recomputed here only to skip them in the warn loop.\n const parameterDefaults = extractParameterDefaults(sql);\n const sqlWithDefaults = substituteParametersForDescribe(sql);\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 (parameterDefaults[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 // Described successfully with a result schema — the only case we cache.\n status: \"ok\";\n index: number;\n schema: QuerySchema;\n cacheEntry: { hash: string; type: string; retry: boolean };\n }\n | {\n // Reachable warehouse ran DESCRIBE and rejected the statement — a\n // genuine SQL error. Eligible to fail the build; never cached.\n status: \"syntax\";\n index: number;\n schema: QuerySchema;\n error: { code?: string; message: string };\n }\n | {\n // DESCRIBE succeeded but returned no columns — soft `unknown`. Not a\n // failure, not cached, retried next run.\n status: \"empty\";\n index: number;\n schema: QuerySchema;\n }\n | {\n // Warehouse reachable but returned a non-terminal state (PENDING/\n // RUNNING) with no rows — stopped/cold-starting/busy. Degrade like a\n // transient outage (reuse cache or `unknown`); not cached, not empty.\n status: \"unavailable\";\n index: number;\n schema: QuerySchema;\n };\n\n const freshResults: Array<{ index: number; schema: QuerySchema }> = [];\n // Genuine SQL errors (reachable warehouse). Connectivity failures are NOT\n // recorded here — they degrade silently so a transient outage isn't fatal.\n const syntaxErrors: QuerySyntaxError[] = [];\n const fatalErrors: QueryFatalError[] = [];\n\n if (uncachedQueries.length > 0) {\n // One-time warehouse preflight (before issuing any DESCRIBE). A single\n // warehouses.get classifies the warehouse so we can skip the whole describe\n // batch when it can't serve this run, instead of letting every query fail\n // (and re-fail next run). Reuses this file's degrade/classify helpers so a\n // not-ready warehouse degrades exactly like a per-query outage.\n let decision: ReturnType<typeof decidePreflight> = \"proceed\";\n let fatalMessage = \"\";\n if (mode === \"non-blocking\") {\n // `non-blocking` never describes and must make ZERO warehouse round-trips:\n // skip the probe entirely (no getWarehouseState) and go straight to\n // degradeAll. A foreground/one-shot run can't describe in the background,\n // so it emits best-available types (reused cache or `unknown`) and returns\n // now.\n decision = \"degradeAll\";\n } else {\n try {\n const state = await getWarehouseState(client, warehouseId);\n decision = decidePreflight(state, mode);\n if (decision === \"fatal\") {\n fatalMessage = `warehouse ${warehouseId} is ${state}`;\n }\n if (decision === \"startWaitProceed\") {\n // Stopped/stopping warehouse: nudge it out of the stopped state, then\n // poll to RUNNING. treatStoppedAsTransient rides out the stale\n // pre-start STOPPED/STOPPING reading the start hasn't propagated past\n // yet — only DELETED/DELETING (or the deadline) ends the wait early.\n await startWarehouse(client, warehouseId);\n const final = await waitUntilRunning(client, warehouseId, {\n maxMs: PREFLIGHT_WAIT_MAX_MS,\n treatStoppedAsTransient: true,\n });\n if (final === \"RUNNING\") {\n decision = \"proceed\";\n } else {\n decision = \"fatal\";\n fatalMessage = `warehouse ${warehouseId} did not reach RUNNING (now ${final})`;\n }\n }\n if (decision === \"waitThenProceed\") {\n const final = await waitUntilRunning(client, warehouseId, {\n maxMs: PREFLIGHT_WAIT_MAX_MS,\n });\n if (final === \"RUNNING\") {\n decision = \"proceed\";\n } else {\n decision = \"fatal\";\n fatalMessage = `warehouse ${warehouseId} did not reach RUNNING (now ${final})`;\n }\n }\n } catch (err) {\n if (isConnectivityError(err)) {\n // Warehouse unreachable (transient outage): degrade silently like a\n // per-query connectivity failure — never fail a build on a blip.\n decision = \"degradeAll\";\n } else {\n // Auth, bad warehouse id, malformed config, or a timed-out wait: fatal.\n decision = \"fatal\";\n fatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n }\n }\n }\n\n if (decision !== \"proceed\") {\n // degradeAll or fatal: skip DESCRIBE entirely. Every uncached query gets a\n // degraded schema (reused cache or `unknown`); fatal additionally records\n // a fatalError per query so the caller fails the build after writing.\n const kind = decision === \"fatal\" ? \"fatal\" : \"connectivity\";\n for (const { index, queryName, sql, sqlHash } of uncachedQueries) {\n freshResults.push({\n index,\n schema: {\n name: queryName,\n type: degradedType(cache, queryName, sql, sqlHash),\n },\n });\n if (decision === \"fatal\") {\n fatalErrors.push({ name: queryName, message: fatalMessage });\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind,\n error: { message: fatalMessage },\n });\n } else {\n logEntries.push({ queryName, status: \"MISS\", kind });\n }\n }\n } else {\n let completed = 0;\n const total = uncachedQueries.length;\n spinner.start(\n `Describing ${total} ${total === 1 ? \"query\" : \"queries\"} (0/${total})`,\n );\n\n // Shared across this run's DESCRIBE QUERY calls: discover the warehouse's\n // result format once, then reuse it.\n const describeFormat: DescribeFormatMemo = {};\n\n const describeOne = async ({\n index,\n queryName,\n sql,\n sqlHash,\n cleanedSql,\n }: (typeof uncachedQueries)[number]): Promise<DescribeResult> => {\n // describeAdaptive negotiates the result format with the warehouse\n // (JSON_ARRAY for standard DBSQL, ARROW_STREAM for Reyden) and returns\n // an already-normalized response, so the state classification below\n // reads it directly.\n const result = await describeAdaptive(\n client,\n `DESCRIBE QUERY ${cleanedSql}`,\n warehouseId,\n describeFormat,\n );\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, hasAttachment=%s\",\n queryName,\n result.status.state,\n result.result?.data_array?.length ?? 0,\n !!result.result?.attachment,\n );\n\n if (result.status.state === \"FAILED\") {\n // The warehouse was reachable and ran DESCRIBE, but the statement\n // failed — a genuine SQL error (bad table, syntax, incompatible type).\n const sqlError =\n result.status.error?.message || \"Query execution failed\";\n // The failure is surfaced once, formatted, by the aggregated\n // TypegenSyntaxError (and the summary table) — don't also log the raw\n // message here or every SQL error prints twice in dev.\n const type = generateUnknownResultQuery(sql, queryName);\n return {\n status: \"syntax\",\n index,\n schema: { name: queryName, type },\n error: withIdentifierHint(parseError(sqlError), sql),\n };\n }\n\n if (result.status.state !== \"SUCCEEDED\") {\n // Non-terminal state (PENDING/RUNNING) with no result rows: the\n // warehouse is reachable but not ready (stopped, cold-starting, or\n // busy). Degrade like a transient outage — reuse the last-good cached\n // type when the SQL is unchanged, else emit `unknown`. Never \"empty\":\n // treating this as empty would emit `result: unknown` AND discard the\n // good cached type, silently throwing away working types.\n return {\n status: \"unavailable\",\n index,\n schema: {\n name: queryName,\n type: degradedType(cache, queryName, sql, sqlHash),\n },\n };\n }\n\n const { type, hasResults } = convertToQueryType(result, sql, queryName);\n if (!hasResults) {\n // Described, but no result columns. Emit `unknown` and retry next run;\n // do not cache (we never persist `result: unknown`).\n return { status: \"empty\", index, schema: { name: queryName, type } };\n }\n return {\n status: \"ok\",\n index,\n schema: { name: queryName, type },\n cacheEntry: { hash: sqlHash, type, retry: false },\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\n if (res.status === \"ok\") {\n // Only a successful describe with a result schema is cached.\n cache.queries[queryName] = res.cacheEntry;\n logEntries.push({ queryName, status: \"MISS\" });\n } else if (res.status === \"syntax\") {\n // Genuine SQL error — record it for the caller's prod/dev gate.\n // Not cached: re-described next run so a fixed query recovers.\n syntaxErrors.push({\n name: queryName,\n message: res.error.message,\n });\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind: \"syntax\",\n error: res.error,\n });\n } else if (res.status === \"empty\") {\n // status === \"empty\": described, no columns. Soft unknown, not cached.\n logEntries.push({ queryName, status: \"MISS\", kind: \"empty\" });\n } else {\n // status === \"unavailable\": non-terminal DESCRIBE (warehouse\n // stopped/cold-starting/busy). Degrade like a transient outage:\n // tag OFFLINE, count as degraded, never cache.\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind: \"connectivity\",\n });\n }\n } else {\n // executeStatement rejected without a normal StatementExecution result.\n // Only structured transport/connectivity failures are treated as\n // offline; auth, bad warehouse IDs, malformed requests, and SDK/config\n // failures stay fatal so users fix the underlying setup issue.\n completed++;\n spinner.update(\n `Describing ${total} ${total === 1 ? \"query\" : \"queries\"} (${completed}/${total})`,\n );\n\n const { sql, sqlHash, index } = uncachedQueries[batchOffset + i];\n const reason = getErrorDiagnostic(entry.reason);\n const error = parseError(reason);\n const priorEntry = cache.queries[queryName];\n const canReusePrior =\n priorEntry?.hash === sqlHash && !priorEntry.retry;\n const type = degradedType(cache, queryName, sql, sqlHash);\n freshResults.push({ index, schema: { name: queryName, type } });\n\n if (!isConnectivityError(entry.reason)) {\n fatalErrors.push({ name: queryName, message: error.message });\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind: \"fatal\",\n error,\n });\n continue;\n }\n\n logger.warn(\n \"DESCRIBE unreachable for %s: %s — %s\",\n queryName,\n reason,\n canReusePrior\n ? \"reusing last cached type\"\n : \"emitting unknown (no matching cache)\",\n );\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind: \"connectivity\",\n error,\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\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 let tag: string;\n switch (entry.kind) {\n case \"syntax\":\n tag = pc.bold(pc.red(\"SQL ERR\"));\n break;\n case \"connectivity\":\n tag = pc.bold(pc.yellow(\"OFFLINE\"));\n break;\n case \"empty\":\n tag = pc.dim(\"EMPTY \");\n break;\n case \"fatal\":\n tag = pc.bold(pc.red(\"FATAL \"));\n break;\n default:\n tag =\n entry.status === \"HIT\"\n ? `cache ${pc.bold(pc.green(\"HIT \"))}`\n : `cache ${pc.bold(pc.yellow(\"MISS \"))}`;\n }\n const rawName = entry.queryName.padEnd(maxNameLen);\n // Only genuine SQL errors are struck through. Connectivity/empty kept a\n // usable type (reused or unknown), so they read as degraded, not broken.\n const name =\n entry.kind === \"syntax\" || entry.kind === \"fatal\"\n ? pc.dim(pc.strikethrough(rawName))\n : 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.kind,\n ).length;\n const cacheCount = logEntries.filter((e) => e.status === \"HIT\").length;\n const syntaxCount = logEntries.filter((e) => e.kind === \"syntax\").length;\n const offlineCount = logEntries.filter(\n (e) => e.kind === \"connectivity\",\n ).length;\n const emptyCount = logEntries.filter((e) => e.kind === \"empty\").length;\n const fatalCount = logEntries.filter((e) => e.kind === \"fatal\").length;\n console.log(` ${separator}`);\n const parts = [`${newCount} new`, `${cacheCount} from cache`];\n if (syntaxCount > 0)\n parts.push(\n `${syntaxCount} SQL ${syntaxCount === 1 ? \"error\" : \"errors\"}`,\n );\n if (offlineCount > 0) parts.push(`${offlineCount} degraded`);\n if (emptyCount > 0) parts.push(`${emptyCount} empty`);\n if (fatalCount > 0)\n parts.push(\n `${fatalCount} fatal ${fatalCount === 1 ? \"error\" : \"errors\"}`,\n );\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 const schemas = [...cachedResults, ...freshResults]\n .sort((a, b) => a.index - b.index)\n .map((r) => r.schema);\n\n return { schemas, syntaxErrors, fatalErrors };\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 { tableFromIPC } from \"apache-arrow\";\nimport pc from \"picocolors\";\nimport { createLogger } from \"../logging/logger\";\nimport { createWorkspaceClient } from \"../workspace-client\";\nimport { CACHE_VERSION, hashSQL, loadCache, saveCache } from \"./cache\";\nimport {\n classifyBlockingFailure,\n classifyEnvironmentalCause,\n getErrorDiagnostic,\n isConnectivityError,\n} from \"./errors\";\nimport { decidePreflight, type PreflightMode } from \"./preflight\";\nimport { Spinner } from \"./spinner\";\nimport { type DescribeFormatMemo, describeAdaptive } from \"./statement-result\";\nimport {\n type DatabricksStatementExecutionResponse,\n type QueryFatalError,\n type QueryGenerationResult,\n type QuerySchema,\n type QuerySyntaxError,\n sqlTypeToHelper,\n sqlTypeToMarker,\n} from \"./types\";\nimport {\n getWarehouseState,\n startWarehouse,\n waitUntilRunning,\n} from \"./warehouse-status\";\n\nconst logger = createLogger(\"type-generator:query-registry\");\n\n/**\n * Upper bound on how long a `blocking`-mode preflight will wait for a starting\n * warehouse to reach RUNNING before giving up (~5 min). Generous enough to ride\n * out a cold start without hanging an interactive CLI invocation indefinitely.\n */\nconst PREFLIGHT_WAIT_MAX_MS = 300_000;\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\n/**\n * Decode a base64 Arrow IPC attachment from a DESCRIBE QUERY response and\n * extract column metadata. Returns the same shape as rows parsed from the\n * legacy data_array path.\n *\n * IMPORTANT: a DESCRIBE QUERY response is itself a result *table* with rows\n * shaped like `(col_name, data_type, comment)` describing the user query's\n * output schema. We must read those rows — NOT `table.schema.fields`, which\n * would describe DESCRIBE QUERY's own output (`col_name`, `data_type`,\n * `comment`) and yield bogus types for every query.\n */\nfunction columnsFromArrowAttachment(\n attachment: string,\n): Array<{ name: string; type_name: string; comment: string | undefined }> {\n const buf = Buffer.from(attachment, \"base64\");\n const table = tableFromIPC(buf);\n return table.toArray().map((row) => {\n const obj = row.toJSON() as {\n col_name?: unknown;\n data_type?: unknown;\n comment?: unknown;\n };\n return {\n name: typeof obj.col_name === \"string\" ? obj.col_name : \"\",\n type_name:\n typeof obj.data_type === \"string\"\n ? obj.data_type.toUpperCase()\n : \"STRING\",\n comment:\n typeof obj.comment === \"string\" && obj.comment !== \"\"\n ? obj.comment\n : undefined,\n };\n });\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 let columns = dataRows.map((row) => ({\n name: row[0] || \"\",\n type_name: row[1]?.toUpperCase() || \"STRING\",\n comment: row[2] || undefined,\n }));\n\n // Fallback: serverless warehouses return ARROW_STREAM format with an inline\n // base64 attachment instead of data_array. Decode the Arrow IPC rows (the\n // DESCRIBE QUERY result table) to extract column names and types.\n if (columns.length === 0 && result.result?.attachment) {\n logger.debug(\"data_array empty, decoding Arrow IPC attachment for schema\");\n try {\n columns = columnsFromArrowAttachment(result.result.attachment);\n } catch (err) {\n logger.warn(\n \"Failed to decode Arrow IPC attachment: %s\",\n err instanceof Error ? err.message : String(err),\n );\n }\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.replace(/\\*\\//g, \"* /\")} */\\n `\n : `/** @sqlType ${column.type_name.replace(/\\*\\//g, \"* /\")} */\\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\n/**\n * Degrade gracefully when DESCRIBE can't produce a fresh schema (transient\n * connectivity outage, or a warehouse that's reachable but not ready). Reuse\n * the last-good cached type when the SQL hash is unchanged, otherwise emit\n * `unknown` from SQL alone. Never persists `result: unknown`.\n */\nfunction degradedType(\n cache: Awaited<ReturnType<typeof loadCache>>,\n queryName: string,\n sql: string,\n sqlHash: string,\n): Pick<QuerySchema, \"type\" | \"degraded\"> {\n const prior = cache.queries[queryName];\n const canReusePrior = prior?.hash === sqlHash && !prior.retry;\n return canReusePrior\n ? { type: prior.type }\n : {\n type: generateUnknownResultQuery(sql, queryName),\n degraded: true,\n };\n}\n\n// Single source of truth for the `@param` type alternation, shared by\n// extractParameterTypes and extractParameterDefaults so the two can't drift.\n// Alternation order matters: TIMESTAMP_NTZ must precede TIMESTAMP so the regex\n// engine doesn't greedy-match TIMESTAMP and leave `_NTZ` unconsumed.\nconst PARAM_TYPE_ALTERNATION =\n \"STRING|NUMERIC|DECIMAL|BIGINT|TINYINT|SMALLINT|INT|FLOAT|DOUBLE|BOOLEAN|DATE|TIMESTAMP_NTZ|TIMESTAMP|BINARY\";\n\nexport function extractParameterTypes(sql: string): Record<string, string> {\n const paramTypes: Record<string, string> = {};\n const regex = new RegExp(\n `--\\\\s*@param\\\\s+(\\\\w+)\\\\s+(${PARAM_TYPE_ALTERNATION})\\\\b`,\n \"gi\",\n );\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 * True when `raw` is already a single, well-formed SQL single-quoted string\n * literal — i.e. it opens and closes with `'` and every interior quote is part\n * of an escaped `''` pair. `'2024-01-01'` and `'O''Brien'` qualify;\n * `'a' OR 1=1 OR 'b'` does not (it has lone interior quotes), so it is treated\n * as raw content and re-escaped rather than trusted. A backslash also\n * disqualifies it: Databricks/Spark treats `\\` as an escape inside literals (so\n * `'a\\'` is unterminated), and we never trust such input — it is re-escaped.\n */\nfunction isWellFormedStringLiteral(raw: string): boolean {\n if (raw.length < 2 || !raw.startsWith(\"'\") || !raw.endsWith(\"'\")) {\n return false;\n }\n const inner = raw.slice(1, -1);\n if (inner.includes(\"\\\\\")) return false;\n return !inner.replace(/''/g, \"\").includes(\"'\");\n}\n\n/**\n * Format a user-supplied sample value as a SQL literal for substitution into\n * the build-time DESCRIBE statement. Returns `null` when the value isn't valid\n * for its type, so the caller falls back to the safe type-based placeholder\n * instead of substituting attacker-controllable text.\n *\n * The value comes from a `.sql` file that may be shared via a template or\n * dependency, so it must not be able to inject SQL into `DESCRIBE QUERY`:\n * - string-like types are always emitted as one well-formed, fully-escaped\n * single-quoted literal (a pre-quoted literal is kept as-is, anything else is\n * quoted with both `\\` and `'` doubled so neither can terminate the literal),\n * so the value can never break out of the string;\n * - numeric / boolean / binary values must match a strict literal shape and are\n * rejected (`null`) otherwise, rather than being passed through verbatim.\n */\nfunction formatSampleValue(\n sqlType: string | undefined,\n raw: string,\n): string | null {\n switch (sqlType?.toUpperCase()) {\n case \"STRING\":\n case \"DATE\":\n case \"TIMESTAMP\":\n case \"TIMESTAMP_NTZ\":\n return isWellFormedStringLiteral(raw)\n ? raw\n : `'${raw.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"''\")}'`;\n case \"NUMERIC\":\n case \"DECIMAL\":\n case \"BIGINT\":\n case \"TINYINT\":\n case \"SMALLINT\":\n case \"INT\":\n case \"FLOAT\":\n case \"DOUBLE\":\n return /^[+-]?\\d+(\\.\\d+)?$/.test(raw) ? raw : null;\n case \"BOOLEAN\":\n return /^(?:true|false)$/i.test(raw) ? raw.toLowerCase() : null;\n case \"BINARY\":\n return /^X'[0-9a-fA-F]*'$/i.test(raw) ? raw : null;\n default:\n return null;\n }\n}\n\n/**\n * Parse optional describe-time sample values from `@param` annotations, e.g.\n * `-- @param target_catalog STRING = main`. The value is substituted into the\n * SQL **only during DESCRIBE QUERY** so type generation can resolve queries\n * whose shape depends on a parameter value — most notably dynamic table names\n * via `IDENTIFIER(:target_catalog || '.schema.table')`, where the empty-string\n * default would otherwise produce malformed SQL. Runtime binding is unaffected:\n * the analytics plugin still binds the real parameter at execution time, so the\n * query stays portable across environments.\n *\n * Returns a map of parameter name to the formatted SQL literal to substitute.\n */\nexport function extractParameterDefaults(sql: string): Record<string, string> {\n const defaults: Record<string, string> = {};\n // Reuses PARAM_TYPE_ALTERNATION, then requires `= <value>` on the same line.\n // All inter-token whitespace is horizontal-only (`[^\\S\\r\\n]`, not `\\s`): `\\s`\n // matches newlines, so a value-less line like `-- @param x STRING =` would let\n // `\\s*=\\s*(.+?)` swallow the *next* line as the sample value. Restricting to\n // same-line whitespace makes such a line simply not match, so it correctly\n // falls back to the type placeholder.\n const regex = new RegExp(\n `--[^\\\\S\\\\r\\\\n]*@param[^\\\\S\\\\r\\\\n]+(\\\\w+)[^\\\\S\\\\r\\\\n]+(${PARAM_TYPE_ALTERNATION})[^\\\\S\\\\r\\\\n]*=[^\\\\S\\\\r\\\\n]*(.+?)[^\\\\S\\\\r\\\\n]*$`,\n \"gim\",\n );\n for (const match of sql.matchAll(regex)) {\n const [, paramName, paramType, rawValue] = match;\n const formatted = formatSampleValue(paramType, rawValue);\n // A value that fails type validation is dropped, not substituted: the param\n // then falls back to the safe type-based placeholder during DESCRIBE.\n if (formatted !== null) {\n defaults[paramName] = formatted;\n }\n }\n return defaults;\n}\n\n/**\n * Replace `:param` placeholders with describe-time literals so `DESCRIBE QUERY`\n * can run without bound parameters. Resolution order per parameter:\n * 1. An explicit `-- @param name TYPE = value` sample value (wins), which lets\n * dynamic table names via `IDENTIFIER(...)` resolve to a real table.\n * 2. Otherwise a placeholder default derived from the annotated/inferred type.\n * Placeholders inside string literals or comments are left untouched.\n */\nexport function substituteParametersForDescribe(sql: string): string {\n const protectedRanges = getProtectedRanges(sql);\n const annotatedTypes = extractParameterTypes(sql);\n const inferredTypes = inferParameterTypes(sql, protectedRanges);\n const parameterTypes = { ...inferredTypes, ...annotatedTypes };\n const parameterDefaults = extractParameterDefaults(sql);\n return sql.replace(\n /(?<!:):([a-zA-Z_]\\w*)/g,\n (original, paramName, offset) => {\n if (isInsideProtectedRange(offset, protectedRanges)) {\n return original;\n }\n const sampleValue = parameterDefaults[paramName];\n if (sampleValue !== undefined) {\n return sampleValue;\n }\n return defaultForType(parameterTypes[paramName]);\n },\n );\n}\n\n/**\n * Append a remediation hint when a DESCRIBE failure looks like a dynamic\n * identifier that couldn't be resolved: the query calls `IDENTIFIER(...)` and\n * has at least one parameter without a describe-time sample value. These fail\n * because typegen substitutes a placeholder default (e.g. `''`) that yields a\n * malformed or non-existent table name. Steering the user to the `= value`\n * annotation turns the fatal error into a one-line fix.\n */\nfunction withIdentifierHint(\n error: { code?: string; message: string },\n sql: string,\n): { code?: string; message: string } {\n if (!/\\bIDENTIFIER\\s*\\(/i.test(sql)) {\n return error;\n }\n const protectedRanges = getProtectedRanges(sql);\n const params = extractParameters(sql, protectedRanges);\n const defaults = extractParameterDefaults(sql);\n const unresolved = params.filter(\n (p) => !SERVER_INJECTED_PARAMS.includes(p) && defaults[p] === undefined,\n );\n if (unresolved.length === 0) {\n return error;\n }\n const example = unresolved[0];\n return {\n ...error,\n message: `${error.message}\\n Hint: this query uses IDENTIFIER() with parameter(s) ${unresolved\n .map((p) => `:${p}`)\n .join(\n \", \",\n )}. Give type generation a sample value so it can resolve the table, e.g. \\`-- @param ${example} STRING = my_catalog\\`. The runtime query still binds the real parameter.`,\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 * @param options.mode - preflight policy: \"non-blocking\" never probes the\n * warehouse and never describes (emits cached/`unknown` types and returns\n * immediately), \"blocking\" waits for a starting warehouse and starts (then\n * waits for) a stopped one, treating only a deleted/deleting warehouse as\n * fatal. Defaults to \"non-blocking\".\n * @returns an array of query schemas\n */\nexport async function generateQueriesFromDescribe(\n queryFolder: string,\n warehouseId: string,\n options: {\n noCache?: boolean;\n concurrency?: number;\n mode?: PreflightMode;\n } = {},\n): Promise<QueryGenerationResult> {\n const {\n noCache = false,\n concurrency: rawConcurrency = 10,\n mode = \"non-blocking\",\n } = 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 = createWorkspaceClient();\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 // Absent for clean hits/misses. \"syntax\" = bad SQL on a reachable warehouse;\n // \"connectivity\" = warehouse unreachable; \"empty\" = described but no columns;\n // \"fatal\" = non-SQL setup/request failure surfaced after .d.ts emission.\n kind?: \"syntax\" | \"connectivity\" | \"empty\" | \"fatal\";\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 // substituteParametersForDescribe applies `-- @param name TYPE = value`\n // sample values (e.g. for IDENTIFIER table names) ahead of type defaults;\n // parameterDefaults is recomputed here only to skip them in the warn loop.\n const parameterDefaults = extractParameterDefaults(sql);\n const sqlWithDefaults = substituteParametersForDescribe(sql);\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 (parameterDefaults[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 // Described successfully with a result schema — the only case we cache.\n status: \"ok\";\n index: number;\n schema: QuerySchema;\n cacheEntry: { hash: string; type: string; retry: boolean };\n }\n | {\n // Reachable warehouse ran DESCRIBE and rejected the statement — a\n // genuine SQL error. Eligible to fail the build; never cached.\n status: \"syntax\";\n index: number;\n schema: QuerySchema;\n error: { code?: string; message: string };\n }\n | {\n // DESCRIBE succeeded but returned no columns — soft `unknown`. Not a\n // failure, not cached, retried next run.\n status: \"empty\";\n index: number;\n schema: QuerySchema;\n }\n | {\n // Warehouse reachable but returned a non-terminal state (PENDING/\n // RUNNING) with no rows — stopped/cold-starting/busy. Degrade like a\n // transient outage (reuse cache or `unknown`); not cached, not empty.\n status: \"unavailable\";\n index: number;\n schema: QuerySchema;\n };\n\n const freshResults: Array<{ index: number; schema: QuerySchema }> = [];\n // Genuine SQL errors (reachable warehouse). Connectivity failures are NOT\n // recorded here — they degrade silently so a transient outage isn't fatal.\n const syntaxErrors: QuerySyntaxError[] = [];\n // Deterministic fatal errors only (404/400). Environmental failures are\n // tracked separately below.\n const fatalErrors: QueryFatalError[] = [];\n // Track whether an environmental failure occurred in blocking mode (for the\n // has-types gate in generateFromEntryPoint), plus its coarse cause so the\n // gate's warning can say why generation fell back to committed types.\n let hadEnvironmentalFailure = false;\n let environmentalCause: \"auth\" | \"unreachable\" | \"unavailable\" | undefined;\n\n if (uncachedQueries.length > 0) {\n // One-time warehouse preflight (before issuing any DESCRIBE). A single\n // warehouses.get classifies the warehouse so we can skip the whole describe\n // batch when it can't serve this run, instead of letting every query fail\n // (and re-fail next run). Reuses this file's degrade/classify helpers so a\n // not-ready warehouse degrades exactly like a per-query outage.\n let decision: ReturnType<typeof decidePreflight> = \"proceed\";\n let fatalMessage = \"\";\n // Track whether an environmental failure occurred so the caller's has-types\n // gate can decide crash-vs-fall-back.\n let isEnvironmental = false;\n if (mode === \"non-blocking\") {\n // `non-blocking` never describes and must make ZERO warehouse round-trips:\n // skip the probe entirely (no getWarehouseState) and go straight to\n // degradeAll. A foreground/one-shot run can't describe in the background,\n // so it emits best-available types (reused cache or `unknown`) and returns\n // now.\n decision = \"degradeAll\";\n } else {\n try {\n const state = await getWarehouseState(client, warehouseId);\n decision = decidePreflight(state, mode);\n if (decision === \"fatal\") {\n // DELETED/DELETING is state-based and environmental.\n fatalMessage = `warehouse ${warehouseId} is ${state}`;\n isEnvironmental = true;\n environmentalCause = \"unavailable\";\n }\n if (decision === \"startWaitProceed\") {\n // Stopped/stopping warehouse: nudge it out of the stopped state, then\n // poll to RUNNING. treatStoppedAsTransient rides out the stale\n // pre-start STOPPED/STOPPING reading the start hasn't propagated past\n // yet — only DELETED/DELETING (or the deadline) ends the wait early.\n await startWarehouse(client, warehouseId);\n const final = await waitUntilRunning(client, warehouseId, {\n maxMs: PREFLIGHT_WAIT_MAX_MS,\n treatStoppedAsTransient: true,\n });\n if (final === \"RUNNING\") {\n decision = \"proceed\";\n } else {\n decision = \"fatal\";\n fatalMessage = `warehouse ${warehouseId} did not reach RUNNING (now ${final})`;\n isEnvironmental = true; // DELETED/DELETING or timeout is environmental\n environmentalCause = \"unavailable\";\n }\n }\n if (decision === \"waitThenProceed\") {\n const final = await waitUntilRunning(client, warehouseId, {\n maxMs: PREFLIGHT_WAIT_MAX_MS,\n });\n if (final === \"RUNNING\") {\n decision = \"proceed\";\n } else {\n decision = \"fatal\";\n fatalMessage = `warehouse ${warehouseId} did not reach RUNNING (now ${final})`;\n isEnvironmental = true; // DELETED/DELETING or timeout is environmental\n environmentalCause = \"unavailable\";\n }\n }\n } catch (err) {\n if (isConnectivityError(err)) {\n // Warehouse unreachable (transient outage): degrade rather than fail —\n // never fail a build on a blip. Still environmental, so the caller's\n // has-types gate decides warn-and-fall-back (committed types present)\n // vs crash (fresh checkout with nothing to fall back to).\n decision = \"degradeAll\";\n isEnvironmental = true;\n environmentalCause = \"unreachable\";\n } else {\n // Classify the exception: deterministic (404/400) or environmental (auth, etc).\n const classification = classifyBlockingFailure(err);\n if (classification === \"deterministic\") {\n // Build-failing deterministic error (bad warehouse id, malformed request).\n decision = \"fatal\";\n fatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n isEnvironmental = false;\n } else {\n // Environmental: auth, timeouts, unrecognized, etc. Degrade for the\n // has-types gate to handle later.\n decision = \"degradeAll\";\n isEnvironmental = true;\n environmentalCause = classifyEnvironmentalCause(err);\n fatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n }\n }\n }\n }\n\n // Record blocking-mode environmental failures for the has-types gate.\n if (\n mode === \"blocking\" &&\n ((decision === \"degradeAll\" && isEnvironmental) ||\n (decision === \"fatal\" && isEnvironmental))\n ) {\n hadEnvironmentalFailure = true;\n }\n\n if (decision !== \"proceed\") {\n // degradeAll or fatal: skip DESCRIBE entirely. Every uncached query gets a\n // degraded schema (reused cache or `unknown`); fatal additionally records\n // a fatalError per query so the caller fails the build after writing. Only\n // deterministic fatals are recorded; environmental degradations go silent\n // so the has-types gate can decide.\n const kind =\n decision === \"fatal\" && !isEnvironmental ? \"fatal\" : \"connectivity\";\n for (const { index, queryName, sql, sqlHash } of uncachedQueries) {\n freshResults.push({\n index,\n schema: {\n name: queryName,\n ...degradedType(cache, queryName, sql, sqlHash),\n },\n });\n if (decision === \"fatal\" && !isEnvironmental) {\n // Only deterministic fatals record an error; environmental failures\n // degrade silently for the has-types gate.\n fatalErrors.push({ name: queryName, message: fatalMessage });\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind,\n error: { message: fatalMessage },\n });\n } else {\n logEntries.push({ queryName, status: \"MISS\", kind });\n }\n }\n } else {\n let completed = 0;\n const total = uncachedQueries.length;\n spinner.start(\n `Describing ${total} ${total === 1 ? \"query\" : \"queries\"} (0/${total})`,\n );\n\n // Shared across this run's DESCRIBE QUERY calls: discover the warehouse's\n // result format once, then reuse it.\n const describeFormat: DescribeFormatMemo = {};\n\n const describeOne = async ({\n index,\n queryName,\n sql,\n sqlHash,\n cleanedSql,\n }: (typeof uncachedQueries)[number]): Promise<DescribeResult> => {\n // describeAdaptive negotiates the result format with the warehouse\n // (JSON_ARRAY for standard DBSQL, ARROW_STREAM for Reyden) and returns\n // an already-normalized response, so the state classification below\n // reads it directly.\n const result = await describeAdaptive(\n client,\n `DESCRIBE QUERY ${cleanedSql}`,\n warehouseId,\n describeFormat,\n );\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, hasAttachment=%s\",\n queryName,\n result.status.state,\n result.result?.data_array?.length ?? 0,\n !!result.result?.attachment,\n );\n\n if (result.status.state === \"FAILED\") {\n // The warehouse was reachable and ran DESCRIBE, but the statement\n // failed — a genuine SQL error (bad table, syntax, incompatible type).\n const sqlError =\n result.status.error?.message || \"Query execution failed\";\n // The failure is surfaced once, formatted, by the aggregated\n // TypegenSyntaxError (and the summary table) — don't also log the raw\n // message here or every SQL error prints twice in dev.\n const type = generateUnknownResultQuery(sql, queryName);\n return {\n status: \"syntax\",\n index,\n schema: { name: queryName, type, degraded: true },\n error: withIdentifierHint(parseError(sqlError), sql),\n };\n }\n\n if (result.status.state !== \"SUCCEEDED\") {\n // Non-terminal state (PENDING/RUNNING) with no result rows: the\n // warehouse is reachable but not ready (stopped, cold-starting, or\n // busy). Degrade like a transient outage — reuse the last-good cached\n // type when the SQL is unchanged, else emit `unknown`. Never \"empty\":\n // treating this as empty would emit `result: unknown` AND discard the\n // good cached type, silently throwing away working types.\n return {\n status: \"unavailable\",\n index,\n schema: {\n name: queryName,\n ...degradedType(cache, queryName, sql, sqlHash),\n },\n };\n }\n\n const { type, hasResults } = convertToQueryType(result, sql, queryName);\n if (!hasResults) {\n // Described, but no result columns. Emit `unknown` and retry next run;\n // do not cache (we never persist `result: unknown`).\n return {\n status: \"empty\",\n index,\n schema: { name: queryName, type, degraded: true },\n };\n }\n return {\n status: \"ok\",\n index,\n schema: { name: queryName, type },\n cacheEntry: { hash: sqlHash, type, retry: false },\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\n if (res.status === \"ok\") {\n // Only a successful describe with a result schema is cached.\n cache.queries[queryName] = res.cacheEntry;\n logEntries.push({ queryName, status: \"MISS\" });\n } else if (res.status === \"syntax\") {\n // Genuine SQL error — record it for the caller's prod/dev gate.\n // Not cached: re-described next run so a fixed query recovers.\n syntaxErrors.push({\n name: queryName,\n message: res.error.message,\n });\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind: \"syntax\",\n error: res.error,\n });\n } else if (res.status === \"empty\") {\n // status === \"empty\": described, no columns. Soft unknown, not cached.\n logEntries.push({ queryName, status: \"MISS\", kind: \"empty\" });\n } else {\n // status === \"unavailable\": non-terminal DESCRIBE (warehouse\n // stopped/cold-starting/busy). Degrade like a transient outage:\n // tag OFFLINE, count as degraded, never cache.\n if (mode === \"blocking\") {\n hadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unavailable\";\n }\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind: \"connectivity\",\n });\n }\n } else {\n // executeStatement rejected without a normal StatementExecution result.\n // Only structured transport/connectivity failures are treated as\n // offline; auth, bad warehouse IDs, malformed requests, and SDK/config\n // failures stay fatal so users fix the underlying setup issue.\n completed++;\n spinner.update(\n `Describing ${total} ${total === 1 ? \"query\" : \"queries\"} (${completed}/${total})`,\n );\n\n const { sql, sqlHash, index } = uncachedQueries[batchOffset + i];\n const reason = getErrorDiagnostic(entry.reason);\n const error = parseError(reason);\n const priorEntry = cache.queries[queryName];\n const canReusePrior =\n priorEntry?.hash === sqlHash && !priorEntry.retry;\n const degraded = degradedType(cache, queryName, sql, sqlHash);\n freshResults.push({\n index,\n schema: { name: queryName, ...degraded },\n });\n\n if (!isConnectivityError(entry.reason)) {\n fatalErrors.push({ name: queryName, message: error.message });\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind: \"fatal\",\n error,\n });\n continue;\n }\n\n // Environmental for the same reason as the preflight connectivity\n // branch above, so the has-types gate still sees it.\n if (mode === \"blocking\") {\n hadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unreachable\";\n }\n\n logger.warn(\n \"DESCRIBE unreachable for %s: %s — %s\",\n queryName,\n reason,\n canReusePrior\n ? \"reusing last cached type\"\n : \"emitting unknown (no matching cache)\",\n );\n logEntries.push({\n queryName,\n status: \"MISS\",\n kind: \"connectivity\",\n error,\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\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 let tag: string;\n switch (entry.kind) {\n case \"syntax\":\n tag = pc.bold(pc.red(\"SQL ERR\"));\n break;\n case \"connectivity\":\n tag = pc.bold(pc.yellow(\"OFFLINE\"));\n break;\n case \"empty\":\n tag = pc.dim(\"EMPTY \");\n break;\n case \"fatal\":\n tag = pc.bold(pc.red(\"FATAL \"));\n break;\n default:\n tag =\n entry.status === \"HIT\"\n ? `cache ${pc.bold(pc.green(\"HIT \"))}`\n : `cache ${pc.bold(pc.yellow(\"MISS \"))}`;\n }\n const rawName = entry.queryName.padEnd(maxNameLen);\n // Only genuine SQL errors are struck through. Connectivity/empty kept a\n // usable type (reused or unknown), so they read as degraded, not broken.\n const name =\n entry.kind === \"syntax\" || entry.kind === \"fatal\"\n ? pc.dim(pc.strikethrough(rawName))\n : 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.kind,\n ).length;\n const cacheCount = logEntries.filter((e) => e.status === \"HIT\").length;\n const syntaxCount = logEntries.filter((e) => e.kind === \"syntax\").length;\n const offlineCount = logEntries.filter(\n (e) => e.kind === \"connectivity\",\n ).length;\n const emptyCount = logEntries.filter((e) => e.kind === \"empty\").length;\n const fatalCount = logEntries.filter((e) => e.kind === \"fatal\").length;\n console.log(` ${separator}`);\n const parts = [`${newCount} new`, `${cacheCount} from cache`];\n if (syntaxCount > 0)\n parts.push(\n `${syntaxCount} SQL ${syntaxCount === 1 ? \"error\" : \"errors\"}`,\n );\n if (offlineCount > 0) parts.push(`${offlineCount} degraded`);\n if (emptyCount > 0) parts.push(`${emptyCount} empty`);\n if (fatalCount > 0)\n parts.push(\n `${fatalCount} fatal ${fatalCount === 1 ? \"error\" : \"errors\"}`,\n );\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 const schemas = [...cachedResults, ...freshResults]\n .sort((a, b) => a.index - b.index)\n .map((r) => r.schema);\n\n return {\n schemas,\n syntaxErrors,\n fatalErrors,\n hadEnvironmentalFailure,\n environmentalCause: hadEnvironmentalFailure\n ? environmentalCause\n : undefined,\n };\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 +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 * DESCRIBE TABLE EXTENDED.\n *\n * Two result shapes matter here:\n * - `result.data_array` — rows already materialized as JSON arrays. Present\n * when the warehouse returns `JSON_ARRAY` (and what every mocked test\n * builds).\n * - `result.attachment` — a base64-encoded Arrow IPC stream. Present when the\n * statement runs with `format: \"ARROW_STREAM\"` + `disposition: \"INLINE\"`,\n * which is the SDK's default disposition. The single row lands here and\n * `data_array` is left undefined. {@link normalizeResultRows} decodes this\n * back into `data_array` so downstream parsers stay shape-agnostic.\n *\n * @property statement_id - the id of the statement\n * @property status - the status of the statement\n * @property manifest - result metadata; `manifest.format` echoes the wire\n * format (`ARROW_STREAM`, `JSON_ARRAY`, ...) the warehouse chose.\n * @property result - the result; either `data_array` (rows as\n * `[col_name, data_type, comment]` arrays) or `attachment` (base64 Arrow IPC)\n */\nexport interface DatabricksStatementExecutionResponse {\n statement_id: string;\n status: {\n state: string;\n error?: { error_code?: string; message?: string };\n };\n manifest?: {\n format?: string;\n };\n result?: {\n data_array?: (string | null)[][];\n /** Base64-encoded Arrow IPC stream (ARROW_STREAM + INLINE disposition). */\n attachment?: string;\n /**\n * Set when the result spans multiple chunks (rows exceeded INLINE's size\n * limit). Its presence means this response holds only the FIRST chunk;\n * {@link normalizeResultRows} throws rather than emit truncated types.\n */\n next_chunk_index?: number;\n /** Companion to {@link next_chunk_index}: link to fetch the next chunk. */\n next_chunk_internal_link?: string;\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\n/**\n * A genuine SQL error: `DESCRIBE QUERY` ran against a *reachable* warehouse and\n * the warehouse reported the statement as FAILED (bad table, syntax error,\n * incompatible type, …). Distinct from a connectivity failure (warehouse\n * unreachable), which is non-fatal and never recorded here.\n * @property name - the query name\n * @property message - the SQL error message reported by the warehouse\n */\nexport interface QuerySyntaxError {\n name: string;\n message: string;\n}\n\n/**\n * A non-SQL fatal error while attempting to describe a query: authentication,\n * authorization, invalid warehouse/configuration, malformed SDK request, or\n * any other setup problem that should not be treated as an offline warehouse.\n * @property name - the query name\n * @property message - the fatal error message\n */\nexport interface QueryFatalError {\n name: string;\n message: string;\n}\n\n/**\n * Result of describing a folder of queries.\n * @property schemas - one schema per query, in original file order. Queries that\n * could not be described carry `result: unknown` so output stays valid.\n * @property syntaxErrors - queries whose DESCRIBE failed against a reachable\n * warehouse (genuine SQL errors). Connectivity failures are deliberately NOT\n * included: they degrade silently (reuse last-known-good type or emit\n * `unknown`) so a transient outage never fails a build.\n * @property fatalErrors - non-SQL fatal describe request failures. These still\n * produce `result: unknown` schemas so callers can write declarations before\n * surfacing the error.\n */\nexport interface QueryGenerationResult {\n schemas: QuerySchema[];\n syntaxErrors: QuerySyntaxError[];\n fatalErrors: QueryFatalError[];\n}\n"],"mappings":";;;;;AAiDA,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"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../src/type-generator/types.ts"],"sourcesContent":["/**\n * Databricks statement execution response interface for DESCRIBE QUERY /\n * DESCRIBE TABLE EXTENDED.\n *\n * Two result shapes matter here:\n * - `result.data_array` — rows already materialized as JSON arrays. Present\n * when the warehouse returns `JSON_ARRAY` (and what every mocked test\n * builds).\n * - `result.attachment` — a base64-encoded Arrow IPC stream. Present when the\n * statement runs with `format: \"ARROW_STREAM\"` + `disposition: \"INLINE\"`,\n * which is the SDK's default disposition. The single row lands here and\n * `data_array` is left undefined. {@link normalizeResultRows} decodes this\n * back into `data_array` so downstream parsers stay shape-agnostic.\n *\n * @property statement_id - the id of the statement\n * @property status - the status of the statement\n * @property manifest - result metadata; `manifest.format` echoes the wire\n * format (`ARROW_STREAM`, `JSON_ARRAY`, ...) the warehouse chose.\n * @property result - the result; either `data_array` (rows as\n * `[col_name, data_type, comment]` arrays) or `attachment` (base64 Arrow IPC)\n */\nexport interface DatabricksStatementExecutionResponse {\n statement_id: string;\n status: {\n state: string;\n error?: { error_code?: string; message?: string };\n };\n manifest?: {\n format?: string;\n };\n result?: {\n data_array?: (string | null)[][];\n /** Base64-encoded Arrow IPC stream (ARROW_STREAM + INLINE disposition). */\n attachment?: string;\n /**\n * Set when the result spans multiple chunks (rows exceeded INLINE's size\n * limit). Its presence means this response holds only the FIRST chunk;\n * {@link normalizeResultRows} throws rather than emit truncated types.\n */\n next_chunk_index?: number;\n /** Companion to {@link next_chunk_index}: link to fetch the next chunk. */\n next_chunk_internal_link?: string;\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 * @property degraded - true when the schema could not be resolved and `type`\n * is an unknown fallback. Absent when `type` came from DESCRIBE or a matching\n * last-known-good cache entry.\n */\nexport interface QuerySchema {\n name: string;\n type: string;\n degraded?: boolean;\n}\n\n/**\n * A genuine SQL error: `DESCRIBE QUERY` ran against a *reachable* warehouse and\n * the warehouse reported the statement as FAILED (bad table, syntax error,\n * incompatible type, …). Distinct from a connectivity failure (warehouse\n * unreachable), which is non-fatal and never recorded here.\n * @property name - the query name\n * @property message - the SQL error message reported by the warehouse\n */\nexport interface QuerySyntaxError {\n name: string;\n message: string;\n}\n\n/**\n * A non-SQL fatal error while attempting to describe a query: authentication,\n * authorization, invalid warehouse/configuration, malformed SDK request, or\n * any other setup problem that should not be treated as an offline warehouse.\n * @property name - the query name\n * @property message - the fatal error message\n */\nexport interface QueryFatalError {\n name: string;\n message: string;\n}\n\n/**\n * Result of describing a folder of queries.\n * @property schemas - one schema per query, in original file order. Queries that\n * could not be described carry `result: unknown` so output stays valid.\n * @property syntaxErrors - queries whose DESCRIBE failed against a reachable\n * warehouse (genuine SQL errors). Connectivity failures are deliberately NOT\n * included: they degrade silently (reuse last-known-good type or emit\n * `unknown`) so a transient outage never fails a build.\n * @property fatalErrors - deterministic non-SQL fatal describe request failures\n * (404/400). These still produce `result: unknown` schemas so callers can write\n * declarations before surfacing the error.\n * @property hadEnvironmentalFailure - `true` when an environmental failure occurred\n * in blocking mode (auth, connectivity, timeouts, or other unrecognized failures).\n * Used by {@link generateFromEntryPoint} to decide whether to apply the has-types\n * gate. Always false in non-blocking mode.\n * @property environmentalCause - coarse cause label for the environmental failure,\n * one of \"auth\" (401/403), \"unreachable\" (connectivity), or \"unavailable\" (other).\n * Only set when hadEnvironmentalFailure is true; used by the warning message.\n */\nexport interface QueryGenerationResult {\n schemas: QuerySchema[];\n syntaxErrors: QuerySyntaxError[];\n fatalErrors: QueryFatalError[];\n hadEnvironmentalFailure?: boolean;\n environmentalCause?: \"auth\" | \"unreachable\" | \"unavailable\";\n}\n"],"mappings":";;;;;AAiDA,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"}
@@ -84,7 +84,22 @@ npx @databricks/appkit generate-types --wait
84
84
 
85
85
  ```
86
86
 
87
- In blocking mode the generator starts a stopped warehouse, waits (bounded) for it to reach `RUNNING`, and then describes your queries. It fails only when the configured warehouse no longer exists (deleted/deleting), so a transient outage or a cold warehouse degrades gracefully rather than breaking the build. The app template wires this up for you: `postinstall` and `predev` run the non-blocking default, while `prebuild` runs `--wait`.
87
+ #### CI resilience: committed types as fallback[​](#ci-resilience-committed-types-as-fallback "Direct link to CI resilience: committed types as fallback")
88
+
89
+ In blocking mode (`--wait`), the generator attempts to fetch real types from your warehouse, but delegates to **committed `.d.ts` files** (`shared/appkit-types/analytics.d.ts`, `metric-views.d.ts`) as the fallback when the warehouse is unreachable. These committed files should be part of your repository. On a fresh CI checkout, every build attempts to DESCRIBE against the warehouse; the committed types are used only when that cannot complete.
90
+
91
+ The generator **never overwrites committed types with degraded (`result: unknown`) types** — it writes real types, or it does not write at all.
92
+
93
+ A **two-bucket failure taxonomy** determines whether the build crashes or falls back to committed types:
94
+
95
+ * **Deterministic failures (always crash):** SQL syntax errors in your queries (genuine DESCRIBE failure against a reachable warehouse), HTTP 404 (bad or unknown warehouse ID), HTTP 400 (malformed request). These are developer or configuration errors that committed types must not hide.
96
+ * **Environmental failures (gate on committed types):** Authentication failures (401/403), network unreachability, warehouse unavailability (cold, deleting, or deleted), wait timeout on `RUNNING`, or any unrecognized failure. If committed types exist, the build **keeps them, emits a loud warning to stderr, and succeeds (exit 0)**. If no committed types exist, the build **crashes** with a message instructing you to run `npx @databricks/appkit generate-types --wait` locally (against a reachable warehouse) and commit the `.d.ts` files.
97
+
98
+ The loud warning is a single greppable stderr line naming the coarse cause (auth blocked / warehouse unreachable / warehouse unavailable) and the warehouse ID, so CI logs surface that the build fell back to committed types.
99
+
100
+ **Note:** If your app declares only metric views and no `config/queries/`, the first build still writes an empty `analytics.d.ts`, which counts as "committed types present" for the gate. An environmental failure will then fall back and warn rather than crash, even on a first build — an accepted v1 simplification.
101
+
102
+ The app template wires this up for you: `postinstall` and `predev` run the non-blocking default, while `prebuild` runs `--wait`.
88
103
 
89
104
  ## Metric-view types[​](#metric-view-types "Direct link to Metric-view types")
90
105
 
@@ -92,7 +107,7 @@ In blocking mode the generator starts a stopped warehouse, waits (bounded) for i
92
107
 
93
108
  * `metric-views.d.ts` — augments the `MetricRegistry` interface so `useMetricView('<key>', …)` is autocompleted and type-checked. Each view's measures, dimensions, and their semantic metadata (SQL type, display name, format, time grains) are encoded at the type level.
94
109
 
95
- If `config/metric-views/definitions.json` is absent the metric path stays dormant (nothing is emitted). When present it follows the **same** warehouse-readiness contract as query types: in the default non-blocking run a view that can't be described yet — a cold warehouse, or a bad/unreachable source — is written with permissive types and a warning, while under `--wait` that same situation fails the build so CI never ships incomplete metric types. A malformed `definitions.json` (invalid JSON, or a source that isn't a three-part UC FQN) fails fast in every mode.
110
+ If `config/metric-views/definitions.json` is absent the metric path stays dormant (nothing is emitted). When present it follows the **same** warehouse-readiness contract as query types: in the default non-blocking run a view that can't be described yet — a cold warehouse, or a bad/unreachable source — is written with permissive types and a warning, while under `--wait` metric views obey the [two-bucket taxonomy](#ci-resilience-committed-types-as-fallback) (environmental failures gate to committed `metric-views.d.ts` + warn; deterministic failures like malformed definitions crash the build). A malformed `definitions.json` (invalid JSON, or a source that isn't a three-part UC FQN) fails fast in every mode.
96
111
 
97
112
  `definitions.json` is keyed by metric key; each entry names the three-part UC FQN of the view and, optionally, the executor it runs as (`app_service_principal`, the default, or `user`):
98
113
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@databricks/appkit",
3
3
  "type": "module",
4
- "version": "0.52.0",
4
+ "version": "0.53.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "bin": {