@databricks/appkit 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/appkit/package.js +1 -1
  2. package/dist/cli/commands/generate-types.js +1 -1
  3. package/dist/cli/commands/generate-types.js.map +1 -1
  4. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  5. package/dist/plugins/analytics/analytics.js +7 -1
  6. package/dist/plugins/analytics/analytics.js.map +1 -1
  7. package/dist/plugins/analytics/metric.js +1 -0
  8. package/dist/plugins/analytics/mv/index.js +1 -0
  9. package/dist/plugins/analytics/mv/metadata.js +30 -0
  10. package/dist/plugins/analytics/mv/metadata.js.map +1 -0
  11. package/dist/plugins/analytics/types.d.ts +7 -0
  12. package/dist/plugins/analytics/types.d.ts.map +1 -1
  13. package/dist/plugins/analytics/types.js.map +1 -1
  14. package/dist/shared/src/index.d.ts +1 -0
  15. package/dist/shared/src/metric-metadata.d.ts +24 -0
  16. package/dist/shared/src/metric-metadata.d.ts.map +1 -0
  17. package/dist/shared/src/schemas/manifest.d.ts +2 -2
  18. package/dist/shared/src/sse/analytics.js +2 -1
  19. package/dist/shared/src/sse/analytics.js.map +1 -1
  20. package/dist/type-generator/errors.js +0 -3
  21. package/dist/type-generator/errors.js.map +1 -1
  22. package/dist/type-generator/index.js +33 -32
  23. package/dist/type-generator/index.js.map +1 -1
  24. package/dist/type-generator/mv-registry/render-types.js +49 -11
  25. package/dist/type-generator/mv-registry/render-types.js.map +1 -1
  26. package/dist/type-generator/query-registry.js +1 -3
  27. package/dist/type-generator/query-registry.js.map +1 -1
  28. package/dist/type-generator/vite-plugin.d.ts +4 -2
  29. package/dist/type-generator/vite-plugin.d.ts.map +1 -1
  30. package/dist/type-generator/vite-plugin.js +1 -0
  31. package/dist/type-generator/vite-plugin.js.map +1 -1
  32. package/docs/development/type-generation.md +7 -7
  33. package/package.json +1 -1
  34. package/sbom.cdx.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.js","names":[],"sources":["../../../../../shared/src/sse/analytics.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * Wire protocol for analytics SSE messages emitted by `/api/analytics/query`.\n *\n * The SSE channel carries only the JSON_ARRAY path (warehouse-readiness\n * events + a `result` message of rows). ARROW_STREAM does NOT use SSE —\n * the server streams the raw Arrow IPC bytes back on the query response body\n * (`_handleArrowStreamQuery`) and the client reads them directly\n * (`fetchArrowDirect`), so there is no `arrow` message type here.\n *\n * These schemas are the single source of truth for the JSON contract between\n * the server (`AnalyticsPlugin._handleQueryRoute`) and the client\n * (`useAnalyticsQuery`). Both sides validate with the same schema:\n *\n * - Server uses the typed builder (`makeResultMessage`) to construct messages\n * with compile-time guarantees that all required fields are present.\n * - Client calls `AnalyticsSseMessage.safeParse(JSON.parse(event.data))` to\n * fail loudly on a malformed payload instead of silently treating an\n * undefined field as data.\n */\n\n/** Successful row-shaped result (JSON_ARRAY format, or empty results). */\nexport const AnalyticsResultMessage = z.object({\n type: z.literal(\"result\"),\n // `data` is intentionally `z.array(z.unknown())` rather than a deep\n // row schema. Validating every row's keys for shape costs O(rows × cols)\n // CPU and main-thread blocking time on the *client* (the schema is\n // also reused for `safeParse` in `useAnalyticsQuery`); for large JSON\n // results that pushes hundreds of ms to seconds of TBT into the\n // render pipeline. The server constructs `data` via the typed\n // `makeResultMessage` builder, so the per-row shape is enforced at\n // the source, not at validation time. The TS-level interface below\n // narrows `data` to `Record<string, unknown>[]` for callers.\n data: z.array(z.unknown()).optional(),\n // Status is opaque metadata forwarded from the warehouse — keep it as\n // `unknown` so we don't bake the SDK's detailed shape into the contract.\n status: z.unknown().optional(),\n statement_id: z.string().optional(),\n});\n\n/**\n * TS-level shape of a successful row-shaped result message.\n *\n * **Kept in sync by hand** with `AnalyticsResultMessage` above. The Zod\n * schema is intentionally loose (`z.array(z.unknown())`) to keep client\n * validation cheap; this interface narrows `data` to\n * `Record<string, unknown>[]` so consumers don't have to cast at every\n * call site. If you add a field to the Zod schema, add it here too.\n */\nexport interface AnalyticsResultMessage {\n type: \"result\";\n data?: Record<string, unknown>[];\n status?: unknown;\n statement_id?: string;\n}\n\n/**\n * Every message the analytics SSE stream may emit. Currently only the\n * row-shaped `result` message (JSON_ARRAY path); `warehouse_status` and\n * `error` events are handled off-schema by the client. ARROW_STREAM never\n * uses SSE.\n */\nexport const AnalyticsSseMessage = AnalyticsResultMessage;\nexport type AnalyticsSseMessage = z.infer<typeof AnalyticsSseMessage>;\n\n// ────────────────────────────────────────────────────────────────────────────\n// Typed builder — call from the server route handler. The compiler enforces\n// that every required field is supplied, and the return type narrows so\n// downstream code (executeStream / SSE writer) keeps full type information.\n// ────────────────────────────────────────────────────────────────────────────\n\nexport function makeResultMessage(\n data: Record<string, unknown>[] | undefined,\n extras: { status?: unknown; statement_id?: string } = {},\n): AnalyticsResultMessage {\n return { type: \"result\", data, ...extras };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,yBAAyB,EAAE,OAAO;CAC7C,MAAM,EAAE,QAAQ,SAAS;CAUzB,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,UAAU;CAGrC,QAAQ,EAAE,SAAS,CAAC,UAAU;CAC9B,cAAc,EAAE,QAAQ,CAAC,UAAU;CACpC,CAAC;AAiCF,SAAgB,kBACd,MACA,SAAsD,EAAE,EAChC;AACxB,QAAO;EAAE,MAAM;EAAU;EAAM,GAAG;EAAQ"}
1
+ {"version":3,"file":"analytics.js","names":[],"sources":["../../../../../shared/src/sse/analytics.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { MetricViewColumnDisplay } from \"../metric-metadata\";\n\n/**\n * Wire protocol for analytics SSE messages emitted by `/api/analytics/query`.\n *\n * The SSE channel carries only the JSON_ARRAY path (warehouse-readiness\n * events + a `result` message of rows). ARROW_STREAM does NOT use SSE —\n * the server streams the raw Arrow IPC bytes back on the query response body\n * (`_handleArrowStreamQuery`) and the client reads them directly\n * (`fetchArrowDirect`), so there is no `arrow` message type here.\n *\n * These schemas are the single source of truth for the JSON contract between\n * the server (`AnalyticsPlugin._handleQueryRoute`) and the client\n * (`useAnalyticsQuery`). Both sides validate with the same schema:\n *\n * - Server uses the typed builder (`makeResultMessage`) to construct messages\n * with compile-time guarantees that all required fields are present.\n * - Client calls `AnalyticsSseMessage.safeParse(JSON.parse(event.data))` to\n * fail loudly on a malformed payload instead of silently treating an\n * undefined field as data.\n */\n\n/** Successful row-shaped result (JSON_ARRAY format, or empty results). */\nexport const AnalyticsResultMessage = z.object({\n type: z.literal(\"result\"),\n // `data` is intentionally `z.array(z.unknown())` rather than a deep\n // row schema. Validating every row's keys for shape costs O(rows × cols)\n // CPU and main-thread blocking time on the *client* (the schema is\n // also reused for `safeParse` in `useAnalyticsQuery`); for large JSON\n // results that pushes hundreds of ms to seconds of TBT into the\n // render pipeline. The server constructs `data` via the typed\n // `makeResultMessage` builder, so the per-row shape is enforced at\n // the source, not at validation time. The TS-level interface below\n // narrows `data` to `Record<string, unknown>[]` for callers.\n data: z.array(z.unknown()).optional(),\n // Status is opaque metadata forwarded from the warehouse — keep it as\n // `unknown` so we don't bake the SDK's detailed shape into the contract.\n status: z.unknown().optional(),\n statement_id: z.string().optional(),\n // Per-column display metadata for a metric-view result; absent for plain\n // `/query` results. Kept loose for the same reason as `data` above.\n metadata: z.record(z.string(), z.unknown()).optional(),\n});\n\n/**\n * TS-level shape of a successful row-shaped result message.\n *\n * **Kept in sync by hand** with `AnalyticsResultMessage` above. The Zod\n * schema is intentionally loose (`z.array(z.unknown())` for `data`,\n * `z.record(z.string(), z.unknown())` for `metadata`) to keep client\n * validation cheap; this interface narrows `data` to\n * `Record<string, unknown>[]` and `metadata` to\n * `Record<string, MetricViewColumnDisplay>` so consumers don't have to cast\n * at every call site. If you add a field to the Zod schema, add it here too.\n */\nexport interface AnalyticsResultMessage {\n type: \"result\";\n data?: Record<string, unknown>[];\n status?: unknown;\n statement_id?: string;\n metadata?: Record<string, MetricViewColumnDisplay>;\n}\n\n/**\n * Every message the analytics SSE stream may emit. Currently only the\n * row-shaped `result` message (JSON_ARRAY path); `warehouse_status` and\n * `error` events are handled off-schema by the client. ARROW_STREAM never\n * uses SSE.\n */\nexport const AnalyticsSseMessage = AnalyticsResultMessage;\nexport type AnalyticsSseMessage = z.infer<typeof AnalyticsSseMessage>;\n\n// ────────────────────────────────────────────────────────────────────────────\n// Typed builder — call from the server route handler. The compiler enforces\n// that every required field is supplied, and the return type narrows so\n// downstream code (executeStream / SSE writer) keeps full type information.\n// ────────────────────────────────────────────────────────────────────────────\n\nexport function makeResultMessage(\n data: Record<string, unknown>[] | undefined,\n extras: {\n status?: unknown;\n statement_id?: string;\n metadata?: Record<string, MetricViewColumnDisplay>;\n } = {},\n): AnalyticsResultMessage {\n return { type: \"result\", data, ...extras };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,yBAAyB,EAAE,OAAO;CAC7C,MAAM,EAAE,QAAQ,SAAS;CAUzB,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,UAAU;CAGrC,QAAQ,EAAE,SAAS,CAAC,UAAU;CAC9B,cAAc,EAAE,QAAQ,CAAC,UAAU;CAGnC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU;CACvD,CAAC;AAoCF,SAAgB,kBACd,MACA,SAII,EAAE,EACkB;AACxB,QAAO;EAAE,MAAM;EAAU;EAAM,GAAG;EAAQ"}
@@ -122,9 +122,6 @@ function classifyBlockingFailure(error) {
122
122
  if (status === 404 || status === 400) return "deterministic";
123
123
  stack.push(...getErrorChildren(current));
124
124
  }
125
- const topLevelStatus = getErrorStatus(error);
126
- if (topLevelStatus !== void 0 && AUTH_ERROR_STATUSES.has(topLevelStatus)) return "environmental";
127
- if (isConnectivityError(error)) return "environmental";
128
125
  return "environmental";
129
126
  }
130
127
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../src/type-generator/errors.ts"],"sourcesContent":["/**\n * Shared error-introspection utilities for the type generator.\n *\n * Both describe paths — the query registry and the metric-view registry — must\n * classify a thrown failure the same way: a transient connectivity blip (which\n * self-converges and should never fail a build) versus a deterministic failure\n * (auth, bad warehouse id, truncated result, malformed request) that must be\n * surfaced. Keeping `isConnectivityError` (and the message/diagnostic helpers it\n * shares) in one module is the single source of truth for that decision, so the\n * two paths can never drift apart.\n */\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (isObject(error) && typeof error.message === \"string\") {\n return error.message;\n }\n return String(error);\n}\n\nexport function getErrorDiagnostic(error: unknown): string {\n const seen = new Set<unknown>();\n const messages: string[] = [];\n const stack = [error];\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined || seen.has(current)) continue;\n seen.add(current);\n\n const message = getErrorMessage(current);\n if (\n message &&\n message !== \"[object Object]\" &&\n !messages.includes(message)\n ) {\n messages.push(message);\n }\n\n const code = getErrorCode(current);\n if (code && !messages.includes(code)) messages.push(code);\n\n stack.push(...getErrorChildren(current));\n }\n\n return messages.length > 0 ? messages.join(\": \") : getErrorMessage(error);\n}\n\nfunction getErrorCode(error: unknown): string | undefined {\n if (!isObject(error)) return undefined;\n const code = error.code ?? error.errno;\n return typeof code === \"string\" ? code : undefined;\n}\n\nfunction getErrorStatus(error: unknown): number | undefined {\n if (!isObject(error)) return undefined;\n const direct = error.status ?? error.statusCode;\n if (typeof direct === \"number\") return direct;\n if (isObject(error.response) && typeof error.response.status === \"number\") {\n return error.response.status;\n }\n return undefined;\n}\n\nfunction getErrorChildren(error: unknown): unknown[] {\n if (!isObject(error)) return [];\n const children: unknown[] = [];\n if (\"cause\" in error) children.push(error.cause);\n if (error instanceof AggregateError) children.push(...error.errors);\n return children;\n}\n\nconst CONNECTIVITY_ERROR_CODES = new Set([\n \"ECONNREFUSED\",\n \"ECONNRESET\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"EAI_AGAIN\",\n \"EAI_NODATA\",\n \"EAI_NONAME\",\n \"EHOSTUNREACH\",\n \"ENETUNREACH\",\n \"CERT_HAS_EXPIRED\",\n \"DEPTH_ZERO_SELF_SIGNED_CERT\",\n \"ERR_TLS_CERT_ALTNAME_INVALID\",\n \"SELF_SIGNED_CERT_IN_CHAIN\",\n \"UNABLE_TO_VERIFY_LEAF_SIGNATURE\",\n]);\n\nfunction isConnectivityMessage(message: string): boolean {\n return (\n /\\bconnection (?:refused|reset|timed out)\\b/i.test(message) ||\n /\\bsocket hang up\\b/i.test(message) ||\n /\\bnetwork error\\b/i.test(message) ||\n /\\bcan'?t connect to\\b/i.test(message) ||\n /\\bcertificate has expired\\b/i.test(message) ||\n /\\bunable to verify the first certificate\\b/i.test(message) ||\n /\\bupstream connect error or disconnect\\/reset before headers\\b/i.test(\n message,\n )\n );\n}\n\n/**\n * True when a thrown failure is a transport/connectivity problem (DNS, refused\n * connection, reset, TLS, 502/503/504, undici `UND_ERR_*`) rather than a\n * deterministic error the warehouse returned. Walks `cause`/`AggregateError`\n * chains so a wrapped connectivity error is still recognized. Callers degrade\n * (and retry) on `true`; everything else is surfaced as a build failure.\n */\nexport function isConnectivityError(error: unknown): boolean {\n const seen = new Set<unknown>();\n const stack = [error];\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined || seen.has(current)) continue;\n seen.add(current);\n\n const code = getErrorCode(current);\n if (\n code &&\n (CONNECTIVITY_ERROR_CODES.has(code) || code.startsWith(\"UND_ERR_\"))\n ) {\n return true;\n }\n\n const status = getErrorStatus(current);\n if (status === 502 || status === 503 || status === 504) return true;\n\n if (isConnectivityMessage(getErrorMessage(current))) return true;\n\n stack.push(...getErrorChildren(current));\n }\n\n return false;\n}\n\nconst AUTH_ERROR_STATUSES = new Set([401, 403]);\n\n/**\n * Classifies a thrown failure into one of two buckets: deterministic failures\n * that must be surfaced (bad warehouse id, malformed request) or environmental\n * issues (connectivity, auth, deleted warehouse, timeouts) that the has-types\n * gate will handle later.\n *\n * Returns:\n * - \"deterministic\": HTTP 404 (bad warehouse id) or 400 (malformed request).\n * The build must fail.\n * - \"environmental\": Everything else — auth (401/403), connectivity errors,\n * warehouse state changes (DELETED/DELETING), wait-for-RUNNING timeouts,\n * unrecognized failures. Default = environmental.\n *\n * Walks `cause`/`AggregateError` chains when checking for deterministic status,\n * so a wrapped 404 is still recognized as deterministic.\n */\nexport function classifyBlockingFailure(\n error: unknown,\n): \"deterministic\" | \"environmental\" {\n // Deterministic: check first so they're never swallowed by environmental rules.\n // Walk the error chain to find any deterministic status.\n const seen = new Set<unknown>();\n const stack = [error];\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined || seen.has(current)) continue;\n seen.add(current);\n\n const status = getErrorStatus(current);\n if (status === 404 || status === 400) {\n return \"deterministic\";\n }\n\n stack.push(...getErrorChildren(current));\n }\n\n // Environmental: auth, connectivity, unrecognized, default.\n const topLevelStatus = getErrorStatus(error);\n if (topLevelStatus !== undefined && AUTH_ERROR_STATUSES.has(topLevelStatus)) {\n return \"environmental\";\n }\n\n if (isConnectivityError(error)) {\n return \"environmental\";\n }\n\n // Default: any unrecognized failure or no status (DELETED/DELETING messages,\n // timeout messages, plain Error objects) → environmental.\n return \"environmental\";\n}\n\n/**\n * Coarse cause label for an environmental failure, used by the `--wait`\n * committed-types warning so the log says *why* generation fell back.\n *\n * Returns:\n * - \"unreachable\": transport/connectivity failure (see {@link isConnectivityError}).\n * - \"auth\": HTTP 401/403, including a status carried on `response.status` or\n * wrapped in a `cause`/`AggregateError` chain.\n * - \"unavailable\": everything else (DELETED/DELETING, wait timeouts, degraded\n * DESCRIBEs).\n */\nexport function classifyEnvironmentalCause(\n error: unknown,\n): \"auth\" | \"unreachable\" | \"unavailable\" {\n if (isConnectivityError(error)) return \"unreachable\";\n\n // Walk the error chain so a wrapped 401/403 is still labeled as auth.\n const seen = new Set<unknown>();\n const stack = [error];\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined || seen.has(current)) continue;\n seen.add(current);\n\n const status = getErrorStatus(current);\n if (status !== undefined && AUTH_ERROR_STATUSES.has(status)) return \"auth\";\n\n stack.push(...getErrorChildren(current));\n }\n\n return \"unavailable\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../src/type-generator/errors.ts"],"sourcesContent":["/**\n * Shared error-introspection utilities for the type generator.\n *\n * Both describe paths — the query registry and the metric-view registry — must\n * classify a thrown failure the same way: a transient connectivity blip (which\n * self-converges and should never fail a build) versus a deterministic failure\n * (auth, bad warehouse id, truncated result, malformed request) that must be\n * surfaced. Keeping `isConnectivityError` (and the message/diagnostic helpers it\n * shares) in one module is the single source of truth for that decision, so the\n * two paths can never drift apart.\n */\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (isObject(error) && typeof error.message === \"string\") {\n return error.message;\n }\n return String(error);\n}\n\nexport function getErrorDiagnostic(error: unknown): string {\n const seen = new Set<unknown>();\n const messages: string[] = [];\n const stack = [error];\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined || seen.has(current)) continue;\n seen.add(current);\n\n const message = getErrorMessage(current);\n if (\n message &&\n message !== \"[object Object]\" &&\n !messages.includes(message)\n ) {\n messages.push(message);\n }\n\n const code = getErrorCode(current);\n if (code && !messages.includes(code)) messages.push(code);\n\n stack.push(...getErrorChildren(current));\n }\n\n return messages.length > 0 ? messages.join(\": \") : getErrorMessage(error);\n}\n\nfunction getErrorCode(error: unknown): string | undefined {\n if (!isObject(error)) return undefined;\n const code = error.code ?? error.errno;\n return typeof code === \"string\" ? code : undefined;\n}\n\nfunction getErrorStatus(error: unknown): number | undefined {\n if (!isObject(error)) return undefined;\n const direct = error.status ?? error.statusCode;\n if (typeof direct === \"number\") return direct;\n if (isObject(error.response) && typeof error.response.status === \"number\") {\n return error.response.status;\n }\n return undefined;\n}\n\nfunction getErrorChildren(error: unknown): unknown[] {\n if (!isObject(error)) return [];\n const children: unknown[] = [];\n if (\"cause\" in error) children.push(error.cause);\n if (error instanceof AggregateError) children.push(...error.errors);\n return children;\n}\n\nconst CONNECTIVITY_ERROR_CODES = new Set([\n \"ECONNREFUSED\",\n \"ECONNRESET\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"EAI_AGAIN\",\n \"EAI_NODATA\",\n \"EAI_NONAME\",\n \"EHOSTUNREACH\",\n \"ENETUNREACH\",\n \"CERT_HAS_EXPIRED\",\n \"DEPTH_ZERO_SELF_SIGNED_CERT\",\n \"ERR_TLS_CERT_ALTNAME_INVALID\",\n \"SELF_SIGNED_CERT_IN_CHAIN\",\n \"UNABLE_TO_VERIFY_LEAF_SIGNATURE\",\n]);\n\nfunction isConnectivityMessage(message: string): boolean {\n return (\n /\\bconnection (?:refused|reset|timed out)\\b/i.test(message) ||\n /\\bsocket hang up\\b/i.test(message) ||\n /\\bnetwork error\\b/i.test(message) ||\n /\\bcan'?t connect to\\b/i.test(message) ||\n /\\bcertificate has expired\\b/i.test(message) ||\n /\\bunable to verify the first certificate\\b/i.test(message) ||\n /\\bupstream connect error or disconnect\\/reset before headers\\b/i.test(\n message,\n )\n );\n}\n\n/**\n * True when a thrown failure is a transport/connectivity problem (DNS, refused\n * connection, reset, TLS, 502/503/504, undici `UND_ERR_*`) rather than a\n * deterministic error the warehouse returned. Walks `cause`/`AggregateError`\n * chains so a wrapped connectivity error is still recognized. Callers degrade\n * (and retry) on `true`; everything else is surfaced as a build failure.\n */\nexport function isConnectivityError(error: unknown): boolean {\n const seen = new Set<unknown>();\n const stack = [error];\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined || seen.has(current)) continue;\n seen.add(current);\n\n const code = getErrorCode(current);\n if (\n code &&\n (CONNECTIVITY_ERROR_CODES.has(code) || code.startsWith(\"UND_ERR_\"))\n ) {\n return true;\n }\n\n const status = getErrorStatus(current);\n if (status === 502 || status === 503 || status === 504) return true;\n\n if (isConnectivityMessage(getErrorMessage(current))) return true;\n\n stack.push(...getErrorChildren(current));\n }\n\n return false;\n}\n\nconst AUTH_ERROR_STATUSES = new Set([401, 403]);\n\n/**\n * Classifies a thrown failure into one of two buckets: deterministic failures\n * that must be surfaced (bad warehouse id, malformed request) or environmental\n * issues (connectivity, auth, deleted warehouse, timeouts) that the has-types\n * gate will handle later.\n *\n * Returns:\n * - \"deterministic\": HTTP 404 (bad warehouse id) or 400 (malformed request).\n * The build must fail.\n * - \"environmental\": Everything else — auth (401/403), connectivity errors,\n * warehouse state changes (DELETED/DELETING), wait-for-RUNNING timeouts,\n * unrecognized failures. Default = environmental.\n *\n * Walks `cause`/`AggregateError` chains when checking for deterministic status,\n * so a wrapped 404 is still recognized as deterministic.\n */\nexport function classifyBlockingFailure(\n error: unknown,\n): \"deterministic\" | \"environmental\" {\n const seen = new Set<unknown>();\n const stack = [error];\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined || seen.has(current)) continue;\n seen.add(current);\n\n const status = getErrorStatus(current);\n if (status === 404 || status === 400) {\n return \"deterministic\";\n }\n\n stack.push(...getErrorChildren(current));\n }\n\n return \"environmental\";\n}\n\n/**\n * Coarse cause label for an environmental failure, used by the `--wait`\n * committed-types warning so the log says *why* generation fell back.\n *\n * Returns:\n * - \"unreachable\": transport/connectivity failure (see {@link isConnectivityError}).\n * - \"auth\": HTTP 401/403, including a status carried on `response.status` or\n * wrapped in a `cause`/`AggregateError` chain.\n * - \"unavailable\": everything else (DELETED/DELETING, wait timeouts, degraded\n * DESCRIBEs).\n */\nexport function classifyEnvironmentalCause(\n error: unknown,\n): \"auth\" | \"unreachable\" | \"unavailable\" {\n if (isConnectivityError(error)) return \"unreachable\";\n\n // Walk the error chain so a wrapped 401/403 is still labeled as auth.\n const seen = new Set<unknown>();\n const stack = [error];\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (current === undefined || seen.has(current)) continue;\n seen.add(current);\n\n const status = getErrorStatus(current);\n if (status !== undefined && AUTH_ERROR_STATUSES.has(status)) return \"auth\";\n\n stack.push(...getErrorChildren(current));\n }\n\n return \"unavailable\";\n}\n"],"mappings":"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"}
@@ -27,26 +27,12 @@ const logger = createLogger("type-generator");
27
27
  * `PREFLIGHT_WAIT_MAX_MS` in query-registry.ts.
28
28
  */
29
29
  const MV_PREFLIGHT_WAIT_MAX_MS = 3e5;
30
- /**
31
- * Generate a loud warning message for environmental failures with committed types present.
32
- * @param cause - coarse cause label: "auth" (401/403), "unreachable" (connectivity), or "unavailable" (other)
33
- */
34
30
  function determineWarningMessage(cause, warehouseId) {
35
31
  return `AppKit typegen: using committed types — warehouse ${warehouseId} ${cause === "auth" ? "auth blocked" : cause === "unreachable" ? "warehouse unreachable" : "warehouse unavailable"}; please check warehouse status and retry`;
36
32
  }
37
33
  function plural(count, singular, pluralForm = `${singular}s`) {
38
34
  return count === 1 ? singular : pluralForm;
39
35
  }
40
- /**
41
- * Check if committed type artifacts exist (at least one of the requested surfaces).
42
- * Serving types are excluded (gitignored, never part of the gate).
43
- * Returns true if either the analytics or metric-views committed .d.ts file exists.
44
- */
45
- function hasCommittedTypes(analyticsOutFile, metricViewsOutFile) {
46
- const hasAnalytics = existsSync(analyticsOutFile);
47
- const hasMetrics = metricViewsOutFile !== void 0 && existsSync(metricViewsOutFile);
48
- return hasAnalytics || hasMetrics;
49
- }
50
36
  function isQueryDegraded(schema) {
51
37
  return schema.degraded === true;
52
38
  }
@@ -211,7 +197,7 @@ async function probeWarehouseState(getClient, warehouseId) {
211
197
  * `metric-views` directory of `queryFolder` (so query-only callers keep
212
198
  * working); when neither is given, the metric path is skipped.
213
199
  * @param options.mvOutFile - optional output file for the MetricRegistry
214
- * augmentation. Defaults to a sibling `metric-views.d.ts` file under the same
200
+ * augmentation. Defaults to a sibling `metric-views.ts` file under the same
215
201
  * directory as `outFile`. Skipped entirely if `definitions.json` is absent.
216
202
  * @param options.metricFetcher - optional DescribeFetcher used by
217
203
  * {@link syncMetrics} (tests inject a mock; production lazily builds a
@@ -222,12 +208,14 @@ async function probeWarehouseState(getClient, warehouseId) {
222
208
  async function generateFromEntryPoint(options) {
223
209
  const { outFile, queryFolder, warehouseId, noCache, mode = "non-blocking", mvOutFile, metricFetcher } = options;
224
210
  const metricViewsFolder = options.metricViewsFolder ?? (queryFolder ? path.resolve(queryFolder, "..", "metric-views") : void 0);
211
+ const resolvedMvFile = mvOutFile ?? path.join(path.dirname(outFile), METRIC_TYPES_FILE);
225
212
  const projectRoot = resolveProjectRoot(outFile);
226
213
  logger.debug("Starting type generation...");
227
214
  let queryRegistry = [];
228
215
  let syntaxErrors = [];
229
216
  let fatalErrors = [];
230
- let hadEnvironmentalFailure = false;
217
+ let queryHadEnvironmentalFailure = false;
218
+ let metricsHadEnvironmentalFailure = false;
231
219
  let environmentalCause;
232
220
  if (queryFolder) {
233
221
  const result = await generateQueriesFromDescribe(queryFolder, warehouseId, {
@@ -237,13 +225,13 @@ async function generateFromEntryPoint(options) {
237
225
  queryRegistry = result.schemas;
238
226
  syntaxErrors = result.syntaxErrors ?? [];
239
227
  fatalErrors = result.fatalErrors ?? [];
240
- hadEnvironmentalFailure = hadEnvironmentalFailure || (result.hadEnvironmentalFailure ?? false);
228
+ queryHadEnvironmentalFailure = result.hadEnvironmentalFailure ?? false;
241
229
  environmentalCause = environmentalCause ?? result.environmentalCause ?? void 0;
242
230
  }
243
231
  const typeDeclarations = generateTypeDeclarations(queryRegistry);
244
232
  const hasAnyDegradedQuery = queryRegistry.some(isQueryDegraded);
245
233
  if (mode === "blocking" && hasAnyDegradedQuery) {
246
- hadEnvironmentalFailure = true;
234
+ queryHadEnvironmentalFailure = true;
247
235
  environmentalCause = environmentalCause ?? "unavailable";
248
236
  }
249
237
  if (mode !== "blocking" || !hasAnyDegradedQuery) {
@@ -251,13 +239,12 @@ async function generateFromEntryPoint(options) {
251
239
  await fs.writeFile(outFile, typeDeclarations, "utf-8");
252
240
  }
253
241
  if (metricViewsFolder) {
254
- const mvFile = mvOutFile ?? path.join(path.dirname(outFile), METRIC_TYPES_FILE);
255
242
  let mvResult;
256
243
  try {
257
244
  mvResult = await syncMetricViewsTypes({
258
245
  metricViewsFolder,
259
246
  warehouseId,
260
- metricOutFile: mvFile,
247
+ metricOutFile: resolvedMvFile,
261
248
  cache: !noCache,
262
249
  metricFetcher,
263
250
  mode,
@@ -270,7 +257,7 @@ async function generateFromEntryPoint(options) {
270
257
  }], warehouseId);
271
258
  }
272
259
  for (const fe of mvResult.fatalErrors) fatalErrors.push(fe);
273
- hadEnvironmentalFailure = hadEnvironmentalFailure || (mvResult.hadEnvironmentalFailure ?? false);
260
+ metricsHadEnvironmentalFailure = (mvResult.hadEnvironmentalFailure ?? false) || mode === "blocking" && hasAnyDegradedMetrics(mvResult.schemas);
274
261
  environmentalCause = environmentalCause ?? mvResult.environmentalCause ?? void 0;
275
262
  if (mode === "blocking") for (const failure of mvResult.failures) {
276
263
  if (failure.transient) continue;
@@ -284,13 +271,18 @@ async function generateFromEntryPoint(options) {
284
271
  await migrateProjectConfig(projectRoot);
285
272
  if (syntaxErrors.length > 0) throw new TypegenSyntaxError(syntaxErrors, warehouseId, fatalErrors);
286
273
  if (fatalErrors.length > 0) throw new TypegenFatalError(fatalErrors, warehouseId);
287
- if (mode === "blocking" && hadEnvironmentalFailure) if (hasCommittedTypes(outFile, options.mvOutFile ?? path.join(path.dirname(outFile), METRIC_TYPES_FILE))) {
288
- const warningMessage = determineWarningMessage(environmentalCause ?? "unavailable", warehouseId);
289
- logger.warn(warningMessage);
290
- } else throw new TypegenFatalError([{
291
- name: "type-generator",
292
- message: `Warehouse ${warehouseId} could not be reached and no committed types exist. Run 'npx @databricks/appkit generate-types --wait' locally and commit the generated .d.ts files.`
293
- }], warehouseId);
274
+ if (mode === "blocking" && (queryHadEnvironmentalFailure || metricsHadEnvironmentalFailure)) {
275
+ const missingCommittedTypes = [];
276
+ if (queryHadEnvironmentalFailure && !existsSync(outFile)) missingCommittedTypes.push(path.basename(outFile));
277
+ if (metricsHadEnvironmentalFailure && !existsSync(resolvedMvFile)) missingCommittedTypes.push(path.basename(resolvedMvFile));
278
+ if (missingCommittedTypes.length === 0) {
279
+ const warningMessage = determineWarningMessage(environmentalCause ?? "unavailable", warehouseId);
280
+ logger.warn(warningMessage);
281
+ } else throw new TypegenFatalError([{
282
+ name: "type-generator",
283
+ message: `Warehouse ${warehouseId} could not provide schemas and the required committed type ${plural(missingCommittedTypes.length, "artifact is", "artifacts are")} missing: ${missingCommittedTypes.join(", ")}. Run 'npx @databricks/appkit generate-types --wait' locally and commit the generated type files.`
284
+ }], warehouseId);
285
+ }
294
286
  logger.debug("Type generation complete!");
295
287
  }
296
288
  /**
@@ -302,14 +294,16 @@ async function generateFromEntryPoint(options) {
302
294
  *
303
295
  * @param options.metricViewsFolder - folder that holds `definitions.json` (`<root>/config/metric-views`).
304
296
  * @param options.warehouseId - SQL warehouse used for `DESCRIBE TABLE EXTENDED`.
305
- * @param options.metricOutFile - output path for the MetricRegistry `.d.ts`.
297
+ * @param options.metricOutFile - output path for the MetricRegistry `.ts` (the
298
+ * generated source carries both the `declare module` augmentation and the
299
+ * runtime `metricViewsMetadata` const).
306
300
  * @param options.cache - cache toggle, default ON. Only `cache === false` disables it (so `undefined`/`true` keep caching).
307
301
  * @param options.metricFetcher - optional injected {@link DescribeFetcher}
308
302
  * @param options.mode - preflight/gate policy, default `"describe-now"`. When set to `"blocking"`,
309
- * metric-view .d.ts writes are suppressed if any metric is degraded (to preserve committed files).
303
+ * metric-view `.ts` writes are suppressed if any metric is degraded (to preserve committed files).
310
304
  * @param options.suppressDegradedWrite - when true (only in `mode === "blocking"` context), skip
311
305
  * the metricOutFile write if any metric schema has `degraded === true`. Used to prevent
312
- * overwriting committed .d.ts files with degraded types in blocking mode.
306
+ * overwriting committed type files with degraded types in blocking mode.
313
307
  */
314
308
  async function syncMetricViewsTypes(options) {
315
309
  const { metricViewsFolder, warehouseId, metricOutFile, cache: cacheEnabled, metricFetcher, mode = "describe-now", suppressDegradedWrite } = options;
@@ -447,6 +441,13 @@ async function syncMetricViewsTypes(options) {
447
441
  await fs.mkdir(path.dirname(metricOutFile), { recursive: true });
448
442
  await fs.writeFile(metricOutFile, generateMetricTypeDeclarations(schemas), "utf-8");
449
443
  }
444
+ if (metricOutFile.endsWith(".ts") && !metricOutFile.endsWith(".d.ts") && existsSync(metricOutFile)) {
445
+ const staleDts = `${metricOutFile.slice(0, -3)}.d.ts`;
446
+ try {
447
+ await fs.unlink(staleDts);
448
+ logger.debug("Removed stale generated types at %s", staleDts);
449
+ } catch {}
450
+ }
450
451
  logger.debug("Wrote MetricRegistry augmentation for %d metric(s)%s", schemas.length, failures.length > 0 ? ` (${failures.length} failure(s))` : "");
451
452
  return {
452
453
  metricOutFile,
@@ -462,7 +463,7 @@ const generateServingTypes = generateServingTypes$1;
462
463
  const TYPES_DIR = "appkit-types";
463
464
  const ANALYTICS_TYPES_FILE = "analytics.d.ts";
464
465
  const SERVING_TYPES_FILE = "serving.d.ts";
465
- const METRIC_TYPES_FILE = "metric-views.d.ts";
466
+ const METRIC_TYPES_FILE = "metric-views.ts";
466
467
 
467
468
  //#endregion
468
469
  export { ANALYTICS_TYPES_FILE, SERVING_TYPES_FILE, TYPES_DIR, TypegenFatalError, TypegenSyntaxError, generateFromEntryPoint, generateServingTypes };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["generateServingTypesImpl"],"sources":["../../src/type-generator/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport pc from \"picocolors\";\nimport { createLogger } from \"../logging/logger\";\nimport {\n createWorkspaceClient,\n type WorkspaceClient,\n} from \"../workspace-client\";\nimport {\n isRevivableMetricCacheEntry,\n loadCache,\n type MetricCacheEntry,\n metricCacheHash,\n saveCache,\n} from \"./cache\";\nimport {\n classifyBlockingFailure,\n classifyEnvironmentalCause,\n getErrorDiagnostic,\n isConnectivityError,\n} from \"./errors\";\nimport {\n migrateProjectConfig,\n removeOldGeneratedTypes,\n resolveProjectRoot,\n} from \"./migration\";\nimport { readMetricConfig, resolveMetricConfig } from \"./mv-registry/config\";\nimport { createWorkspaceDescribeFetcher } from \"./mv-registry/describe\";\nimport { generateMetricTypeDeclarations } from \"./mv-registry/render-types\";\nimport { emptyMetricSchema, syncMetrics } from \"./mv-registry/sync\";\nimport type {\n DescribeFetcher,\n MetricColumnMetadata,\n MetricLane,\n MetricSchema,\n MetricSyncFailure,\n MetricSyncResult,\n} from \"./mv-registry/types\";\nimport { decidePreflight, type PreflightMode } from \"./preflight\";\nimport { generateQueriesFromDescribe } from \"./query-registry\";\nimport { generateServingTypes as generateServingTypesImpl } from \"./serving/generator\";\nimport type { QueryFatalError, QuerySchema, QuerySyntaxError } from \"./types\";\nimport {\n getWarehouseState,\n startWarehouse,\n type WarehouseState,\n waitUntilRunning,\n} from \"./warehouse-status\";\n\ndotenv.config();\n\nconst logger = createLogger(\"type-generator\");\n\n/**\n * Upper bound (~5 min) on how long the Metric Views path's `blocking`-mode preflight\n * waits for a warehouse to reach RUNNING. Mirrors the query path's (unexported)\n * `PREFLIGHT_WAIT_MAX_MS` in query-registry.ts.\n */\nconst MV_PREFLIGHT_WAIT_MAX_MS = 300_000;\n\n/**\n * Generate a loud warning message for environmental failures with committed types present.\n * @param cause - coarse cause label: \"auth\" (401/403), \"unreachable\" (connectivity), or \"unavailable\" (other)\n */\nfunction determineWarningMessage(\n cause: \"auth\" | \"unreachable\" | \"unavailable\",\n warehouseId: string,\n): string {\n const causeLabel =\n cause === \"auth\"\n ? \"auth blocked\"\n : cause === \"unreachable\"\n ? \"warehouse unreachable\"\n : \"warehouse unavailable\";\n // Use a stable prefix for greppability and CI log matching.\n return `AppKit typegen: using committed types — warehouse ${warehouseId} ${causeLabel}; please check warehouse status and retry`;\n}\n\ntype TypegenFailure = QuerySyntaxError | QueryFatalError;\n\nfunction plural(count: number, singular: string, pluralForm = `${singular}s`) {\n return count === 1 ? singular : pluralForm;\n}\n\n/**\n * Check if committed type artifacts exist (at least one of the requested surfaces).\n * Serving types are excluded (gitignored, never part of the gate).\n * Returns true if either the analytics or metric-views committed .d.ts file exists.\n */\nfunction hasCommittedTypes(\n analyticsOutFile: string,\n metricViewsOutFile: string | undefined,\n): boolean {\n const hasAnalytics = existsSync(analyticsOutFile);\n const hasMetrics =\n metricViewsOutFile !== undefined && existsSync(metricViewsOutFile);\n return hasAnalytics || hasMetrics;\n}\n\nfunction isQueryDegraded(schema: QuerySchema): boolean {\n return schema.degraded === true;\n}\n\nfunction hasAnyDegradedMetrics(schemas: MetricSchema[]): boolean {\n return schemas.some((s) => s.degraded === true);\n}\n\nfunction formatFailureRows(\n label: string,\n queries: TypegenFailure[],\n color: (value: string) => string,\n) {\n if (queries.length === 0) return [];\n\n // Group by message so a shared failure — e.g. a warehouse-level fatal that\n // hits every query identically — prints once instead of repeating per row.\n const byMessage = new Map<string, string[]>();\n for (const { name, message } of queries) {\n const names = byMessage.get(message);\n if (names) names.push(name);\n else byMessage.set(message, [name]);\n }\n\n const maxNameLen = Math.max(...queries.map((query) => query.name.length));\n const tag = color(label.padEnd(7));\n const rows: string[] = [];\n for (const [message, names] of byMessage) {\n if (names.length === 1) {\n rows.push(\n ` ${tag} ${pc.bold(names[0].padEnd(maxNameLen))} ${pc.dim(message)}`,\n );\n continue;\n }\n // Shared message → print it once, then list the affected query names.\n rows.push(\n ` ${tag} ${pc.dim(message)} ${pc.dim(`(${names.length} ${plural(names.length, \"query\", \"queries\")})`)}`,\n );\n rows.push(\n ` ${names.map((name) => pc.bold(name)).join(pc.dim(\", \"))}`,\n );\n }\n return rows;\n}\n\nfunction formatTypegenFailureMessage(options: {\n syntaxErrors: QuerySyntaxError[];\n fatalErrors?: QueryFatalError[];\n warehouseId?: string;\n title: string;\n causes: string[];\n nextStep: string;\n}) {\n const { syntaxErrors, fatalErrors = [], warehouseId, title } = options;\n const total = syntaxErrors.length + fatalErrors.length;\n const separator = pc.dim(\"─\".repeat(60));\n const warehouse = warehouseId\n ? ` against ${pc.dim(`warehouse ${warehouseId}`)}`\n : \"\";\n\n return [\n ` ${pc.bold(pc.red(\"Type generation failed\"))}`,\n ` ${separator}`,\n ` ${title}: ${total} ${plural(total, \"query\", \"queries\")} could not be described${warehouse}.`,\n ` AppKit wrote generated types with ${pc.bold(\"result: unknown\")} for the failed ${plural(total, \"query\", \"queries\")}.`,\n \"\",\n ...formatFailureRows(\"SQL ERR\", syntaxErrors, pc.red),\n ...(syntaxErrors.length > 0 && fatalErrors.length > 0 ? [\"\"] : []),\n ...formatFailureRows(\"FATAL\", fatalErrors, pc.red),\n \"\",\n ` ${pc.bold(\"Common causes\")}`,\n ...options.causes.map((cause) => ` - ${cause}`),\n \"\",\n ` ${pc.bold(\"Next step\")}`,\n ` ${options.nextStep}`,\n ].join(\"\\n\");\n}\n\n/**\n * Thrown when one or more queries fail `DESCRIBE QUERY` against a *reachable*\n * warehouse — i.e. genuine SQL errors (bad table, syntax, incompatible type),\n * as opposed to a connectivity failure (warehouse unreachable), which degrades\n * silently. Whether this is fatal is the caller's decision: the Vite plugin and\n * CLI fail the build in production and warn-only in development.\n */\nexport class TypegenSyntaxError extends Error {\n readonly queries: QuerySyntaxError[];\n readonly fatalQueries: QueryFatalError[];\n\n constructor(\n queries: QuerySyntaxError[],\n warehouseId?: string,\n fatalQueries: QueryFatalError[] = [],\n ) {\n super(\n formatTypegenFailureMessage({\n syntaxErrors: queries,\n fatalErrors: fatalQueries,\n warehouseId,\n title: \"DESCRIBE QUERY failed\",\n causes: [\n \"SQL syntax errors\",\n \"missing tables or views\",\n \"warehouse format incompatibilities\",\n ],\n nextStep: warehouseId\n ? `Run each SQL ERR query directly in a Databricks SQL editor against warehouse ${pc.bold(warehouseId)}.`\n : \"Run each SQL ERR query directly in a Databricks SQL editor.\",\n }),\n );\n this.name = \"TypegenSyntaxError\";\n this.queries = queries;\n this.fatalQueries = fatalQueries;\n }\n}\n\n/**\n * Thrown when DESCRIBE QUERY could not be requested because of a non-SQL fatal\n * setup/request problem, such as missing permissions, invalid warehouse IDs, or\n * malformed SDK configuration. Like TypegenSyntaxError, this is thrown only\n * after the declaration file has been written with `result: unknown` entries.\n */\nexport class TypegenFatalError extends Error {\n readonly queries: QueryFatalError[];\n\n constructor(queries: QueryFatalError[], warehouseId?: string) {\n super(\n formatTypegenFailureMessage({\n syntaxErrors: [],\n fatalErrors: queries,\n warehouseId,\n title: \"DESCRIBE QUERY could not be requested\",\n causes: [\n \"missing warehouse permissions\",\n \"invalid warehouse ID\",\n \"authentication failure\",\n \"SDK configuration errors\",\n ],\n nextStep: warehouseId\n ? `Verify access to warehouse ${pc.bold(warehouseId)} and rerun type generation.`\n : \"Verify warehouse access and rerun type generation.\",\n }),\n );\n this.name = \"TypegenFatalError\";\n this.queries = queries;\n }\n}\n\n/**\n * Generate type declarations for QueryRegistry\n * Create the d.ts file from the plugin routes and query schemas\n * @param querySchemas - the list of query schemas\n * @returns - the type declarations as a string\n */\nfunction generateTypeDeclarations(querySchemas: QuerySchema[] = []): string {\n const queryEntries = querySchemas\n .map(({ name, type }) => {\n const indentedType = type\n .split(\"\\n\")\n .map((line, i) => (i === 0 ? line : ` ${line}`))\n .join(\"\\n\");\n return ` ${name}: ${indentedType}`;\n })\n .join(\";\\n\");\n\n const querySection = queryEntries ? `\\n${queryEntries};\\n ` : \"\";\n\n return `// Auto-generated by AppKit - DO NOT EDIT\n// Generated by 'npx @databricks/appkit generate-types' or Vite plugin during build\nimport \"@databricks/appkit-ui/react\";\nimport type { SQLTypeMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLBinaryMarker, SQLDateMarker, SQLTimestampMarker } from \"@databricks/appkit-ui/js\";\n\ndeclare module \"@databricks/appkit-ui/react\" {\n interface QueryRegistry {${querySection}}\n}\n`;\n}\n\n/**\n * Status-only probe for the metric-view gate in {@link generateFromEntryPoint}\n *\n * Uses {@link getWarehouseState} (`warehouses.get`) —\n * a read-only GET that can never start the warehouse\n *\n * Takes the lazy client *getter* so the probe also absorbs client construction failure.\n * A connectivity blip returns `undefined`, which the gate reads as transient not-running;\n * a deterministic failure (auth, bad id) is re-thrown so the gate can classify it\n * fatal rather than silently degrading.\n */\nasync function probeWarehouseState(\n getClient: () => WorkspaceClient,\n warehouseId: string,\n): Promise<WarehouseState | undefined> {\n try {\n return await getWarehouseState(getClient(), warehouseId);\n } catch (err) {\n // Connectivity blip → undefined (gate degrades, retries next pass). A\n // deterministic failure (auth, bad warehouse id, client construction) must\n // not masquerade as not-running — re-throw so the gate pins it fatal, the\n // same split the query path's preflight makes.\n if (isConnectivityError(err)) return undefined;\n throw err;\n }\n}\n\n/**\n * Entry point for generating type declarations from all imported files\n * @param options - the options for the generation\n * @param options.entryPoint - the entry point file\n * @param options.outFile - the output file\n * @param options.noCache - skip the typegen cache entirely: every query is\n * re-described, and the metric path ignores its cached schemas (every\n * configured key becomes describe-needed) and overwrites the cache's\n * `metrics` section with this pass's results.\n * @param options.mode - preflight policy (see {@link PreflightMode}), default\n * `\"non-blocking\"`. For queries, `\"non-blocking\"` never touches the\n * warehouse. For metric views it makes one status-only probe and DESCRIBEs\n * only when the warehouse is already RUNNING, otherwise emits permissive\n * degraded types immediately. `\"blocking\"` waits for / starts the warehouse\n * first, failing the build only for a deleted/deleting one.\n * @param options.metricViewsFolder - folder that holds `definitions.json`\n * (`<root>/config/metric-views`). Optional and independent of `queryFolder`:\n * metric-view types generate whenever this folder holds a config, even if the\n * app has no `config/queries`. When omitted it defaults to a sibling\n * `metric-views` directory of `queryFolder` (so query-only callers keep\n * working); when neither is given, the metric path is skipped.\n * @param options.mvOutFile - optional output file for the MetricRegistry\n * augmentation. Defaults to a sibling `metric-views.d.ts` file under the same\n * directory as `outFile`. Skipped entirely if `definitions.json` is absent.\n * @param options.metricFetcher - optional DescribeFetcher used by\n * {@link syncMetrics} (tests inject a mock; production lazily builds a\n * default WorkspaceClient-backed one). An injected fetcher always runs: it\n * hits no warehouse, so it bypasses both the non-blocking gate and the\n * blocking preflight.\n */\nexport async function generateFromEntryPoint(options: {\n outFile: string;\n queryFolder?: string;\n metricViewsFolder?: string;\n warehouseId: string;\n noCache?: boolean;\n mode?: PreflightMode;\n mvOutFile?: string;\n metricFetcher?: DescribeFetcher;\n}) {\n const {\n outFile,\n queryFolder,\n warehouseId,\n noCache,\n mode = \"non-blocking\",\n mvOutFile,\n metricFetcher,\n } = options;\n\n // Metric config lives in `config/metric-views/`, a sibling of the queries\n // folder. Prefer the explicit option; otherwise derive the sibling of\n // `queryFolder` so callers that pass only `queryFolder` keep emitting metric\n // types. Undefined when neither is given → the metric path stays dormant.\n const metricViewsFolder =\n options.metricViewsFolder ??\n (queryFolder ? path.resolve(queryFolder, \"..\", \"metric-views\") : undefined);\n\n const projectRoot = resolveProjectRoot(outFile);\n\n logger.debug(\"Starting type generation...\");\n\n let queryRegistry: QuerySchema[] = [];\n let syntaxErrors: QuerySyntaxError[] = [];\n // Deterministic fatal errors only (404/400).\n let fatalErrors: QueryFatalError[] = [];\n // Track whether an environmental failure occurred in blocking mode.\n let hadEnvironmentalFailure = false;\n // Track the coarse cause of the environmental failure for the warning message.\n let environmentalCause: \"auth\" | \"unreachable\" | \"unavailable\" | undefined;\n\n if (queryFolder) {\n const result = await generateQueriesFromDescribe(queryFolder, warehouseId, {\n noCache,\n mode,\n });\n queryRegistry = result.schemas;\n syntaxErrors = result.syntaxErrors ?? [];\n fatalErrors = result.fatalErrors ?? [];\n hadEnvironmentalFailure =\n hadEnvironmentalFailure || (result.hadEnvironmentalFailure ?? false);\n environmentalCause =\n environmentalCause ?? result.environmentalCause ?? undefined;\n }\n\n const typeDeclarations = generateTypeDeclarations(queryRegistry);\n\n // In blocking mode, never overwrite committed types with a schema explicitly\n // marked degraded. Leave the committed .d.ts as the fallback of record.\n // Non-blocking mode always writes.\n const hasAnyDegradedQuery = queryRegistry.some(isQueryDegraded);\n if (mode === \"blocking\" && hasAnyDegradedQuery) {\n // A degraded schema always participates in the committed-types gate. Keep\n // this invariant next to write suppression so a new producer cannot update\n // one decision without the other.\n hadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unavailable\";\n }\n const shouldWriteQueries = mode !== \"blocking\" || !hasAnyDegradedQuery;\n\n if (shouldWriteQueries) {\n await fs.mkdir(path.dirname(outFile), { recursive: true });\n await fs.writeFile(outFile, typeDeclarations, \"utf-8\");\n }\n\n // Metric-view types: emit whenever a metric-views folder is resolved (gated\n // on the metric config's own dir, NOT the queries folder — an app can declare\n // metric views without any `.sql` queries). `syncMetricViewsTypes` still\n // returns `noConfig` when the folder holds no `definitions.json`.\n if (metricViewsFolder) {\n const mvFile =\n mvOutFile ?? path.join(path.dirname(outFile), METRIC_TYPES_FILE);\n\n let mvResult: SyncMetricViewsTypesResult;\n try {\n mvResult = await syncMetricViewsTypes({\n metricViewsFolder,\n warehouseId,\n metricOutFile: mvFile,\n cache: !noCache,\n metricFetcher,\n mode,\n // In blocking mode, never overwrite committed metric types with a\n // degraded result — including on runs that go on to throw, so a failing\n // build leaves the committed .d.ts intact. Non-blocking always writes.\n suppressDegradedWrite: mode === \"blocking\",\n });\n } catch (configError) {\n // syncMetricViewsTypes only throws for a malformed definitions.json — re-throw as a message-only TypegenFatalError.\n throw new TypegenFatalError(\n [\n {\n name: \"config/metric-views/definitions.json\",\n message: getErrorDiagnostic(configError),\n },\n ],\n warehouseId,\n );\n }\n\n // Deleted/deleting-warehouse fatal preflight (blocking mode only);\n // empty (no-op) when definitions.json is absent or in non-blocking mode.\n // Only deterministic fatals are recorded in fatalErrors.\n for (const fe of mvResult.fatalErrors) {\n fatalErrors.push(fe);\n }\n\n // Thread through the environmental failure flag and cause.\n hadEnvironmentalFailure =\n hadEnvironmentalFailure || (mvResult.hadEnvironmentalFailure ?? false);\n environmentalCause =\n environmentalCause ?? mvResult.environmentalCause ?? undefined;\n\n // Blocking (`--wait` / prod Vite) escalates only deterministic per-key\n // DESCRIBE failures. Transient connectivity failures are already recorded\n // as environmental by syncMetricViewsTypes and fall through to the\n // committed-types gate below.\n if (mode === \"blocking\") {\n for (const failure of mvResult.failures) {\n if (failure.transient) continue;\n fatalErrors.push({\n name: failure.key,\n message: `metric view ${failure.key} (${failure.source}) could not be described: ${failure.reason}`,\n });\n }\n }\n }\n\n await removeOldGeneratedTypes(projectRoot, \"appKitTypes.d.ts\");\n await migrateProjectConfig(projectRoot);\n\n // Deterministic failures (SQL syntax errors or 404/400 HTTP) always crash regardless of mode.\n if (syntaxErrors.length > 0) {\n throw new TypegenSyntaxError(syntaxErrors, warehouseId, fatalErrors);\n }\n if (fatalErrors.length > 0) {\n throw new TypegenFatalError(fatalErrors, warehouseId);\n }\n\n // Environmental failures (in blocking mode) trigger the has-types gate.\n if (mode === \"blocking\" && hadEnvironmentalFailure) {\n // Determine resolved metric-views file for the has-types check.\n const resolvedMvFile =\n options.mvOutFile ?? path.join(path.dirname(outFile), METRIC_TYPES_FILE);\n\n const hasTypes = hasCommittedTypes(outFile, resolvedMvFile);\n\n if (hasTypes) {\n // Committed types present: emit loud warning and exit 0.\n const warningMessage = determineWarningMessage(\n environmentalCause ?? \"unavailable\",\n warehouseId,\n );\n logger.warn(warningMessage);\n } else {\n // No committed types: crash with a generic message.\n throw new TypegenFatalError(\n [\n {\n name: \"type-generator\",\n message: `Warehouse ${warehouseId} could not be reached and no committed types exist. Run 'npx @databricks/appkit generate-types --wait' locally and commit the generated .d.ts files.`,\n },\n ],\n warehouseId,\n );\n }\n }\n\n logger.debug(\"Type generation complete!\");\n}\n\n/**\n * Result of a {@link syncMetricViewsTypes} run, returned to the caller (the CLI\n * directly, or {@link generateFromEntryPoint} which delegates to it) so it can\n * report what happened and decide its exit code.\n */\nexport interface SyncMetricViewsTypesResult {\n metricOutFile?: string;\n schemas: MetricSchema[];\n failures: MetricSyncFailure[];\n /**\n * `true` when no `definitions.json` was found in the metric-views folder, so\n * nothing was synced.\n */\n noConfig: boolean;\n /**\n * Per-key fatal preflight errors (empty except in the `blocking`-mode\n * deleted/deleting-warehouse and deterministic-preflight-failure cases).\n * {@link generateFromEntryPoint} surfaces these by throwing\n * {@link TypegenFatalError}; when the run also degraded, `suppressDegradedWrite`\n * means no artifact was written and the committed types stand. A `\"describe-now\"`\n * run sets no blocking preflight, so for that mode this is always empty.\n * ONLY contains deterministic failures (404/400).\n */\n fatalErrors: Array<{ name: string; message: string }>;\n /**\n * `true` when an environmental failure occurred in blocking mode (auth, connectivity,\n * DELETED/DELETING, wait-timeout, or other unrecognized failures). Used by\n * {@link generateFromEntryPoint} to decide whether to apply the has-types gate.\n * Does not directly cause a throw — the gate decides that. Always false in\n * non-blocking or describe-now mode.\n */\n hadEnvironmentalFailure?: boolean;\n /**\n * Coarse cause label for the environmental failure, one of \"auth\" (401/403),\n * \"unreachable\" (connectivity), or \"unavailable\" (other). Only set when\n * hadEnvironmentalFailure is true; used by the warning message.\n */\n environmentalCause?: \"auth\" | \"unreachable\" | \"unavailable\";\n}\n\n/**\n * Unified metric-view type-generation pipeline behind {@link\n * generateFromEntryPoint}'s metric section (which forwards its\n * `\"non-blocking\"`/`\"blocking\"` mode). Also directly callable with the default\n * `\"describe-now\"` mode for a focused, always-converge metric refresh.\n *\n *\n * @param options.metricViewsFolder - folder that holds `definitions.json` (`<root>/config/metric-views`).\n * @param options.warehouseId - SQL warehouse used for `DESCRIBE TABLE EXTENDED`.\n * @param options.metricOutFile - output path for the MetricRegistry `.d.ts`.\n * @param options.cache - cache toggle, default ON. Only `cache === false` disables it (so `undefined`/`true` keep caching).\n * @param options.metricFetcher - optional injected {@link DescribeFetcher}\n * @param options.mode - preflight/gate policy, default `\"describe-now\"`. When set to `\"blocking\"`,\n * metric-view .d.ts writes are suppressed if any metric is degraded (to preserve committed files).\n * @param options.suppressDegradedWrite - when true (only in `mode === \"blocking\"` context), skip\n * the metricOutFile write if any metric schema has `degraded === true`. Used to prevent\n * overwriting committed .d.ts files with degraded types in blocking mode.\n */\nexport async function syncMetricViewsTypes(options: {\n metricViewsFolder: string;\n warehouseId: string;\n metricOutFile: string;\n cache?: boolean;\n metricFetcher?: DescribeFetcher;\n mode?: \"describe-now\" | \"non-blocking\" | \"blocking\";\n suppressDegradedWrite?: boolean;\n}): Promise<SyncMetricViewsTypesResult> {\n const {\n metricViewsFolder,\n warehouseId,\n metricOutFile,\n cache: cacheEnabled,\n metricFetcher,\n mode = \"describe-now\",\n suppressDegradedWrite,\n } = options;\n\n // Only `cache === false` disables caching; `undefined`/`true` keep it on.\n const noCache = cacheEnabled === false;\n\n const mvConfig = await readMetricConfig(metricViewsFolder);\n if (!mvConfig) {\n // No definitions.json — additive path stays dormant. The CLI turns this\n // into a friendly \"nothing to sync\" message and exits 0;\n // generateFromEntryPoint simply ignores `noConfig`.\n return { schemas: [], failures: [], fatalErrors: [], noConfig: true };\n }\n\n const resolution = resolveMetricConfig(mvConfig);\n\n const fatalErrors: Array<{ name: string; message: string }> = [];\n\n // Load the shared typegen cache and copy its `metrics` section into a null-prototype map.\n const cache = await loadCache();\n const mvCacheSection: Record<string, MetricCacheEntry> = Object.create(null);\n if (!noCache && cache.metrics) {\n for (const key of Object.keys(cache.metrics)) {\n mvCacheSection[key] = cache.metrics[key];\n }\n }\n\n // Partition BEFORE any gate/preflight decision: a hit (a structurally valid,\n // hash-matching, NON-degraded cached entry) is served from cache no matter\n // what the warehouse is doing. The cache only ever holds successful describes\n // (a degraded outcome is never persisted — see the write block below), so the\n // `degraded !== true` guard is normally moot; it also defends against a stale\n // degraded entry left by an older writer, which re-describes instead of\n // serving. Everything else (new, edited, unrevivable, or degraded) is eligible\n // for DESCRIBE, so a fully-warm pass makes zero warehouse calls and constructs\n // zero clients. Mirrors the query path: only a good result is cache-servable.\n const hitSchemas = new Map<string, MetricSchema>();\n const describeNeeded: typeof resolution.entries = [];\n for (const entry of resolution.entries) {\n const prior = mvCacheSection[entry.key];\n if (\n prior !== undefined &&\n isRevivableMetricCacheEntry(prior) &&\n prior.hash === metricCacheHash(entry.source, entry.lane) &&\n prior.schema.degraded !== true\n ) {\n hitSchemas.set(entry.key, prior.schema);\n } else {\n describeNeeded.push(entry);\n }\n }\n\n let mvClient: WorkspaceClient | undefined;\n const getMvClient = (): WorkspaceClient => {\n mvClient ??= createWorkspaceClient();\n return mvClient;\n };\n\n // Blocking-mode preflight: ensure the warehouse is running before the MV DESCRIBE\n // batch (probe → decide → wait / start+wait; only DELETED/DELETING is fatal). Two softenings vs the query preflight: a failed probe and a timed-out wait are NOT fatal here — we fall through to syncMetrics, which classifies a still-not-ready warehouse as degraded rather than failing the build. Skipped for `describe-now`/`non-blocking` (only `mode === \"blocking\"` enters here).\n let preflightFatalMessage: string | undefined;\n let hadEnvironmentalFailure = false;\n let environmentalCause: \"auth\" | \"unreachable\" | \"unavailable\" | undefined;\n if (\n mode === \"blocking\" &&\n metricFetcher === undefined &&\n describeNeeded.length > 0\n ) {\n try {\n const state = await getWarehouseState(getMvClient(), warehouseId);\n const decision = decidePreflight(state, mode);\n if (decision === \"fatal\") {\n preflightFatalMessage = `warehouse ${warehouseId} is ${state}`;\n // State-based DELETED/DELETING is environmental, not deterministic.\n hadEnvironmentalFailure = true;\n environmentalCause = \"unavailable\";\n } else if (decision === \"startWaitProceed\") {\n // treatStoppedAsTransient rides out the stale pre-start STOPPED/STOPPING\n // reading, same as the query preflight.\n await startWarehouse(getMvClient(), warehouseId);\n const settled = await waitUntilRunning(getMvClient(), warehouseId, {\n maxMs: MV_PREFLIGHT_WAIT_MAX_MS,\n treatStoppedAsTransient: true,\n });\n if (settled !== \"RUNNING\") {\n // With treatStoppedAsTransient, a non-RUNNING resolve is exactly\n // DELETED/DELETING — the warehouse was deleted while we waited.\n preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;\n hadEnvironmentalFailure = true;\n }\n } else if (decision === \"waitThenProceed\") {\n const settled = await waitUntilRunning(getMvClient(), warehouseId, {\n maxMs: MV_PREFLIGHT_WAIT_MAX_MS,\n });\n if (settled === \"DELETED\" || settled === \"DELETING\") {\n // Deleted mid-wait: fatal. Environmental (state-based).\n preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;\n hadEnvironmentalFailure = true;\n }\n }\n } catch (err) {\n // Connectivity blip: fall through to syncMetrics, whose DESCRIBEs degrade\n // a not-ready / unreachable warehouse rather than throwing.\n if (!isConnectivityError(err)) {\n // Classify: deterministic (404/400) or environmental (auth, etc).\n const classification = classifyBlockingFailure(err);\n if (classification === \"deterministic\") {\n // Keep as fatal preflight for deterministic errors (404/400).\n preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n } else {\n // Environmental: set preflightFatalMessage so DESCRIBE is skipped, but\n // mark hadEnvironmentalFailure so the gate handles it later (not added\n // to fatalErrors).\n preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n hadEnvironmentalFailure = true;\n environmentalCause = classifyEnvironmentalCause(err);\n }\n }\n }\n }\n\n let gateState: WarehouseState | undefined;\n let describeNow =\n metricFetcher !== undefined ||\n mode !== \"non-blocking\" ||\n describeNeeded.length === 0;\n if (!describeNow) {\n try {\n gateState = await probeWarehouseState(getMvClient, warehouseId);\n } catch (err) {\n preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n }\n describeNow = gateState === \"RUNNING\";\n }\n\n let described: MetricSchema[];\n let failures: MetricSyncFailure[] = [];\n if (preflightFatalMessage !== undefined) {\n // Fatal preflight (deleted/deleting warehouse or deterministic error):\n // skip DESCRIBE, emit degraded schemas so both artifacts are still written,\n // and record one fatal error per describe-needed key (cache hits are\n // unaffected) ONLY if it's a deterministic error. Environmental failures\n // degrade silently. The degraded schemas are not cached (see the write\n // block), so a later pass re-probes.\n described = describeNeeded.map(emptyMetricSchema);\n // Only deterministic fatals (404/400) record errors; environmental failures\n // degrade silently for the has-types gate to handle.\n if (!hadEnvironmentalFailure) {\n for (const entry of describeNeeded) {\n fatalErrors.push({ name: entry.key, message: preflightFatalMessage });\n }\n }\n } else if (describeNeeded.length === 0) {\n // Nothing left to describe — every configured key was a cache hit.\n // syncMetrics would be a no-op (and building its fetcher would construct a\n // client for nothing); artifacts regenerate from cache.\n described = [];\n } else if (describeNow) {\n const fetcher =\n metricFetcher ??\n createWorkspaceDescribeFetcher(getMvClient(), warehouseId);\n ({ schemas: described, failures } = await syncMetrics(\n { entries: describeNeeded },\n fetcher,\n ));\n\n // Surface DESCRIBE failures loudly: a misconfigured definitions.json would\n // otherwise silently ship an empty entry that the runtime fail-closed gate\n // 503s in production. syncMetrics is log-free; this caller is the single\n // owner of failure logging.\n if (failures.length > 0) {\n for (const f of failures) {\n logger.warn(\n \"metric sync failed for %s (%s): %s\",\n f.key,\n f.source,\n f.reason,\n );\n }\n }\n\n // A rejected DESCRIBE with a connectivity signal is expected to recover on\n // a later pass. In blocking mode, route it through the same committed-types\n // gate as preflight outages instead of treating it as a configuration error.\n if (mode === \"blocking\" && failures.some((failure) => failure.transient)) {\n hadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unreachable\";\n }\n\n // Degraded-but-not-failed keys: the warehouse answered with a non-terminal\n // state (stopped / cold-starting), so their schemas are unknown.\n const failedKeys = new Set(failures.map((f) => f.key));\n const degradedKeys = described\n .filter((s) => s.degraded && !failedKeys.has(s.key))\n .map((s) => s.key);\n if (degradedKeys.length > 0) {\n logger.info(\n \"Warehouse %s did not return schemas for %d metric view(s) (%s) — wrote degraded metric types (permissive); they will refresh once the warehouse is available.\",\n warehouseId,\n degradedKeys.length,\n degradedKeys.join(\", \"),\n );\n hadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unavailable\";\n }\n } else {\n // Un-probed DESCRIBEs deliberately skipped, not failures: emit each\n // describe-needed key as a degraded schema so both artifacts exist; cache\n // hits keep serving last-known-good. This is an environmental failure path.\n described = describeNeeded.map(emptyMetricSchema);\n logger.info(\n \"Warehouse %s is not running — wrote degraded metric types (permissive) for %d metric view(s) (%s); they will refresh once the warehouse is available.\",\n warehouseId,\n describeNeeded.length,\n describeNeeded.map((e) => e.key).join(\", \"),\n );\n hadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unavailable\";\n }\n\n // Cache only successful schema results for describe-needed keys; remove stale cache for degraded ones.\n for (let i = 0; i < describeNeeded.length; i++) {\n // syncMetrics return one schema per entry in entry order, so described[i] always belongs to describeNeeded[i].\n const entry = describeNeeded[i];\n if (described[i].degraded === true) {\n delete mvCacheSection[entry.key];\n continue;\n }\n mvCacheSection[entry.key] = {\n hash: metricCacheHash(entry.source, entry.lane),\n schema: described[i],\n // Vestigial, mirrors the query path's only cache write (always false): a\n // persisted entry is by construction a good result, so it never needs a\n // re-describe flag. Kept for on-disk shape compatibility with existing\n // version-3 caches (isRevivableMetricCacheEntry gates on a boolean).\n retry: false,\n };\n }\n\n // Prune entries whose key is no longer configured\n const configuredKeys = new Set(resolution.entries.map((e) => e.key));\n let prunedCount = 0;\n for (const key of Object.keys(mvCacheSection)) {\n if (!configuredKeys.has(key)) {\n delete mvCacheSection[key];\n prunedCount++;\n }\n }\n\n // Save when this pass produced outcomes, bypassed the cache, or pruned.\n if (describeNeeded.length > 0 || noCache || prunedCount > 0) {\n cache.metrics = mvCacheSection;\n await saveCache(cache);\n }\n\n // Merge cached hits with fresh results back into config order.\n const describedByKey = new Map<string, MetricSchema>();\n for (const schema of described) {\n describedByKey.set(schema.key, schema);\n }\n const schemas = resolution.entries.map((entry) => {\n const schema = hitSchemas.get(entry.key) ?? describedByKey.get(entry.key);\n if (schema !== undefined) return schema;\n logger.warn(\n \"no schema resolved for metric key %s — emitting degraded types (should not happen)\",\n entry.key,\n );\n return emptyMetricSchema(entry);\n });\n\n // Same anti-clobber rule as the query path: when suppressDegradedWrite is set\n // (blocking mode), skip the write if any metric degraded, preserving the\n // committed metric-views.d.ts. Non-blocking mode always writes.\n const shouldWriteMetrics =\n !suppressDegradedWrite || !hasAnyDegradedMetrics(schemas);\n\n if (shouldWriteMetrics) {\n await fs.mkdir(path.dirname(metricOutFile), { recursive: true });\n await fs.writeFile(\n metricOutFile,\n generateMetricTypeDeclarations(schemas),\n \"utf-8\",\n );\n }\n\n logger.debug(\n \"Wrote MetricRegistry augmentation for %d metric(s)%s\",\n schemas.length,\n failures.length > 0 ? ` (${failures.length} failure(s))` : \"\",\n );\n\n return {\n metricOutFile,\n schemas,\n failures,\n fatalErrors,\n noConfig: false,\n hadEnvironmentalFailure:\n mode === \"blocking\" ? hadEnvironmentalFailure : undefined,\n environmentalCause:\n mode === \"blocking\" && hadEnvironmentalFailure\n ? environmentalCause\n : undefined,\n };\n}\n\n// Rolldown tree-shaking only preserves \"own exports\" (locally defined) — not re-exports.\n// A local binding ensures the serving vite plugin's import keeps this in the dependency graph,\n// mirroring how generateFromEntryPoint (also defined here) is preserved via the analytics vite plugin.\nexport const generateServingTypes = generateServingTypesImpl;\n\n// Re-export the mv-registry types so consumers (CLI, the type-generator\n// .d.ts shim in `packages/shared`) can pick them up from this entry point —\n// the .d.ts shim documents these as part of the package's public surface.\nexport type {\n MetricColumnMetadata,\n MetricLane,\n MetricSchema,\n MetricSyncFailure,\n MetricSyncResult,\n};\n\nexport const TYPES_DIR = \"appkit-types\";\nexport const ANALYTICS_TYPES_FILE = \"analytics.d.ts\";\nexport const SERVING_TYPES_FILE = \"serving.d.ts\";\nexport const METRIC_TYPES_FILE = \"metric-views.d.ts\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["generateServingTypesImpl"],"sources":["../../src/type-generator/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport pc from \"picocolors\";\nimport { createLogger } from \"../logging/logger\";\nimport {\n createWorkspaceClient,\n type WorkspaceClient,\n} from \"../workspace-client\";\nimport {\n isRevivableMetricCacheEntry,\n loadCache,\n type MetricCacheEntry,\n metricCacheHash,\n saveCache,\n} from \"./cache\";\nimport {\n classifyBlockingFailure,\n classifyEnvironmentalCause,\n getErrorDiagnostic,\n isConnectivityError,\n} from \"./errors\";\nimport {\n migrateProjectConfig,\n removeOldGeneratedTypes,\n resolveProjectRoot,\n} from \"./migration\";\nimport { readMetricConfig, resolveMetricConfig } from \"./mv-registry/config\";\nimport { createWorkspaceDescribeFetcher } from \"./mv-registry/describe\";\nimport { generateMetricTypeDeclarations } from \"./mv-registry/render-types\";\nimport { emptyMetricSchema, syncMetrics } from \"./mv-registry/sync\";\nimport type {\n DescribeFetcher,\n MetricColumnMetadata,\n MetricLane,\n MetricSchema,\n MetricSyncFailure,\n MetricSyncResult,\n} from \"./mv-registry/types\";\nimport { decidePreflight, type PreflightMode } from \"./preflight\";\nimport { generateQueriesFromDescribe } from \"./query-registry\";\nimport { generateServingTypes as generateServingTypesImpl } from \"./serving/generator\";\nimport type { QueryFatalError, QuerySchema, QuerySyntaxError } from \"./types\";\nimport {\n getWarehouseState,\n startWarehouse,\n type WarehouseState,\n waitUntilRunning,\n} from \"./warehouse-status\";\n\ndotenv.config();\n\nconst logger = createLogger(\"type-generator\");\n\n/**\n * Upper bound (~5 min) on how long the Metric Views path's `blocking`-mode preflight\n * waits for a warehouse to reach RUNNING. Mirrors the query path's (unexported)\n * `PREFLIGHT_WAIT_MAX_MS` in query-registry.ts.\n */\nconst MV_PREFLIGHT_WAIT_MAX_MS = 300_000;\n\nfunction determineWarningMessage(\n cause: \"auth\" | \"unreachable\" | \"unavailable\",\n warehouseId: string,\n): string {\n const causeLabel =\n cause === \"auth\"\n ? \"auth blocked\"\n : cause === \"unreachable\"\n ? \"warehouse unreachable\"\n : \"warehouse unavailable\";\n // Use a stable prefix for greppability and CI log matching.\n return `AppKit typegen: using committed types — warehouse ${warehouseId} ${causeLabel}; please check warehouse status and retry`;\n}\n\ntype TypegenFailure = QuerySyntaxError | QueryFatalError;\n\nfunction plural(count: number, singular: string, pluralForm = `${singular}s`) {\n return count === 1 ? singular : pluralForm;\n}\n\nfunction isQueryDegraded(schema: QuerySchema): boolean {\n return schema.degraded === true;\n}\n\nfunction hasAnyDegradedMetrics(schemas: MetricSchema[]): boolean {\n return schemas.some((s) => s.degraded === true);\n}\n\nfunction formatFailureRows(\n label: string,\n queries: TypegenFailure[],\n color: (value: string) => string,\n) {\n if (queries.length === 0) return [];\n\n // Group by message so a shared failure — e.g. a warehouse-level fatal that\n // hits every query identically — prints once instead of repeating per row.\n const byMessage = new Map<string, string[]>();\n for (const { name, message } of queries) {\n const names = byMessage.get(message);\n if (names) names.push(name);\n else byMessage.set(message, [name]);\n }\n\n const maxNameLen = Math.max(...queries.map((query) => query.name.length));\n const tag = color(label.padEnd(7));\n const rows: string[] = [];\n for (const [message, names] of byMessage) {\n if (names.length === 1) {\n rows.push(\n ` ${tag} ${pc.bold(names[0].padEnd(maxNameLen))} ${pc.dim(message)}`,\n );\n continue;\n }\n // Shared message → print it once, then list the affected query names.\n rows.push(\n ` ${tag} ${pc.dim(message)} ${pc.dim(`(${names.length} ${plural(names.length, \"query\", \"queries\")})`)}`,\n );\n rows.push(\n ` ${names.map((name) => pc.bold(name)).join(pc.dim(\", \"))}`,\n );\n }\n return rows;\n}\n\nfunction formatTypegenFailureMessage(options: {\n syntaxErrors: QuerySyntaxError[];\n fatalErrors?: QueryFatalError[];\n warehouseId?: string;\n title: string;\n causes: string[];\n nextStep: string;\n}) {\n const { syntaxErrors, fatalErrors = [], warehouseId, title } = options;\n const total = syntaxErrors.length + fatalErrors.length;\n const separator = pc.dim(\"─\".repeat(60));\n const warehouse = warehouseId\n ? ` against ${pc.dim(`warehouse ${warehouseId}`)}`\n : \"\";\n\n return [\n ` ${pc.bold(pc.red(\"Type generation failed\"))}`,\n ` ${separator}`,\n ` ${title}: ${total} ${plural(total, \"query\", \"queries\")} could not be described${warehouse}.`,\n ` AppKit wrote generated types with ${pc.bold(\"result: unknown\")} for the failed ${plural(total, \"query\", \"queries\")}.`,\n \"\",\n ...formatFailureRows(\"SQL ERR\", syntaxErrors, pc.red),\n ...(syntaxErrors.length > 0 && fatalErrors.length > 0 ? [\"\"] : []),\n ...formatFailureRows(\"FATAL\", fatalErrors, pc.red),\n \"\",\n ` ${pc.bold(\"Common causes\")}`,\n ...options.causes.map((cause) => ` - ${cause}`),\n \"\",\n ` ${pc.bold(\"Next step\")}`,\n ` ${options.nextStep}`,\n ].join(\"\\n\");\n}\n\n/**\n * Thrown when one or more queries fail `DESCRIBE QUERY` against a *reachable*\n * warehouse — i.e. genuine SQL errors (bad table, syntax, incompatible type),\n * as opposed to a connectivity failure (warehouse unreachable), which degrades\n * silently. Whether this is fatal is the caller's decision: the Vite plugin and\n * CLI fail the build in production and warn-only in development.\n */\nexport class TypegenSyntaxError extends Error {\n readonly queries: QuerySyntaxError[];\n readonly fatalQueries: QueryFatalError[];\n\n constructor(\n queries: QuerySyntaxError[],\n warehouseId?: string,\n fatalQueries: QueryFatalError[] = [],\n ) {\n super(\n formatTypegenFailureMessage({\n syntaxErrors: queries,\n fatalErrors: fatalQueries,\n warehouseId,\n title: \"DESCRIBE QUERY failed\",\n causes: [\n \"SQL syntax errors\",\n \"missing tables or views\",\n \"warehouse format incompatibilities\",\n ],\n nextStep: warehouseId\n ? `Run each SQL ERR query directly in a Databricks SQL editor against warehouse ${pc.bold(warehouseId)}.`\n : \"Run each SQL ERR query directly in a Databricks SQL editor.\",\n }),\n );\n this.name = \"TypegenSyntaxError\";\n this.queries = queries;\n this.fatalQueries = fatalQueries;\n }\n}\n\n/**\n * Thrown when DESCRIBE QUERY could not be requested because of a non-SQL fatal\n * setup/request problem, such as missing permissions, invalid warehouse IDs, or\n * malformed SDK configuration. Like TypegenSyntaxError, this is thrown only\n * after the declaration file has been written with `result: unknown` entries.\n */\nexport class TypegenFatalError extends Error {\n readonly queries: QueryFatalError[];\n\n constructor(queries: QueryFatalError[], warehouseId?: string) {\n super(\n formatTypegenFailureMessage({\n syntaxErrors: [],\n fatalErrors: queries,\n warehouseId,\n title: \"DESCRIBE QUERY could not be requested\",\n causes: [\n \"missing warehouse permissions\",\n \"invalid warehouse ID\",\n \"authentication failure\",\n \"SDK configuration errors\",\n ],\n nextStep: warehouseId\n ? `Verify access to warehouse ${pc.bold(warehouseId)} and rerun type generation.`\n : \"Verify warehouse access and rerun type generation.\",\n }),\n );\n this.name = \"TypegenFatalError\";\n this.queries = queries;\n }\n}\n\n/**\n * Generate type declarations for QueryRegistry\n * Create the d.ts file from the plugin routes and query schemas\n * @param querySchemas - the list of query schemas\n * @returns - the type declarations as a string\n */\nfunction generateTypeDeclarations(querySchemas: QuerySchema[] = []): string {\n const queryEntries = querySchemas\n .map(({ name, type }) => {\n const indentedType = type\n .split(\"\\n\")\n .map((line, i) => (i === 0 ? line : ` ${line}`))\n .join(\"\\n\");\n return ` ${name}: ${indentedType}`;\n })\n .join(\";\\n\");\n\n const querySection = queryEntries ? `\\n${queryEntries};\\n ` : \"\";\n\n return `// Auto-generated by AppKit - DO NOT EDIT\n// Generated by 'npx @databricks/appkit generate-types' or Vite plugin during build\nimport \"@databricks/appkit-ui/react\";\nimport type { SQLTypeMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLBinaryMarker, SQLDateMarker, SQLTimestampMarker } from \"@databricks/appkit-ui/js\";\n\ndeclare module \"@databricks/appkit-ui/react\" {\n interface QueryRegistry {${querySection}}\n}\n`;\n}\n\n/**\n * Status-only probe for the metric-view gate in {@link generateFromEntryPoint}\n *\n * Uses {@link getWarehouseState} (`warehouses.get`) —\n * a read-only GET that can never start the warehouse\n *\n * Takes the lazy client *getter* so the probe also absorbs client construction failure.\n * A connectivity blip returns `undefined`, which the gate reads as transient not-running;\n * a deterministic failure (auth, bad id) is re-thrown so the gate can classify it\n * fatal rather than silently degrading.\n */\nasync function probeWarehouseState(\n getClient: () => WorkspaceClient,\n warehouseId: string,\n): Promise<WarehouseState | undefined> {\n try {\n return await getWarehouseState(getClient(), warehouseId);\n } catch (err) {\n // Connectivity blip → undefined (gate degrades, retries next pass). A\n // deterministic failure (auth, bad warehouse id, client construction) must\n // not masquerade as not-running — re-throw so the gate pins it fatal, the\n // same split the query path's preflight makes.\n if (isConnectivityError(err)) return undefined;\n throw err;\n }\n}\n\n/**\n * Entry point for generating type declarations from all imported files\n * @param options - the options for the generation\n * @param options.entryPoint - the entry point file\n * @param options.outFile - the output file\n * @param options.noCache - skip the typegen cache entirely: every query is\n * re-described, and the metric path ignores its cached schemas (every\n * configured key becomes describe-needed) and overwrites the cache's\n * `metrics` section with this pass's results.\n * @param options.mode - preflight policy (see {@link PreflightMode}), default\n * `\"non-blocking\"`. For queries, `\"non-blocking\"` never touches the\n * warehouse. For metric views it makes one status-only probe and DESCRIBEs\n * only when the warehouse is already RUNNING, otherwise emits permissive\n * degraded types immediately. `\"blocking\"` waits for / starts the warehouse\n * first, failing the build only for a deleted/deleting one.\n * @param options.metricViewsFolder - folder that holds `definitions.json`\n * (`<root>/config/metric-views`). Optional and independent of `queryFolder`:\n * metric-view types generate whenever this folder holds a config, even if the\n * app has no `config/queries`. When omitted it defaults to a sibling\n * `metric-views` directory of `queryFolder` (so query-only callers keep\n * working); when neither is given, the metric path is skipped.\n * @param options.mvOutFile - optional output file for the MetricRegistry\n * augmentation. Defaults to a sibling `metric-views.ts` file under the same\n * directory as `outFile`. Skipped entirely if `definitions.json` is absent.\n * @param options.metricFetcher - optional DescribeFetcher used by\n * {@link syncMetrics} (tests inject a mock; production lazily builds a\n * default WorkspaceClient-backed one). An injected fetcher always runs: it\n * hits no warehouse, so it bypasses both the non-blocking gate and the\n * blocking preflight.\n */\nexport async function generateFromEntryPoint(options: {\n outFile: string;\n queryFolder?: string;\n metricViewsFolder?: string;\n warehouseId: string;\n noCache?: boolean;\n mode?: PreflightMode;\n mvOutFile?: string;\n metricFetcher?: DescribeFetcher;\n}) {\n const {\n outFile,\n queryFolder,\n warehouseId,\n noCache,\n mode = \"non-blocking\",\n mvOutFile,\n metricFetcher,\n } = options;\n\n // Metric config lives in `config/metric-views/`, a sibling of the queries\n // folder. Prefer the explicit option; otherwise derive the sibling of\n // `queryFolder` so callers that pass only `queryFolder` keep emitting metric\n // types. Undefined when neither is given → the metric path stays dormant.\n const metricViewsFolder =\n options.metricViewsFolder ??\n (queryFolder ? path.resolve(queryFolder, \"..\", \"metric-views\") : undefined);\n const resolvedMvFile =\n mvOutFile ?? path.join(path.dirname(outFile), METRIC_TYPES_FILE);\n\n const projectRoot = resolveProjectRoot(outFile);\n\n logger.debug(\"Starting type generation...\");\n\n let queryRegistry: QuerySchema[] = [];\n let syntaxErrors: QuerySyntaxError[] = [];\n // Deterministic fatal errors only (404/400).\n let fatalErrors: QueryFatalError[] = [];\n let queryHadEnvironmentalFailure = false;\n let metricsHadEnvironmentalFailure = false;\n // Track the coarse cause of the environmental failure for the warning message.\n let environmentalCause: \"auth\" | \"unreachable\" | \"unavailable\" | undefined;\n\n if (queryFolder) {\n const result = await generateQueriesFromDescribe(queryFolder, warehouseId, {\n noCache,\n mode,\n });\n queryRegistry = result.schemas;\n syntaxErrors = result.syntaxErrors ?? [];\n fatalErrors = result.fatalErrors ?? [];\n queryHadEnvironmentalFailure = result.hadEnvironmentalFailure ?? false;\n environmentalCause =\n environmentalCause ?? result.environmentalCause ?? undefined;\n }\n\n const typeDeclarations = generateTypeDeclarations(queryRegistry);\n\n // In blocking mode, never overwrite committed types with a schema explicitly\n // marked degraded. Leave the committed .d.ts as the fallback of record.\n // Non-blocking mode always writes.\n const hasAnyDegradedQuery = queryRegistry.some(isQueryDegraded);\n if (mode === \"blocking\" && hasAnyDegradedQuery) {\n // A degraded schema always participates in the committed-types gate. Keep\n // this invariant next to write suppression so a new producer cannot update\n // one decision without the other.\n queryHadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unavailable\";\n }\n const shouldWriteQueries = mode !== \"blocking\" || !hasAnyDegradedQuery;\n\n if (shouldWriteQueries) {\n await fs.mkdir(path.dirname(outFile), { recursive: true });\n await fs.writeFile(outFile, typeDeclarations, \"utf-8\");\n }\n\n // Metric-view types: emit whenever a metric-views folder is resolved (gated\n // on the metric config's own dir, NOT the queries folder — an app can declare\n // metric views without any `.sql` queries). `syncMetricViewsTypes` still\n // returns `noConfig` when the folder holds no `definitions.json`.\n if (metricViewsFolder) {\n let mvResult: SyncMetricViewsTypesResult;\n try {\n mvResult = await syncMetricViewsTypes({\n metricViewsFolder,\n warehouseId,\n metricOutFile: resolvedMvFile,\n cache: !noCache,\n metricFetcher,\n mode,\n // In blocking mode, never overwrite committed metric types with a\n // degraded result — including on runs that go on to throw, so a failing\n // build leaves the committed type file intact. Non-blocking always writes.\n suppressDegradedWrite: mode === \"blocking\",\n });\n } catch (configError) {\n // syncMetricViewsTypes only throws for a malformed definitions.json — re-throw as a message-only TypegenFatalError.\n throw new TypegenFatalError(\n [\n {\n name: \"config/metric-views/definitions.json\",\n message: getErrorDiagnostic(configError),\n },\n ],\n warehouseId,\n );\n }\n\n // Deleted/deleting-warehouse fatal preflight (blocking mode only);\n // empty (no-op) when definitions.json is absent or in non-blocking mode.\n // Only deterministic fatals are recorded in fatalErrors.\n for (const fe of mvResult.fatalErrors) {\n fatalErrors.push(fe);\n }\n\n metricsHadEnvironmentalFailure =\n (mvResult.hadEnvironmentalFailure ?? false) ||\n (mode === \"blocking\" && hasAnyDegradedMetrics(mvResult.schemas));\n environmentalCause =\n environmentalCause ?? mvResult.environmentalCause ?? undefined;\n\n // Blocking (`--wait` / prod Vite) escalates only deterministic per-key\n // DESCRIBE failures. Transient connectivity failures are already recorded\n // as environmental by syncMetricViewsTypes and fall through to the\n // committed-types gate below.\n if (mode === \"blocking\") {\n for (const failure of mvResult.failures) {\n if (failure.transient) continue;\n fatalErrors.push({\n name: failure.key,\n message: `metric view ${failure.key} (${failure.source}) could not be described: ${failure.reason}`,\n });\n }\n }\n }\n\n await removeOldGeneratedTypes(projectRoot, \"appKitTypes.d.ts\");\n await migrateProjectConfig(projectRoot);\n\n // Deterministic failures (SQL syntax errors or 404/400 HTTP) always crash regardless of mode.\n if (syntaxErrors.length > 0) {\n throw new TypegenSyntaxError(syntaxErrors, warehouseId, fatalErrors);\n }\n if (fatalErrors.length > 0) {\n throw new TypegenFatalError(fatalErrors, warehouseId);\n }\n\n if (\n mode === \"blocking\" &&\n (queryHadEnvironmentalFailure || metricsHadEnvironmentalFailure)\n ) {\n const missingCommittedTypes: string[] = [];\n if (queryHadEnvironmentalFailure && !existsSync(outFile)) {\n missingCommittedTypes.push(path.basename(outFile));\n }\n if (metricsHadEnvironmentalFailure && !existsSync(resolvedMvFile)) {\n missingCommittedTypes.push(path.basename(resolvedMvFile));\n }\n\n if (missingCommittedTypes.length === 0) {\n // Committed types present: emit loud warning and exit 0.\n const warningMessage = determineWarningMessage(\n environmentalCause ?? \"unavailable\",\n warehouseId,\n );\n logger.warn(warningMessage);\n } else {\n throw new TypegenFatalError(\n [\n {\n name: \"type-generator\",\n message: `Warehouse ${warehouseId} could not provide schemas and the required committed type ${plural(missingCommittedTypes.length, \"artifact is\", \"artifacts are\")} missing: ${missingCommittedTypes.join(\", \")}. Run 'npx @databricks/appkit generate-types --wait' locally and commit the generated type files.`,\n },\n ],\n warehouseId,\n );\n }\n }\n\n logger.debug(\"Type generation complete!\");\n}\n\n/**\n * Result of a {@link syncMetricViewsTypes} run, returned to the caller (the CLI\n * directly, or {@link generateFromEntryPoint} which delegates to it) so it can\n * report what happened and decide its exit code.\n */\nexport interface SyncMetricViewsTypesResult {\n metricOutFile?: string;\n schemas: MetricSchema[];\n failures: MetricSyncFailure[];\n /**\n * `true` when no `definitions.json` was found in the metric-views folder, so\n * nothing was synced.\n */\n noConfig: boolean;\n /**\n * Per-key fatal preflight errors (empty except in the `blocking`-mode\n * deleted/deleting-warehouse and deterministic-preflight-failure cases).\n * {@link generateFromEntryPoint} surfaces these by throwing\n * {@link TypegenFatalError}; when the run also degraded, `suppressDegradedWrite`\n * means no artifact was written and the committed types stand. A `\"describe-now\"`\n * run sets no blocking preflight, so for that mode this is always empty.\n * ONLY contains deterministic failures (404/400).\n */\n fatalErrors: Array<{ name: string; message: string }>;\n /**\n * `true` when an environmental failure occurred in blocking mode (auth, connectivity,\n * DELETED/DELETING, wait-timeout, or other unrecognized failures). Used by\n * {@link generateFromEntryPoint} to decide whether to apply the has-types gate.\n * Does not directly cause a throw — the gate decides that. Always false in\n * non-blocking or describe-now mode.\n */\n hadEnvironmentalFailure?: boolean;\n /**\n * Coarse cause label for the environmental failure, one of \"auth\" (401/403),\n * \"unreachable\" (connectivity), or \"unavailable\" (other). Only set when\n * hadEnvironmentalFailure is true; used by the warning message.\n */\n environmentalCause?: \"auth\" | \"unreachable\" | \"unavailable\";\n}\n\n/**\n * Unified metric-view type-generation pipeline behind {@link\n * generateFromEntryPoint}'s metric section (which forwards its\n * `\"non-blocking\"`/`\"blocking\"` mode). Also directly callable with the default\n * `\"describe-now\"` mode for a focused, always-converge metric refresh.\n *\n *\n * @param options.metricViewsFolder - folder that holds `definitions.json` (`<root>/config/metric-views`).\n * @param options.warehouseId - SQL warehouse used for `DESCRIBE TABLE EXTENDED`.\n * @param options.metricOutFile - output path for the MetricRegistry `.ts` (the\n * generated source carries both the `declare module` augmentation and the\n * runtime `metricViewsMetadata` const).\n * @param options.cache - cache toggle, default ON. Only `cache === false` disables it (so `undefined`/`true` keep caching).\n * @param options.metricFetcher - optional injected {@link DescribeFetcher}\n * @param options.mode - preflight/gate policy, default `\"describe-now\"`. When set to `\"blocking\"`,\n * metric-view `.ts` writes are suppressed if any metric is degraded (to preserve committed files).\n * @param options.suppressDegradedWrite - when true (only in `mode === \"blocking\"` context), skip\n * the metricOutFile write if any metric schema has `degraded === true`. Used to prevent\n * overwriting committed type files with degraded types in blocking mode.\n */\nexport async function syncMetricViewsTypes(options: {\n metricViewsFolder: string;\n warehouseId: string;\n metricOutFile: string;\n cache?: boolean;\n metricFetcher?: DescribeFetcher;\n mode?: \"describe-now\" | \"non-blocking\" | \"blocking\";\n suppressDegradedWrite?: boolean;\n}): Promise<SyncMetricViewsTypesResult> {\n const {\n metricViewsFolder,\n warehouseId,\n metricOutFile,\n cache: cacheEnabled,\n metricFetcher,\n mode = \"describe-now\",\n suppressDegradedWrite,\n } = options;\n\n // Only `cache === false` disables caching; `undefined`/`true` keep it on.\n const noCache = cacheEnabled === false;\n\n const mvConfig = await readMetricConfig(metricViewsFolder);\n if (!mvConfig) {\n // No definitions.json — additive path stays dormant. The CLI turns this\n // into a friendly \"nothing to sync\" message and exits 0;\n // generateFromEntryPoint simply ignores `noConfig`.\n return { schemas: [], failures: [], fatalErrors: [], noConfig: true };\n }\n\n const resolution = resolveMetricConfig(mvConfig);\n\n const fatalErrors: Array<{ name: string; message: string }> = [];\n\n // Load the shared typegen cache and copy its `metrics` section into a null-prototype map.\n const cache = await loadCache();\n const mvCacheSection: Record<string, MetricCacheEntry> = Object.create(null);\n if (!noCache && cache.metrics) {\n for (const key of Object.keys(cache.metrics)) {\n mvCacheSection[key] = cache.metrics[key];\n }\n }\n\n // Partition BEFORE any gate/preflight decision: a hit (a structurally valid,\n // hash-matching, NON-degraded cached entry) is served from cache no matter\n // what the warehouse is doing. The cache only ever holds successful describes\n // (a degraded outcome is never persisted — see the write block below), so the\n // `degraded !== true` guard is normally moot; it also defends against a stale\n // degraded entry left by an older writer, which re-describes instead of\n // serving. Everything else (new, edited, unrevivable, or degraded) is eligible\n // for DESCRIBE, so a fully-warm pass makes zero warehouse calls and constructs\n // zero clients. Mirrors the query path: only a good result is cache-servable.\n const hitSchemas = new Map<string, MetricSchema>();\n const describeNeeded: typeof resolution.entries = [];\n for (const entry of resolution.entries) {\n const prior = mvCacheSection[entry.key];\n if (\n prior !== undefined &&\n isRevivableMetricCacheEntry(prior) &&\n prior.hash === metricCacheHash(entry.source, entry.lane) &&\n prior.schema.degraded !== true\n ) {\n hitSchemas.set(entry.key, prior.schema);\n } else {\n describeNeeded.push(entry);\n }\n }\n\n let mvClient: WorkspaceClient | undefined;\n const getMvClient = (): WorkspaceClient => {\n mvClient ??= createWorkspaceClient();\n return mvClient;\n };\n\n // Blocking-mode preflight: ensure the warehouse is running before the MV DESCRIBE\n // batch (probe → decide → wait / start+wait; only DELETED/DELETING is fatal). Two softenings vs the query preflight: a failed probe and a timed-out wait are NOT fatal here — we fall through to syncMetrics, which classifies a still-not-ready warehouse as degraded rather than failing the build. Skipped for `describe-now`/`non-blocking` (only `mode === \"blocking\"` enters here).\n let preflightFatalMessage: string | undefined;\n let hadEnvironmentalFailure = false;\n let environmentalCause: \"auth\" | \"unreachable\" | \"unavailable\" | undefined;\n if (\n mode === \"blocking\" &&\n metricFetcher === undefined &&\n describeNeeded.length > 0\n ) {\n try {\n const state = await getWarehouseState(getMvClient(), warehouseId);\n const decision = decidePreflight(state, mode);\n if (decision === \"fatal\") {\n preflightFatalMessage = `warehouse ${warehouseId} is ${state}`;\n // State-based DELETED/DELETING is environmental, not deterministic.\n hadEnvironmentalFailure = true;\n environmentalCause = \"unavailable\";\n } else if (decision === \"startWaitProceed\") {\n // treatStoppedAsTransient rides out the stale pre-start STOPPED/STOPPING\n // reading, same as the query preflight.\n await startWarehouse(getMvClient(), warehouseId);\n const settled = await waitUntilRunning(getMvClient(), warehouseId, {\n maxMs: MV_PREFLIGHT_WAIT_MAX_MS,\n treatStoppedAsTransient: true,\n });\n if (settled !== \"RUNNING\") {\n // With treatStoppedAsTransient, a non-RUNNING resolve is exactly\n // DELETED/DELETING — the warehouse was deleted while we waited.\n preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;\n hadEnvironmentalFailure = true;\n }\n } else if (decision === \"waitThenProceed\") {\n const settled = await waitUntilRunning(getMvClient(), warehouseId, {\n maxMs: MV_PREFLIGHT_WAIT_MAX_MS,\n });\n if (settled === \"DELETED\" || settled === \"DELETING\") {\n // Deleted mid-wait: fatal. Environmental (state-based).\n preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;\n hadEnvironmentalFailure = true;\n }\n }\n } catch (err) {\n // Connectivity blip: fall through to syncMetrics, whose DESCRIBEs degrade\n // a not-ready / unreachable warehouse rather than throwing.\n if (!isConnectivityError(err)) {\n // Deterministic failures become fatal errors; environmental failures\n // degrade for the committed-types gate.\n const classification = classifyBlockingFailure(err);\n if (classification === \"deterministic\") {\n preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n } else {\n preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n hadEnvironmentalFailure = true;\n environmentalCause = classifyEnvironmentalCause(err);\n }\n }\n }\n }\n\n let gateState: WarehouseState | undefined;\n let describeNow =\n metricFetcher !== undefined ||\n mode !== \"non-blocking\" ||\n describeNeeded.length === 0;\n if (!describeNow) {\n try {\n gateState = await probeWarehouseState(getMvClient, warehouseId);\n } catch (err) {\n preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n }\n describeNow = gateState === \"RUNNING\";\n }\n\n let described: MetricSchema[];\n let failures: MetricSyncFailure[] = [];\n if (preflightFatalMessage !== undefined) {\n // Environmental failures degrade for the committed-types gate; deterministic\n // failures record one fatal error per key. Degraded schemas are not cached.\n described = describeNeeded.map(emptyMetricSchema);\n if (!hadEnvironmentalFailure) {\n for (const entry of describeNeeded) {\n fatalErrors.push({ name: entry.key, message: preflightFatalMessage });\n }\n }\n } else if (describeNeeded.length === 0) {\n // Nothing left to describe — every configured key was a cache hit.\n // syncMetrics would be a no-op (and building its fetcher would construct a\n // client for nothing); artifacts regenerate from cache.\n described = [];\n } else if (describeNow) {\n const fetcher =\n metricFetcher ??\n createWorkspaceDescribeFetcher(getMvClient(), warehouseId);\n ({ schemas: described, failures } = await syncMetrics(\n { entries: describeNeeded },\n fetcher,\n ));\n\n // Surface DESCRIBE failures loudly: a misconfigured definitions.json would\n // otherwise silently ship an empty entry that the runtime fail-closed gate\n // 503s in production. syncMetrics is log-free; this caller is the single\n // owner of failure logging.\n if (failures.length > 0) {\n for (const f of failures) {\n logger.warn(\n \"metric sync failed for %s (%s): %s\",\n f.key,\n f.source,\n f.reason,\n );\n }\n }\n\n // A rejected DESCRIBE with a connectivity signal is expected to recover on\n // a later pass. In blocking mode, route it through the same committed-types\n // gate as preflight outages instead of treating it as a configuration error.\n if (mode === \"blocking\" && failures.some((failure) => failure.transient)) {\n hadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unreachable\";\n }\n\n // Degraded-but-not-failed keys: the warehouse answered with a non-terminal\n // state (stopped / cold-starting), so their schemas are unknown.\n const failedKeys = new Set(failures.map((f) => f.key));\n const degradedKeys = described\n .filter((s) => s.degraded && !failedKeys.has(s.key))\n .map((s) => s.key);\n if (degradedKeys.length > 0) {\n logger.info(\n \"Warehouse %s did not return schemas for %d metric view(s) (%s) — wrote degraded metric types (permissive); they will refresh once the warehouse is available.\",\n warehouseId,\n degradedKeys.length,\n degradedKeys.join(\", \"),\n );\n hadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unavailable\";\n }\n } else {\n // Un-probed DESCRIBEs emit degraded schemas; cache hits remain last-known-good.\n described = describeNeeded.map(emptyMetricSchema);\n logger.info(\n \"Warehouse %s is not running — wrote degraded metric types (permissive) for %d metric view(s) (%s); they will refresh once the warehouse is available.\",\n warehouseId,\n describeNeeded.length,\n describeNeeded.map((e) => e.key).join(\", \"),\n );\n hadEnvironmentalFailure = true;\n environmentalCause = environmentalCause ?? \"unavailable\";\n }\n\n // Cache only successful schema results for describe-needed keys; remove stale cache for degraded ones.\n for (let i = 0; i < describeNeeded.length; i++) {\n // syncMetrics return one schema per entry in entry order, so described[i] always belongs to describeNeeded[i].\n const entry = describeNeeded[i];\n if (described[i].degraded === true) {\n delete mvCacheSection[entry.key];\n continue;\n }\n mvCacheSection[entry.key] = {\n hash: metricCacheHash(entry.source, entry.lane),\n schema: described[i],\n // Vestigial, mirrors the query path's only cache write (always false): a\n // persisted entry is by construction a good result, so it never needs a\n // re-describe flag. Kept for on-disk shape compatibility with existing\n // version-3 caches (isRevivableMetricCacheEntry gates on a boolean).\n retry: false,\n };\n }\n\n // Prune entries whose key is no longer configured\n const configuredKeys = new Set(resolution.entries.map((e) => e.key));\n let prunedCount = 0;\n for (const key of Object.keys(mvCacheSection)) {\n if (!configuredKeys.has(key)) {\n delete mvCacheSection[key];\n prunedCount++;\n }\n }\n\n // Save when this pass produced outcomes, bypassed the cache, or pruned.\n if (describeNeeded.length > 0 || noCache || prunedCount > 0) {\n cache.metrics = mvCacheSection;\n await saveCache(cache);\n }\n\n // Merge cached hits with fresh results back into config order.\n const describedByKey = new Map<string, MetricSchema>();\n for (const schema of described) {\n describedByKey.set(schema.key, schema);\n }\n const schemas = resolution.entries.map((entry) => {\n const schema = hitSchemas.get(entry.key) ?? describedByKey.get(entry.key);\n if (schema !== undefined) return schema;\n logger.warn(\n \"no schema resolved for metric key %s — emitting degraded types (should not happen)\",\n entry.key,\n );\n return emptyMetricSchema(entry);\n });\n\n // Same anti-clobber rule as the query path: when suppressDegradedWrite is set\n // (blocking mode), skip the write if any metric degraded, preserving the\n // committed metric-views.ts. Non-blocking mode always writes.\n const shouldWriteMetrics =\n !suppressDegradedWrite || !hasAnyDegradedMetrics(schemas);\n\n if (shouldWriteMetrics) {\n await fs.mkdir(path.dirname(metricOutFile), { recursive: true });\n await fs.writeFile(\n metricOutFile,\n generateMetricTypeDeclarations(schemas),\n \"utf-8\",\n );\n }\n // Sweep the ambient `metric-views.d.ts` a pre-`.ts` version left behind,\n // which would otherwise duplicate the augmentation the new `.ts` emits.\n // Skipped unless the replacement was actually written, so a degraded\n // blocking pass leaves an app's only committed metric types in place.\n if (\n metricOutFile.endsWith(\".ts\") &&\n !metricOutFile.endsWith(\".d.ts\") &&\n existsSync(metricOutFile)\n ) {\n const staleDts = `${metricOutFile.slice(0, -\".ts\".length)}.d.ts`;\n try {\n await fs.unlink(staleDts);\n logger.debug(\"Removed stale generated types at %s\", staleDts);\n } catch {\n // No stale sibling — nothing to clean up.\n }\n }\n\n logger.debug(\n \"Wrote MetricRegistry augmentation for %d metric(s)%s\",\n schemas.length,\n failures.length > 0 ? ` (${failures.length} failure(s))` : \"\",\n );\n\n return {\n metricOutFile,\n schemas,\n failures,\n fatalErrors,\n noConfig: false,\n hadEnvironmentalFailure:\n mode === \"blocking\" ? hadEnvironmentalFailure : undefined,\n environmentalCause:\n mode === \"blocking\" && hadEnvironmentalFailure\n ? environmentalCause\n : undefined,\n };\n}\n\n// Rolldown tree-shaking only preserves \"own exports\" (locally defined) — not re-exports.\n// A local binding ensures the serving vite plugin's import keeps this in the dependency graph,\n// mirroring how generateFromEntryPoint (also defined here) is preserved via the analytics vite plugin.\nexport const generateServingTypes = generateServingTypesImpl;\n\n// Re-export the mv-registry types so consumers (CLI, the type-generator\n// .d.ts shim in `packages/shared`) can pick them up from this entry point —\n// the .d.ts shim documents these as part of the package's public surface.\nexport type {\n MetricColumnMetadata,\n MetricLane,\n MetricSchema,\n MetricSyncFailure,\n MetricSyncResult,\n};\n\nexport const TYPES_DIR = \"appkit-types\";\nexport const ANALYTICS_TYPES_FILE = \"analytics.d.ts\";\nexport const SERVING_TYPES_FILE = \"serving.d.ts\";\nexport const METRIC_TYPES_FILE = \"metric-views.ts\";\n"],"mappings":"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"}
@@ -75,25 +75,54 @@ function renderDegradedMetricEntry(schema) {
75
75
  };
76
76
  }`;
77
77
  }
78
+ function metadataFields(col, includeTimeGrain = false) {
79
+ const fields = [["type", JSON.stringify(col.type)]];
80
+ const optionalFields = [
81
+ ["display_name", col.displayName],
82
+ ["format", col.format],
83
+ ["description", col.description]
84
+ ];
85
+ for (const [name, value] of optionalFields) if (value) fields.push([name, JSON.stringify(value)]);
86
+ if (includeTimeGrain && col.timeGrains && col.timeGrains.length > 0) {
87
+ const grainTuple = col.timeGrains.map((g) => JSON.stringify(g)).join(", ");
88
+ fields.push(["time_grain", `readonly [${grainTuple}]`]);
89
+ }
90
+ return fields;
91
+ }
78
92
  function renderMetadataMap(cols, indent, includeTimeGrain = false) {
79
93
  if (cols.length === 0) return "Record<string, never>";
80
94
  return `{
81
95
  ${cols.map((col) => {
82
- const fields = [`type: ${JSON.stringify(col.type)}`];
83
- if (col.displayName) fields.push(`display_name: ${JSON.stringify(col.displayName)}`);
84
- if (col.format) fields.push(`format: ${JSON.stringify(col.format)}`);
85
- if (col.description) fields.push(`description: ${JSON.stringify(col.description)}`);
86
- if (includeTimeGrain && col.timeGrains && col.timeGrains.length > 0) {
87
- const grainTuple = col.timeGrains.map((g) => JSON.stringify(g)).join(", ");
88
- fields.push(`time_grain: readonly [${grainTuple}]`);
89
- }
90
- const fieldsBlock = fields.map((f) => `${indent} ${f}`).join(";\n");
96
+ const fieldsBlock = metadataFields(col, includeTimeGrain).map(([name, value]) => `${indent} ${name}: ${value}`).join(";\n");
91
97
  return `${indent}${JSON.stringify(col.name)}: {
92
98
  ${fieldsBlock};
93
99
  ${indent}}`;
94
100
  }).join(";\n")};
95
101
  }`;
96
102
  }
103
+ function renderMetadataValueField(col) {
104
+ return `{ ${metadataFields(col).map(([name, value]) => `${name}: ${value}`).join(", ")} }`;
105
+ }
106
+ function renderMetadataValueMap(cols, indent) {
107
+ if (cols.length === 0) return "{}";
108
+ return `{
109
+ ${cols.map((col) => `${indent} ${JSON.stringify(col.name)}: ${renderMetadataValueField(col)}`).join(",\n")},
110
+ ${indent}}`;
111
+ }
112
+ function renderMetricViewsMetadata(schemas) {
113
+ if (schemas.length === 0) return "export const metricViewsMetadata = {} as const;\n";
114
+ return `export const metricViewsMetadata = {
115
+ ${schemas.map((schema) => {
116
+ const measures = renderMetadataValueMap(schema.measures, " ");
117
+ const dimensions = renderMetadataValueMap(schema.dimensions, " ");
118
+ return ` ${JSON.stringify(schema.key)}: {
119
+ measures: ${measures},
120
+ dimensions: ${dimensions},
121
+ }`;
122
+ }).join(",\n")},
123
+ } as const;
124
+ `;
125
+ }
97
126
  function renderMetricRegistry(schemas) {
98
127
  if (schemas.length === 0) return `declare module "@databricks/appkit-ui/react" {
99
128
  interface MetricRegistry {}
@@ -106,11 +135,20 @@ ${schemas.map(renderMetricEntry).join(";\n")};
106
135
  }
107
136
  `;
108
137
  }
138
+ /**
139
+ * Build the full metric-views.ts file from a list of metric schemas.
140
+ *
141
+ * The header must stay a type-only `import type {} from`: it anchors the module
142
+ * so the augmentation resolves while compiling to zero runtime code, whereas a
143
+ * bare `import "@databricks/appkit-ui/react"` would execute the client package
144
+ * entry on the Node server.
145
+ */
109
146
  function generateMetricTypeDeclarations(schemas) {
110
147
  return `// Auto-generated by AppKit - DO NOT EDIT
111
148
  // Generated by 'npx @databricks/appkit generate-types' or Vite plugin during build
112
- import "@databricks/appkit-ui/react";
113
- ${renderMetricRegistry(schemas)}`;
149
+ import type {} from "@databricks/appkit-ui/react";
150
+ ${renderMetricRegistry(schemas)}
151
+ ${renderMetricViewsMetadata(schemas)}`;
114
152
  }
115
153
 
116
154
  //#endregion