@databricks/appkit 0.42.0 → 0.43.1

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 (43) hide show
  1. package/dist/agents/databricks.d.ts.map +1 -1
  2. package/dist/agents/databricks.js +3 -1
  3. package/dist/agents/databricks.js.map +1 -1
  4. package/dist/appkit/package.js +1 -1
  5. package/dist/cache/index.d.ts.map +1 -1
  6. package/dist/cache/index.js +2 -1
  7. package/dist/cache/index.js.map +1 -1
  8. package/dist/cli/commands/generate-types.js +5 -0
  9. package/dist/cli/commands/generate-types.js.map +1 -1
  10. package/dist/connectors/files/client.js +4 -1
  11. package/dist/connectors/files/client.js.map +1 -1
  12. package/dist/connectors/mcp/client.d.ts.map +1 -1
  13. package/dist/connectors/mcp/client.js +3 -0
  14. package/dist/connectors/mcp/client.js.map +1 -1
  15. package/dist/context/client-options.js +28 -0
  16. package/dist/context/client-options.js.map +1 -0
  17. package/dist/context/service-context.d.ts.map +1 -1
  18. package/dist/context/service-context.js +1 -10
  19. package/dist/context/service-context.js.map +1 -1
  20. package/dist/index.js +0 -1
  21. package/dist/plugins/analytics/query.js +0 -1
  22. package/dist/plugins/analytics/query.js.map +1 -1
  23. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  24. package/dist/plugins/lakebase/lakebase.js +6 -1
  25. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  26. package/dist/type-generator/cache.js +1 -6
  27. package/dist/type-generator/cache.js.map +1 -1
  28. package/dist/type-generator/index.js +162 -146
  29. package/dist/type-generator/index.js.map +1 -1
  30. package/dist/type-generator/mv-registry/config.js +5 -5
  31. package/dist/type-generator/mv-registry/config.js.map +1 -1
  32. package/dist/type-generator/mv-registry/render-types.js.map +1 -1
  33. package/dist/type-generator/vite-plugin.d.ts +0 -7
  34. package/dist/type-generator/vite-plugin.d.ts.map +1 -1
  35. package/dist/type-generator/vite-plugin.js +1 -4
  36. package/dist/type-generator/vite-plugin.js.map +1 -1
  37. package/docs/development/type-generation.md +28 -2
  38. package/package.json +2 -1
  39. package/sbom.cdx.json +1 -1
  40. package/dist/shared/src/index.js +0 -4
  41. package/dist/shared/src/sql/index.js +0 -3
  42. package/dist/type-generator/mv-registry/metadata.js +0 -40
  43. package/dist/type-generator/mv-registry/metadata.js.map +0 -1
@@ -4,7 +4,6 @@ import { getErrorDiagnostic, isConnectivityError } from "./errors.js";
4
4
  import { migrateProjectConfig, removeOldGeneratedTypes, resolveProjectRoot } from "./migration.js";
5
5
  import { readMetricConfig, resolveMetricConfig } from "./mv-registry/config.js";
6
6
  import { createWorkspaceDescribeFetcher } from "./mv-registry/describe.js";
7
- import { generateMetricsMetadataJson } from "./mv-registry/metadata.js";
8
7
  import { generateMetricTypeDeclarations } from "./mv-registry/render-types.js";
9
8
  import { emptyMetricSchema, syncMetrics } from "./mv-registry/sync.js";
10
9
  import { decidePreflight } from "./preflight.js";
@@ -21,7 +20,7 @@ import dotenv from "dotenv";
21
20
  dotenv.config();
22
21
  const logger = createLogger("type-generator");
23
22
  /**
24
- * Upper bound (~5 min) on how long the metric path's `blocking`-mode preflight
23
+ * Upper bound (~5 min) on how long the Metric Views path's `blocking`-mode preflight
25
24
  * waits for a warehouse to reach RUNNING. Mirrors the query path's (unexported)
26
25
  * `PREFLIGHT_WAIT_MAX_MS` in query-registry.ts.
27
26
  */
@@ -147,20 +146,15 @@ declare module "@databricks/appkit-ui/react" {
147
146
  `;
148
147
  }
149
148
  /**
150
- * Status-only probe for the metric-view gate in {@link generateFromEntryPoint}:
151
- * what state is the warehouse in right now?
149
+ * Status-only probe for the metric-view gate in {@link generateFromEntryPoint}
152
150
  *
153
- * Uses {@link getWarehouseState} (`warehouses.get`) — a read-only GET that can
154
- * never start the warehouse — unlike the metric DESCRIBE statements it guards,
155
- * whose execution auto-starts a stopped warehouse and waits on it.
151
+ * Uses {@link getWarehouseState} (`warehouses.get`) —
152
+ * a read-only GET that can never start the warehouse
156
153
  *
157
- * Returns the observed state so the gate can distinguish a transient
158
- * not-running state (STOPPED/STARTING/... → degraded entries that retry) from a
159
- * terminal one (DELETED/DELETING → degraded entries pinned sticky). Takes the
160
- * lazy client *getter* (not a client) so the probe also absorbs client
161
- * construction failure. A connectivity blip returns `undefined`, which the gate
162
- * reads as transient not-running; a deterministic failure (auth, bad id) is
163
- * re-thrown so the gate can classify it fatal rather than silently degrading.
154
+ * Takes the lazy client *getter* so the probe also absorbs client construction failure.
155
+ * A connectivity blip returns `undefined`, which the gate reads as transient not-running;
156
+ * a deterministic failure (auth, bad id) is re-thrown so the gate can classify it
157
+ * fatal rather than silently degrading.
164
158
  */
165
159
  async function probeWarehouseState(getClient, warehouseId) {
166
160
  try {
@@ -186,12 +180,8 @@ async function probeWarehouseState(getClient, warehouseId) {
186
180
  * degraded types immediately. `"blocking"` waits for / starts the warehouse
187
181
  * first, failing the build only for a deleted/deleting one.
188
182
  * @param options.mvOutFile - optional output file for the MetricRegistry
189
- * augmentation. Defaults to a sibling `metric.d.ts` file under the same
183
+ * augmentation. Defaults to a sibling `metric-views.d.ts` file under the same
190
184
  * directory as `outFile`. Skipped entirely if `metric-views.json` is absent.
191
- * @param options.mvMetadataOutFile - optional output file for the
192
- * build-time semantic metadata JSON bundle (`metrics.metadata.json`).
193
- * Defaults to a sibling of `mvOutFile`. Skipped entirely if
194
- * `metric-views.json` is absent.
195
185
  * @param options.metricFetcher - optional DescribeFetcher used by
196
186
  * {@link syncMetrics} (tests inject a mock; production lazily builds a
197
187
  * default WorkspaceClient-backed one). An injected fetcher always runs: it
@@ -199,7 +189,7 @@ async function probeWarehouseState(getClient, warehouseId) {
199
189
  * blocking preflight.
200
190
  */
201
191
  async function generateFromEntryPoint(options) {
202
- const { outFile, queryFolder, warehouseId, noCache, mode = "non-blocking", mvOutFile, mvMetadataOutFile, metricFetcher } = options;
192
+ const { outFile, queryFolder, warehouseId, noCache, mode = "non-blocking", mvOutFile, metricFetcher } = options;
203
193
  const projectRoot = resolveProjectRoot(outFile);
204
194
  logger.debug("Starting type generation...");
205
195
  let queryRegistry = [];
@@ -218,119 +208,28 @@ async function generateFromEntryPoint(options) {
218
208
  await fs.mkdir(path.dirname(outFile), { recursive: true });
219
209
  await fs.writeFile(outFile, typeDeclarations, "utf-8");
220
210
  if (queryFolder) {
221
- const mvConfig = await readMetricConfig(queryFolder);
222
- if (mvConfig) {
223
- const resolution = resolveMetricConfig(mvConfig);
224
- const cache = await loadCache();
225
- const mvCacheSection = Object.create(null);
226
- if (!noCache && cache.metrics) for (const key of Object.keys(cache.metrics)) mvCacheSection[key] = cache.metrics[key];
227
- const hitSchemas = /* @__PURE__ */ new Map();
228
- const describeNeeded = [];
229
- const stickyDegradedHits = [];
230
- for (const entry of resolution.entries) {
231
- const prior = mvCacheSection[entry.key];
232
- if (prior !== void 0 && isRevivableMetricCacheEntry(prior) && prior.hash === metricCacheHash(entry.source, entry.lane) && !prior.retry) {
233
- hitSchemas.set(entry.key, prior.schema);
234
- if (prior.schema.degraded === true) stickyDegradedHits.push(entry.key);
235
- } else describeNeeded.push(entry);
236
- }
237
- if (stickyDegradedHits.length > 0) logger.warn("cached failure for %s — fix the entry in metric-views.json or run with --no-cache to retry.", stickyDegradedHits.join(", "));
238
- let mvClient;
239
- const getMvClient = () => {
240
- mvClient ??= new WorkspaceClient({});
241
- return mvClient;
242
- };
243
- let preflightFatalMessage;
244
- if (mode === "blocking" && metricFetcher === void 0 && describeNeeded.length > 0) try {
245
- const state = await getWarehouseState(getMvClient(), warehouseId);
246
- const decision = decidePreflight(state, mode);
247
- if (decision === "fatal") preflightFatalMessage = `warehouse ${warehouseId} is ${state}`;
248
- else if (decision === "startWaitProceed") {
249
- await startWarehouse(getMvClient(), warehouseId);
250
- const settled = await waitUntilRunning(getMvClient(), warehouseId, {
251
- maxMs: MV_PREFLIGHT_WAIT_MAX_MS,
252
- treatStoppedAsTransient: true
253
- });
254
- if (settled !== "RUNNING") preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;
255
- } else if (decision === "waitThenProceed") {
256
- const settled = await waitUntilRunning(getMvClient(), warehouseId, { maxMs: MV_PREFLIGHT_WAIT_MAX_MS });
257
- if (settled === "DELETED" || settled === "DELETING") preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;
258
- }
259
- } catch (err) {
260
- if (!isConnectivityError(err)) preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;
261
- }
262
- let gateState;
263
- let describeNow = metricFetcher !== void 0 || mode !== "non-blocking" || describeNeeded.length === 0;
264
- if (!describeNow) {
265
- try {
266
- gateState = await probeWarehouseState(getMvClient, warehouseId);
267
- } catch (err) {
268
- preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;
269
- }
270
- describeNow = gateState === "RUNNING";
271
- }
272
- let described;
273
- let failures = [];
274
- let terminalSkip = false;
275
- if (preflightFatalMessage !== void 0) {
276
- described = describeNeeded.map(emptyMetricSchema);
277
- terminalSkip = true;
278
- for (const entry of describeNeeded) fatalErrors.push({
279
- name: entry.key,
280
- message: preflightFatalMessage
281
- });
282
- } else if (describeNeeded.length === 0) described = [];
283
- else if (describeNow) {
284
- const fetcher = metricFetcher ?? createWorkspaceDescribeFetcher(getMvClient(), warehouseId);
285
- ({schemas: described, failures} = await syncMetrics({ entries: describeNeeded }, fetcher));
286
- if (failures.length > 0) for (const f of failures) logger.warn("metric sync failed for %s (%s): %s", f.key, f.source, f.reason);
287
- const failedKeys = new Set(failures.map((f) => f.key));
288
- const degradedKeys = described.filter((s) => s.degraded && !failedKeys.has(s.key)).map((s) => s.key);
289
- if (degradedKeys.length > 0) logger.info("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.", warehouseId, degradedKeys.length, degradedKeys.join(", "));
290
- } else {
291
- described = describeNeeded.map(emptyMetricSchema);
292
- terminalSkip = gateState === "DELETED" || gateState === "DELETING";
293
- logger.info("Warehouse %s is not running — wrote degraded metric types (permissive) for %d metric view(s) (%s); they will refresh once the warehouse is available.", warehouseId, describeNeeded.length, describeNeeded.map((e) => e.key).join(", "));
294
- }
295
- const failureByKey = /* @__PURE__ */ new Map();
296
- for (const failure of failures) failureByKey.set(failure.key, failure);
297
- for (let i = 0; i < describeNeeded.length; i++) {
298
- const entry = describeNeeded[i];
299
- const failure = failureByKey.get(entry.key);
300
- mvCacheSection[entry.key] = {
301
- hash: metricCacheHash(entry.source, entry.lane),
302
- schema: described[i],
303
- retry: described[i].degraded === true && !terminalSkip && (failure === void 0 || failure.transient === true)
304
- };
305
- }
306
- const configuredKeys = new Set(resolution.entries.map((e) => e.key));
307
- let prunedCount = 0;
308
- for (const key of Object.keys(mvCacheSection)) if (!configuredKeys.has(key)) {
309
- delete mvCacheSection[key];
310
- prunedCount++;
311
- }
312
- if (describeNeeded.length > 0 || noCache || prunedCount > 0) {
313
- cache.metrics = mvCacheSection;
314
- await saveCache(cache);
315
- }
316
- const describedByKey = /* @__PURE__ */ new Map();
317
- for (const schema of described) describedByKey.set(schema.key, schema);
318
- const mvSchemas = resolution.entries.map((entry) => {
319
- const schema = hitSchemas.get(entry.key) ?? describedByKey.get(entry.key);
320
- if (schema !== void 0) return schema;
321
- logger.warn("no schema resolved for metric key %s — emitting degraded types (should not happen)", entry.key);
322
- return emptyMetricSchema(entry);
211
+ const mvFile = mvOutFile ?? path.join(path.dirname(outFile), METRIC_TYPES_FILE);
212
+ let mvResult;
213
+ try {
214
+ mvResult = await syncMetricViewsTypes({
215
+ queryFolder,
216
+ warehouseId,
217
+ metricOutFile: mvFile,
218
+ cache: !noCache,
219
+ metricFetcher,
220
+ mode
323
221
  });
324
- const mvFile = mvOutFile ?? path.join(path.dirname(outFile), METRIC_TYPES_FILE);
325
- const mvDeclarations = generateMetricTypeDeclarations(mvSchemas);
326
- await fs.mkdir(path.dirname(mvFile), { recursive: true });
327
- await fs.writeFile(mvFile, mvDeclarations, "utf-8");
328
- const mvMetadataFile = mvMetadataOutFile ?? path.join(path.dirname(mvFile), METRIC_METADATA_FILE);
329
- const metadataJson = generateMetricsMetadataJson(mvSchemas);
330
- await fs.mkdir(path.dirname(mvMetadataFile), { recursive: true });
331
- await fs.writeFile(mvMetadataFile, metadataJson, "utf-8");
332
- logger.debug("Wrote MetricRegistry augmentation + metadata bundle for %d metric(s)%s", mvSchemas.length, failures.length > 0 ? ` (${failures.length} failure(s))` : "");
222
+ } catch (configError) {
223
+ throw new TypegenFatalError([{
224
+ name: "metric-views.json",
225
+ message: getErrorDiagnostic(configError)
226
+ }], warehouseId);
333
227
  }
228
+ for (const fe of mvResult.fatalErrors) fatalErrors.push(fe);
229
+ if (mode === "blocking") for (const failure of mvResult.failures) fatalErrors.push({
230
+ name: failure.key,
231
+ message: `metric view ${failure.key} (${failure.source}) could not be described: ${failure.reason}`
232
+ });
334
233
  }
335
234
  await removeOldGeneratedTypes(projectRoot, "appKitTypes.d.ts");
336
235
  await migrateProjectConfig(projectRoot);
@@ -338,25 +237,142 @@ async function generateFromEntryPoint(options) {
338
237
  if (fatalErrors.length > 0) throw new TypegenFatalError(fatalErrors, warehouseId);
339
238
  logger.debug("Type generation complete!");
340
239
  }
240
+ /**
241
+ * Unified metric-view type-generation pipeline behind {@link
242
+ * generateFromEntryPoint}'s metric section (which forwards its
243
+ * `"non-blocking"`/`"blocking"` mode). Also directly callable with the default
244
+ * `"describe-now"` mode for a focused, always-converge metric refresh.
245
+ *
246
+ *
247
+ * @param options.queryFolder - folder that holds `metric-views.json` (`<root>/config/queries`).
248
+ * @param options.warehouseId - SQL warehouse used for `DESCRIBE TABLE EXTENDED`.
249
+ * @param options.metricOutFile - output path for the MetricRegistry `.d.ts`.
250
+ * @param options.cache - cache toggle, default ON. Only `cache === false` disables it (so `undefined`/`true` keep caching).
251
+ * @param options.metricFetcher - optional injected {@link DescribeFetcher}
252
+ * @param options.mode - preflight/gate policy, default `"describe-now"`.
253
+ */
254
+ async function syncMetricViewsTypes(options) {
255
+ const { queryFolder, warehouseId, metricOutFile, cache: cacheEnabled, metricFetcher, mode = "describe-now" } = options;
256
+ const noCache = cacheEnabled === false;
257
+ const mvConfig = await readMetricConfig(queryFolder);
258
+ if (!mvConfig) return {
259
+ schemas: [],
260
+ failures: [],
261
+ fatalErrors: [],
262
+ noConfig: true
263
+ };
264
+ const resolution = resolveMetricConfig(mvConfig);
265
+ const fatalErrors = [];
266
+ const cache = await loadCache();
267
+ const mvCacheSection = Object.create(null);
268
+ if (!noCache && cache.metrics) for (const key of Object.keys(cache.metrics)) mvCacheSection[key] = cache.metrics[key];
269
+ const hitSchemas = /* @__PURE__ */ new Map();
270
+ const describeNeeded = [];
271
+ for (const entry of resolution.entries) {
272
+ const prior = mvCacheSection[entry.key];
273
+ if (prior !== void 0 && isRevivableMetricCacheEntry(prior) && prior.hash === metricCacheHash(entry.source, entry.lane) && prior.schema.degraded !== true) hitSchemas.set(entry.key, prior.schema);
274
+ else describeNeeded.push(entry);
275
+ }
276
+ let mvClient;
277
+ const getMvClient = () => {
278
+ mvClient ??= new WorkspaceClient({});
279
+ return mvClient;
280
+ };
281
+ let preflightFatalMessage;
282
+ if (mode === "blocking" && metricFetcher === void 0 && describeNeeded.length > 0) try {
283
+ const state = await getWarehouseState(getMvClient(), warehouseId);
284
+ const decision = decidePreflight(state, mode);
285
+ if (decision === "fatal") preflightFatalMessage = `warehouse ${warehouseId} is ${state}`;
286
+ else if (decision === "startWaitProceed") {
287
+ await startWarehouse(getMvClient(), warehouseId);
288
+ const settled = await waitUntilRunning(getMvClient(), warehouseId, {
289
+ maxMs: MV_PREFLIGHT_WAIT_MAX_MS,
290
+ treatStoppedAsTransient: true
291
+ });
292
+ if (settled !== "RUNNING") preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;
293
+ } else if (decision === "waitThenProceed") {
294
+ const settled = await waitUntilRunning(getMvClient(), warehouseId, { maxMs: MV_PREFLIGHT_WAIT_MAX_MS });
295
+ if (settled === "DELETED" || settled === "DELETING") preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;
296
+ }
297
+ } catch (err) {
298
+ if (!isConnectivityError(err)) preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;
299
+ }
300
+ let gateState;
301
+ let describeNow = metricFetcher !== void 0 || mode !== "non-blocking" || describeNeeded.length === 0;
302
+ if (!describeNow) {
303
+ try {
304
+ gateState = await probeWarehouseState(getMvClient, warehouseId);
305
+ } catch (err) {
306
+ preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;
307
+ }
308
+ describeNow = gateState === "RUNNING";
309
+ }
310
+ let described;
311
+ let failures = [];
312
+ if (preflightFatalMessage !== void 0) {
313
+ described = describeNeeded.map(emptyMetricSchema);
314
+ for (const entry of describeNeeded) fatalErrors.push({
315
+ name: entry.key,
316
+ message: preflightFatalMessage
317
+ });
318
+ } else if (describeNeeded.length === 0) described = [];
319
+ else if (describeNow) {
320
+ const fetcher = metricFetcher ?? createWorkspaceDescribeFetcher(getMvClient(), warehouseId);
321
+ ({schemas: described, failures} = await syncMetrics({ entries: describeNeeded }, fetcher));
322
+ if (failures.length > 0) for (const f of failures) logger.warn("metric sync failed for %s (%s): %s", f.key, f.source, f.reason);
323
+ const failedKeys = new Set(failures.map((f) => f.key));
324
+ const degradedKeys = described.filter((s) => s.degraded && !failedKeys.has(s.key)).map((s) => s.key);
325
+ if (degradedKeys.length > 0) logger.info("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.", warehouseId, degradedKeys.length, degradedKeys.join(", "));
326
+ } else {
327
+ described = describeNeeded.map(emptyMetricSchema);
328
+ logger.info("Warehouse %s is not running — wrote degraded metric types (permissive) for %d metric view(s) (%s); they will refresh once the warehouse is available.", warehouseId, describeNeeded.length, describeNeeded.map((e) => e.key).join(", "));
329
+ }
330
+ for (let i = 0; i < describeNeeded.length; i++) {
331
+ const entry = describeNeeded[i];
332
+ if (described[i].degraded === true) {
333
+ delete mvCacheSection[entry.key];
334
+ continue;
335
+ }
336
+ mvCacheSection[entry.key] = {
337
+ hash: metricCacheHash(entry.source, entry.lane),
338
+ schema: described[i],
339
+ retry: false
340
+ };
341
+ }
342
+ const configuredKeys = new Set(resolution.entries.map((e) => e.key));
343
+ let prunedCount = 0;
344
+ for (const key of Object.keys(mvCacheSection)) if (!configuredKeys.has(key)) {
345
+ delete mvCacheSection[key];
346
+ prunedCount++;
347
+ }
348
+ if (describeNeeded.length > 0 || noCache || prunedCount > 0) {
349
+ cache.metrics = mvCacheSection;
350
+ await saveCache(cache);
351
+ }
352
+ const describedByKey = /* @__PURE__ */ new Map();
353
+ for (const schema of described) describedByKey.set(schema.key, schema);
354
+ const schemas = resolution.entries.map((entry) => {
355
+ const schema = hitSchemas.get(entry.key) ?? describedByKey.get(entry.key);
356
+ if (schema !== void 0) return schema;
357
+ logger.warn("no schema resolved for metric key %s — emitting degraded types (should not happen)", entry.key);
358
+ return emptyMetricSchema(entry);
359
+ });
360
+ await fs.mkdir(path.dirname(metricOutFile), { recursive: true });
361
+ await fs.writeFile(metricOutFile, generateMetricTypeDeclarations(schemas), "utf-8");
362
+ logger.debug("Wrote MetricRegistry augmentation for %d metric(s)%s", schemas.length, failures.length > 0 ? ` (${failures.length} failure(s))` : "");
363
+ return {
364
+ metricOutFile,
365
+ schemas,
366
+ failures,
367
+ fatalErrors,
368
+ noConfig: false
369
+ };
370
+ }
341
371
  const generateServingTypes = generateServingTypes$1;
342
- /** Directory name for generated AppKit type declaration files. */
343
372
  const TYPES_DIR = "appkit-types";
344
- /** Default filename for analytics query type declarations. */
345
373
  const ANALYTICS_TYPES_FILE = "analytics.d.ts";
346
- /** Default filename for serving endpoint type declarations. */
347
374
  const SERVING_TYPES_FILE = "serving.d.ts";
348
- /** Default filename for metric-view registry type declarations. */
349
- const METRIC_TYPES_FILE = "metric.d.ts";
350
- /**
351
- * Default filename for the build-time semantic-metadata JSON bundle, sibling of
352
- * {@link METRIC_TYPES_FILE}. Shape is `Record<metricKey, { measures,
353
- * dimensions }>` (UC FQN and execution lane are server-side concerns, kept out
354
- * of this client-shipped artifact). The consuming app imports it at build time
355
- * and registers it via `@databricks/appkit-ui/format`'s
356
- * `registerMetricsMetadata()`, so the React hook returns per-metric `metadata`
357
- * without a second network round-trip.
358
- */
359
- const METRIC_METADATA_FILE = "metrics.metadata.json";
375
+ const METRIC_TYPES_FILE = "metric-views.d.ts";
360
376
 
361
377
  //#endregion
362
378
  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 fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport dotenv from \"dotenv\";\nimport pc from \"picocolors\";\nimport { createLogger } from \"../logging/logger\";\nimport {\n isRevivableMetricCacheEntry,\n loadCache,\n type MetricCacheEntry,\n metricCacheHash,\n saveCache,\n} from \"./cache\";\nimport { getErrorDiagnostic, isConnectivityError } 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 { generateMetricsMetadataJson } from \"./mv-registry/metadata\";\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 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\ntype TypegenFailure = QuerySyntaxError | QueryFatalError;\n\nfunction plural(count: number, singular: string, pluralForm = `${singular}s`) {\n return count === 1 ? singular : pluralForm;\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 // Unique message → keep the compact one-line `tag name message` form.\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 * what state is the warehouse in right now?\n *\n * Uses {@link getWarehouseState} (`warehouses.get`) — a read-only GET that can\n * never start the warehouse — unlike the metric DESCRIBE statements it guards,\n * whose execution auto-starts a stopped warehouse and waits on it.\n *\n * Returns the observed state so the gate can distinguish a transient\n * not-running state (STOPPED/STARTING/... → degraded entries that retry) from a\n * terminal one (DELETED/DELETING → degraded entries pinned sticky). Takes the\n * lazy client *getter* (not a client) so the probe also absorbs client\n * construction failure. A connectivity blip returns `undefined`, which the gate\n * reads as transient not-running; a deterministic failure (auth, bad id) is\n * re-thrown so the gate can classify it 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.mvOutFile - optional output file for the MetricRegistry\n * augmentation. Defaults to a sibling `metric.d.ts` file under the same\n * directory as `outFile`. Skipped entirely if `metric-views.json` is absent.\n * @param options.mvMetadataOutFile - optional output file for the\n * build-time semantic metadata JSON bundle (`metrics.metadata.json`).\n * Defaults to a sibling of `mvOutFile`. Skipped entirely if\n * `metric-views.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 warehouseId: string;\n noCache?: boolean;\n mode?: PreflightMode;\n mvOutFile?: string;\n mvMetadataOutFile?: string;\n metricFetcher?: DescribeFetcher;\n}) {\n const {\n outFile,\n queryFolder,\n warehouseId,\n noCache,\n mode = \"non-blocking\",\n mvOutFile,\n mvMetadataOutFile,\n metricFetcher,\n } = options;\n const projectRoot = resolveProjectRoot(outFile);\n\n logger.debug(\"Starting type generation...\");\n\n let queryRegistry: QuerySchema[] = [];\n let syntaxErrors: QuerySyntaxError[] = [];\n let fatalErrors: QueryFatalError[] = [];\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 }\n\n const typeDeclarations = generateTypeDeclarations(queryRegistry);\n\n await fs.mkdir(path.dirname(outFile), { recursive: true });\n await fs.writeFile(outFile, typeDeclarations, \"utf-8\");\n\n // Metric-view types: only emit when metric-views.json exists. The path is\n // purely additive — apps that never adopt metric views must not produce\n // empty noise.\n if (queryFolder) {\n const mvConfig = await readMetricConfig(queryFolder);\n if (mvConfig) {\n const resolution = resolveMetricConfig(mvConfig);\n\n // Metric schemas persist in the shared typegen cache as a `metrics`\n // section (sibling of `queries`, same file/version), keyed by metric key\n // with md5(\"<source>|<lane>\") as the change detector. Loaded strictly\n // AFTER the query path's own load → mutate → save cycle, so the single\n // metric-side save below can never clobber a query entry.\n const cache = await loadCache();\n\n // The section is consumed through a null-prototype copy: metric keys\n // are user-controlled config input and \"__proto__\" passes the metric\n // key regex — on a plain object, writing it would hit the\n // Object.prototype setter (mutating the object's prototype and silently\n // dropping the entry) instead of storing data. A null prototype also\n // keeps partition reads from resolving inherited names (\"constructor\",\n // \"toString\", ...) as phantom entries.\n const mvCacheSection: Record<string, MetricCacheEntry> =\n 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 (structurally valid\n // entry, hash match, not retry-flagged) is served from cache no matter\n // what the warehouse is doing — a degraded pass falls back to\n // last-known-good schemas, exactly like queries degrade to cached types.\n // Only the remainder (new, edited, retry-flagged, or unrevivable entries)\n // is eligible for DESCRIBE, so a fully-warm pass makes zero warehouse\n // calls and constructs zero clients.\n const hitSchemas = new Map<string, MetricSchema>();\n const describeNeeded: typeof resolution.entries = [];\n // Degraded cached schemas pinned `retry: false` are sticky failures: they\n // serve their permissive schema like any hit, but are collected here for\n // the single notice below so the misconfiguration isn't silently hidden.\n const stickyDegradedHits: string[] = [];\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.retry\n ) {\n hitSchemas.set(entry.key, prior.schema);\n if (prior.schema.degraded === true) {\n stickyDegradedHits.push(entry.key);\n }\n } else {\n describeNeeded.push(entry);\n }\n }\n\n if (stickyDegradedHits.length > 0) {\n logger.warn(\n \"cached failure for %s — fix the entry in metric-views.json or run with --no-cache to retry.\",\n stickyDegradedHits.join(\", \"),\n );\n }\n\n // At most ONE WorkspaceClient per pass for the whole metric path: the\n // status probe, the blocking preflight, and the default DESCRIBE fetcher\n // share this lazily-created instance, so a pass that never contacts the\n // warehouse constructs zero clients.\n let mvClient: WorkspaceClient | undefined;\n const getMvClient = (): WorkspaceClient => {\n mvClient ??= new WorkspaceClient({});\n return mvClient;\n };\n\n // Blocking-mode preflight: ensure the warehouse is running before the\n // DESCRIBE batch (probe → decide → wait / start+wait; only\n // DELETED/DELETING is fatal). Deliberately split from the query path's\n // preflight — metric views may bind a different warehouse in future. Two\n // softenings vs the query preflight: a failed probe and a timed-out wait\n // are NOT fatal here — we fall through to syncMetrics, which classifies a\n // still-not-ready warehouse as degraded rather than failing the build.\n let preflightFatalMessage: string | 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 } else if (decision === \"startWaitProceed\") {\n // treatStoppedAsTransient rides out the stale pre-start\n // STOPPED/STOPPING 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\n // exactly DELETED/DELETING — the warehouse was deleted while\n // we waited. Fatal, same as catching it at decision time.\n preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;\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. A STOPPED/STOPPING resolve (this\n // wait runs without treatStoppedAsTransient) stays a soft\n // fall-through — a stopped warehouse is startable, so it\n // degrades and converges rather than failing the build.\n preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;\n }\n }\n } catch (err) {\n // Connectivity blip: fall through to syncMetrics, whose DESCRIBEs\n // degrade a not-ready / unreachable warehouse rather than throwing. A\n // deterministic failure (auth, bad warehouse id, a timed-out start)\n // is fatal — surface it instead of stalling ~5 min against a\n // not-ready warehouse, mirroring the query path's preflight catch.\n if (!isConnectivityError(err)) {\n preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n }\n }\n }\n\n // Honor the non-blocking preflight contract (#406) for metric DESCRIBEs:\n // a `DESCRIBE TABLE EXTENDED ... AS JSON` waits up to 30s per key and\n // auto-starts a stopped warehouse — exactly what \"non-blocking\" promises\n // not to do. So one status-only probe (which can't start the warehouse)\n // decides whether to DESCRIBE now or emit degraded artifacts for a later\n // blocking run; it keeps the observed state so the skip can tell a\n // transient not-running warehouse from a terminal DELETED/DELETING one.\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 // probeWarehouseState only throws on a deterministic failure (auth,\n // bad warehouse id) — a connectivity blip already returned undefined.\n // Pin it fatal through the same path as a fatal blocking preflight.\n preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\n }\n describeNow = gateState === \"RUNNING\";\n }\n\n let described: MetricSchema[];\n let failures: MetricSyncFailure[] = [];\n // True when this pass skipped DESCRIBE for a reason that can never\n // self-converge — a deleted/deleting warehouse (fatal preflight or gate\n // skip). The write site pins those degraded outcomes sticky.\n let terminalSkip = false;\n if (preflightFatalMessage !== undefined) {\n // Fatal preflight (deleted/deleting warehouse): fail like the query\n // path — skip DESCRIBE, emit degraded schemas so both artifacts are\n // still written, and record one fatal error per describe-needed key\n // (cache hits are unaffected). The end-of-run throw below surfaces them\n // after the writes. Terminal, so these entries are pinned sticky.\n described = describeNeeded.map(emptyMetricSchema);\n terminalSkip = true;\n for (const entry of describeNeeded) {\n fatalErrors.push({ name: entry.key, message: preflightFatalMessage });\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\n // construct a 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 metric-views.json\n // would otherwise silently ship an empty entry that the runtime\n // fail-closed gate 503s in production. syncMetrics is log-free; this\n // caller is the single 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 // Degraded-but-not-failed keys: the warehouse answered with a\n // non-terminal state (stopped / cold-starting), so their schemas are\n // unknown — not errors. One summary line, no per-key warns; failed\n // keys are excluded (the warn loop above already reported them).\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 }\n } else {\n // Un-probed DESCRIBEs deliberately skipped, not failures: emit each\n // describe-needed key as a degraded schema (permissive types) so both\n // artifacts exist; cache hits keep serving last-known-good. A transient\n // state refreshes on a later RUNNING pass; a DELETED/DELETING probe is\n // terminal, so those keys are pinned sticky below.\n described = describeNeeded.map(emptyMetricSchema);\n terminalSkip = gateState === \"DELETED\" || gateState === \"DELETING\";\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 }\n\n // Persist outcomes for exactly the keys this pass owned (the\n // describe-needed set); hits were partitioned out above and are never\n // rewritten, so a warehouse-down pass keeps last-known-good entries. A\n // successful DESCRIBE caches `retry: false`; a degraded outcome caches\n // `retry: true` only when re-describing could later succeed (non-terminal\n // state or transient failure), else sticky `retry: false`. One save per\n // pass; with `noCache` the section started empty, so it's overwritten.\n const failureByKey = new Map<string, MetricSyncFailure>();\n for (const failure of failures) {\n failureByKey.set(failure.key, failure);\n }\n for (let i = 0; i < describeNeeded.length; i++) {\n // syncMetrics (and both .map(emptyMetricSchema) branches) return\n // one schema per entry in entry order, so described[i] always\n // belongs to describeNeeded[i].\n const entry = describeNeeded[i];\n const failure = failureByKey.get(entry.key);\n mvCacheSection[entry.key] = {\n hash: metricCacheHash(entry.source, entry.lane),\n schema: described[i],\n retry:\n described[i].degraded === true &&\n !terminalSkip &&\n (failure === undefined || failure.transient === true),\n };\n }\n\n // Prune entries whose key is no longer configured, so a removed metric\n // doesn't haunt the cache file forever.\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 // — a warm pass over a shrunk config has nothing to describe but must\n // still shrink the file.\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 // (resolution.entries order — the renderers sort internally where\n // determinism matters).\n const describedByKey = new Map<string, MetricSchema>();\n for (const schema of described) {\n describedByKey.set(schema.key, schema);\n }\n const mvSchemas = resolution.entries.map((entry) => {\n const schema =\n hitSchemas.get(entry.key) ?? describedByKey.get(entry.key);\n if (schema !== undefined) return schema;\n // Defensive: every entry is either a cache hit or describe-needed (and\n // every describe-needed entry yields exactly one schema above), so this\n // should be unreachable. If the invariant ever breaks, warn loudly but\n // still emit a permissive degraded schema — the metric path never\n // crashes a build over a single entry.\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 const mvFile =\n mvOutFile ?? path.join(path.dirname(outFile), METRIC_TYPES_FILE);\n const mvDeclarations = generateMetricTypeDeclarations(mvSchemas);\n await fs.mkdir(path.dirname(mvFile), { recursive: true });\n await fs.writeFile(mvFile, mvDeclarations, \"utf-8\");\n\n // Emit the semantic-metadata JSON bundle alongside the .d.ts. The hook\n // imports this artifact (via a registration call from the consuming\n // app) and exposes the per-metric subset on its return value.\n const mvMetadataFile =\n mvMetadataOutFile ??\n path.join(path.dirname(mvFile), METRIC_METADATA_FILE);\n const metadataJson = generateMetricsMetadataJson(mvSchemas);\n await fs.mkdir(path.dirname(mvMetadataFile), { recursive: true });\n await fs.writeFile(mvMetadataFile, metadataJson, \"utf-8\");\n\n logger.debug(\n \"Wrote MetricRegistry augmentation + metadata bundle for %d metric(s)%s\",\n mvSchemas.length,\n failures.length > 0 ? ` (${failures.length} failure(s))` : \"\",\n );\n }\n }\n\n // One-time migration: remove old generated file and patch project configs\n await removeOldGeneratedTypes(projectRoot, \"appKitTypes.d.ts\");\n await migrateProjectConfig(projectRoot);\n\n // Types are always written above — including `result: unknown` for any query\n // that could not be described. Connectivity failures pass silently so a\n // transient warehouse outage never blocks a build; genuine SQL errors and\n // non-connectivity fatal request failures surface after the file write.\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 logger.debug(\"Type generation complete!\");\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\n/** Directory name for generated AppKit type declaration files. */\nexport const TYPES_DIR = \"appkit-types\";\n/** Default filename for analytics query type declarations. */\nexport const ANALYTICS_TYPES_FILE = \"analytics.d.ts\";\n/** Default filename for serving endpoint type declarations. */\nexport const SERVING_TYPES_FILE = \"serving.d.ts\";\n/** Default filename for metric-view registry type declarations. */\nexport const METRIC_TYPES_FILE = \"metric.d.ts\";\n/**\n * Default filename for the build-time semantic-metadata JSON bundle, sibling of\n * {@link METRIC_TYPES_FILE}. Shape is `Record<metricKey, { measures,\n * dimensions }>` (UC FQN and execution lane are server-side concerns, kept out\n * of this client-shipped artifact). The consuming app imports it at build time\n * and registers it via `@databricks/appkit-ui/format`'s\n * `registerMetricsMetadata()`, so the React hook returns per-metric `metadata`\n * without a second network round-trip.\n */\nexport const METRIC_METADATA_FILE = \"metrics.metadata.json\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["generateServingTypesImpl"],"sources":["../../src/type-generator/index.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport dotenv from \"dotenv\";\nimport pc from \"picocolors\";\nimport { createLogger } from \"../logging/logger\";\nimport {\n isRevivableMetricCacheEntry,\n loadCache,\n type MetricCacheEntry,\n metricCacheHash,\n saveCache,\n} from \"./cache\";\nimport { getErrorDiagnostic, isConnectivityError } 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\ntype TypegenFailure = QuerySyntaxError | QueryFatalError;\n\nfunction plural(count: number, singular: string, pluralForm = `${singular}s`) {\n return count === 1 ? singular : pluralForm;\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.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 `metric-views.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 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 const projectRoot = resolveProjectRoot(outFile);\n\n logger.debug(\"Starting type generation...\");\n\n let queryRegistry: QuerySchema[] = [];\n let syntaxErrors: QuerySyntaxError[] = [];\n let fatalErrors: QueryFatalError[] = [];\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 }\n\n const typeDeclarations = generateTypeDeclarations(queryRegistry);\n\n await fs.mkdir(path.dirname(outFile), { recursive: true });\n await fs.writeFile(outFile, typeDeclarations, \"utf-8\");\n\n // Metric-view types: only emit when metric-views.json exists.\n if (queryFolder) {\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 queryFolder,\n warehouseId,\n metricOutFile: mvFile,\n cache: !noCache,\n metricFetcher,\n mode,\n });\n } catch (configError) {\n // syncMetricViewsTypes only throws for a malformed metric-views.json — re-throw as a message-only TypegenFatalError.\n throw new TypegenFatalError(\n [\n {\n name: \"metric-views.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 metric-views.json is absent or in non-blocking mode.\n for (const fe of mvResult.fatalErrors) {\n fatalErrors.push(fe);\n }\n\n // Blocking (`--wait` / prod Vite) escalates per-key DESCRIBE failures — a bad or unreachable source, i.e. a config error\n // to build failures so the end-of-run throw fails after the writes.\n if (mode === \"blocking\") {\n for (const failure of mvResult.failures) {\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 // Types are always written above — including `result: unknown` for any Metric View that could not be described.\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 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 `metric-views.json` was found in the query folder, so nothing\n * 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). The\n * artifacts are still written; {@link generateFromEntryPoint} surfaces these\n * by throwing {@link TypegenFatalError} after the writes. A `\"describe-now\"`\n * run sets no blocking preflight, so for that mode this is always empty.\n */\n fatalErrors: Array<{ name: string; message: string }>;\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.queryFolder - folder that holds `metric-views.json` (`<root>/config/queries`).\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\"`.\n */\nexport async function syncMetricViewsTypes(options: {\n queryFolder: string;\n warehouseId: string;\n metricOutFile: string;\n cache?: boolean;\n metricFetcher?: DescribeFetcher;\n mode?: \"describe-now\" | \"non-blocking\" | \"blocking\";\n}): Promise<SyncMetricViewsTypesResult> {\n const {\n queryFolder,\n warehouseId,\n metricOutFile,\n cache: cacheEnabled,\n metricFetcher,\n mode = \"describe-now\",\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(queryFolder);\n if (!mvConfig) {\n // No metric-views.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 ??= new WorkspaceClient({});\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 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 } 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 }\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.\n preflightFatalMessage = `warehouse ${warehouseId} is ${settled}`;\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 preflightFatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;\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): fail like the query path —\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). The caller surfaces them after the writes. The degraded\n // schemas are not cached (see the write block), so a later pass re-probes.\n described = describeNeeded.map(emptyMetricSchema);\n for (const entry of describeNeeded) {\n fatalErrors.push({ name: entry.key, message: preflightFatalMessage });\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 metric-views.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 // 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 }\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.\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 }\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 await fs.mkdir(path.dirname(metricOutFile), { recursive: true });\n await fs.writeFile(\n metricOutFile,\n generateMetricTypeDeclarations(schemas),\n \"utf-8\",\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 };\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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"}
@@ -21,10 +21,10 @@ const FQN_SEGMENT_NAMES = [
21
21
  ];
22
22
  const FQN_SEGMENT_COUNT = FQN_SEGMENT_NAMES.length;
23
23
  /**
24
- * Locale-independent comparator (UTF-16 code-unit order) shared by BOTH artifact
25
- * key orderings. Plain `sort()` is locale-sensitive, so keys could order
26
- * differently across environments and invalidate the cache hash — this keeps the
27
- * ordering stable everywhere.
24
+ * Locale-independent comparator (UTF-16 code-unit order) for metric-view key
25
+ * ordering. Plain `sort()` is locale-sensitive, so keys could order differently
26
+ * across environments and invalidate the cache hash — this keeps the ordering
27
+ * stable everywhere.
28
28
  */
29
29
  function compareKeys(a, b) {
30
30
  return a < b ? -1 : a > b ? 1 : 0;
@@ -127,5 +127,5 @@ function resolveMetricConfig(config) {
127
127
  }
128
128
 
129
129
  //#endregion
130
- export { compareKeys, isValidFqn, readMetricConfig, resolveMetricConfig };
130
+ export { isValidFqn, readMetricConfig, resolveMetricConfig };
131
131
  //# sourceMappingURL=config.js.map