Skip to content

chore: CLI typegen for Metric Views#459

Merged
atilafassina merged 16 commits into
mainfrom
mv-cli
Jul 7, 2026
Merged

chore: CLI typegen for Metric Views#459
atilafassina merged 16 commits into
mainfrom
mv-cli

Conversation

@atilafassina

@atilafassina atilafassina commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Adds Metric View type generation to generate-types (the same command + Vite plugin that produces query types — no separate command). When config/queries/metric-views.json is present, the run DESCRIBEs each declared UC Metric View and emits metric-views.d.ts, which augments MetricRegistry so useMetricView('<key>', …) is autocompleted and type-checked. Absent that file, the path stays dormant and nothing is emitted.

Note

Dormant, not yet user-facing, types only. Apps opt in by adding metric-views.json. The runtime metric path (route, SQL engine, useMetricView hook) lands in later PRs of the stack.

What it delivers

  • One entry point — metric-view typegen is folded into generate-types, sharing its warehouse round-trip and cache. shared reaches appkit's syncMetricViewsTypes through the existing dynamic import("@databricks/appkit/type-generator"), keeping no static dependency on appkit.
  • One artifactmetric-views.d.ts (compile-time only). Encodes each view's measures, dimensions, key unions (measureKeys/dimensionKeys/timeGrains), and per-column semantic metadata (type, display_name, format, time_grain). No runtime metadata bundle — that's deferred to the runtime PR where a real consumer drives its shape.
  • Warehouse-readiness contract — identical to query types: non-blocking runs write permissive types + warn on a view that can't be described; --wait fails the build so CI never ships incomplete types.
  • Cache holds only good describes — keyed on source|lane; a degraded outcome is rendered into the .d.ts (so tsc never breaks) but never cached, so --wait re-describes a still-bad source every run instead of serving a sticky degrade. A malformed metric-views.json fails fast in every mode.

Versioning

Everything under shared/appkit-types/ is a build artifact; the versioned input is config/queries/*. The template gitignores metric-views.d.ts (regenerated at build via prebuild); dev-playground tracks it as a demo. Documented in docs/docs/development/type-generation.md.

@github-actions

Copy link
Copy Markdown
Contributor

🔬  Run evals on this PR  ·  Go to Evals Monitor →

@atilafassina atilafassina force-pushed the mv-cli branch 4 times, most recently from b0ca822 to bef7429 Compare June 23, 2026 14:05
@atilafassina atilafassina changed the title feat(shared): appkit metric sync CLI chore: CLI typegen for Metric Views Jun 25, 2026
@atilafassina atilafassina marked this pull request as ready for review June 25, 2026 11:55
@atilafassina atilafassina requested a review from a team as a code owner June 25, 2026 11:55
@atilafassina atilafassina requested review from Copilot and pkosiec June 25, 2026 11:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new appkit mv sync CLI surface (in packages/shared) that can generate Metric View type artifacts outside the Vite dev loop, backed by a new/expanded syncMetricViewsTypes export in @databricks/appkit/type-generator and a refactor to reuse the same unified metric sync pipeline from both the CLI and generateFromEntryPoint.

Changes:

  • Add appkit mv sync command with interactive/non-interactive flows, schema validation, dormancy behavior, and cache controls.
  • Introduce/extend syncMetricViewsTypes in the appkit type-generator and refactor generateFromEntryPoint to delegate Metric View emission to it.
  • Standardize Metric View artifact filenames to metric-views.d.ts and metric-views.metadata.json, updating tests accordingly.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/shared/src/cli/index.ts Wires the new mv parent command into the shared CLI.
packages/shared/src/cli/commands/type-generator.d.ts Extends the ambient optional @databricks/appkit/type-generator declaration with syncMetricViewsTypes + metric artifact constants.
packages/shared/src/cli/commands/metric-views/index.ts Adds the mv parent command and attaches the sync subcommand.
packages/shared/src/cli/commands/metric-views/validate-metric-views-source.ts Adds Zod-based validation and CLI-friendly formatting for metric-views.json.
packages/shared/src/cli/commands/metric-views/validate-metric-views-source.test.ts Unit tests for metric source validation + formatting.
packages/shared/src/cli/commands/metric-views/sync/sync.ts Implements appkit mv sync (path resolution, dormancy, dynamic import, error taxonomy, interactive prompts).
packages/shared/src/cli/commands/metric-views/sync/sync.test.ts Tests CLI behavior (flag vs interactive, error taxonomy, cache flag propagation).
packages/appkit/tsdown.config.ts Ensures ./type-generator is built as its own entry so dynamic-import consumers see required exports.
packages/appkit/src/type-generator/index.ts Refactors metric emission to a unified syncMetricViewsTypes pipeline and updates exported metric artifact constants.
packages/appkit/src/type-generator/mv-registry/render-types.ts Updates comments to reflect the new artifact filename.
packages/appkit/src/type-generator/tests/vite-plugin.test.ts Updates expected default/custom Metric View artifact filenames.
packages/appkit/src/type-generator/tests/index.test.ts Updates expected Metric View artifact filenames in type-generator tests.
packages/appkit/src/type-generator/tests/mv-registry.test.ts Updates metadata bundle naming references in tests.
packages/appkit/src/type-generator/tests/sync-metric-views-types.test.ts Adds focused unit tests for the new syncMetricViewsTypes export, including cache behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/shared/src/cli/commands/metric-views/sync/sync.ts Outdated
Comment thread packages/shared/src/cli/commands/metric-views/sync/sync.ts Outdated
Comment thread packages/appkit/src/type-generator/index.ts Outdated
Comment thread packages/shared/src/cli/commands/metric-views/index.ts Outdated
Comment thread packages/shared/src/cli/commands/metric-views/sync/sync.ts Outdated

@pkosiec pkosiec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the mv-cli typegen changes — solid refactor, and behavior parity for the dev/Vite (non-blocking/blocking) paths reads as preserved 1:1 (the extracted syncMetricViewsTypes keeps serveDegraded = mode !== "describe-now", the preflight, the #406 gate, the sticky-degraded notice, cache save/prune, and the fatalErrors→throw ordering). Findings inline below — mostly P3 polish; nothing blocks correctness of the extraction. Verified the --no-cache / getOptionValueSource handling and the @clack/prompts usage against their docs — both correct.

Two nits not worth their own threads:

  • Stale metric.d.ts / metrics.metadata.json aren't cleaned up on the rename to metric-views.* (removeOldGeneratedTypes only removes appKitTypes.d.ts) — low impact for a pre-release feature, but an upgraded app could keep an orphaned metric.d.ts.
  • The CLI's Zod pre-validation doesn't mirror resolveMetricConfig's 200-entry / 255-char-segment caps, so an over-cap config passes the "fail fast before the dynamic import" stage and throws only after it (still exit 1, just later).

Verdict: ready with minor fixes.

Comment thread packages/shared/src/cli/commands/metric-views/sync/sync.ts Outdated
Comment thread packages/shared/src/cli/commands/metric-views/sync/sync.ts Outdated
Comment thread packages/shared/src/cli/commands/type-generator.d.ts Outdated
Comment thread packages/shared/src/cli/commands/metric-views/sync/sync.ts Outdated
Comment thread packages/shared/src/cli/commands/metric-views/sync/sync.ts Outdated
Comment thread packages/shared/src/cli/commands/metric-views/sync/sync.ts Outdated
Comment thread packages/shared/src/cli/commands/metric-views/index.ts Outdated
Comment thread packages/shared/src/cli/commands/metric-views/sync/sync.ts Outdated
@atilafassina atilafassina enabled auto-merge (squash) June 30, 2026 10:21
@atilafassina atilafassina disabled auto-merge June 30, 2026 11:40
Comment thread packages/shared/src/cli/commands/type-generator.d.ts Outdated
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 658 KB (-4.1 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 686 KB (-2.7 KB) 240 KB (-1.1 KB)
Type declarations 268 KB (-323 B) 91 KB (-113 B)
Source maps 1.3 MB (-9.1 KB) 445 KB (-3.3 KB)
Other 11 KB 3.7 KB
Total 2.3 MB (-12 KB) 779 KB (-4.5 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 74 KB (-186 B) 2.5 KB 76 KB (-186 B) external 243 KB (-576 B)
./beta 39 KB 231 B 39 KB external 117 KB
./type-generator 18 KB (-161 B) 0 B 18 KB (-161 B) external 53 KB (-449 B)

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 70 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 30 KB
./beta databricks.js initial 5.7 KB
./beta service-context.js initial 3.0 KB
./beta client-options.js initial 219 B
./beta databricks.js lazy 128 B
./beta index.js lazy 103 B
./type-generator index.js initial 18 KB

@databricks/appkit-ui

npm tarball (packed): 297 KB — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 363 KB 120 KB
Type declarations 203 KB 73 KB
Source maps 672 KB 219 KB
CSS 16 KB 3.3 KB
Total 1.2 MB 415 KB
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 4.2 KB 49 KB 54 KB 208 KB 11 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 591 KB 49 KB 640 KB 1.8 MB 167 KB
./react/beta 20 B 0 B 20 B 0 B 0 B

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 4.1 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 589 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 20 B

Adds an appkit mv sync command that fetches Unity Catalog metric-view schemas
and emits metric.d.ts plus metrics.metadata.json outside the Vite dev loop (CI,
non-Vite builds, manual refresh). The command lives in shared and reaches
appkit's sync core via dynamic import of the type-generator entry with an
ambient declaration and a graceful appkit-absent fallback, so shared keeps no
static appkit dependency. A new appkit syncMetricViewsTypes export reuses the
existing metric writers, adaptive describe fetcher and persistent cache helpers,
so the emitted bundle matches the Vite plugin output. Config is validated
against metricSourceSchema before sync, an absent default file exits zero for
dormancy while error modes exit non-zero with distinct messages, and
interactive and non-interactive flows mirror plugin create. Flags are
--warehouse-id, --metric-views-json-path, --output-dir and --no-cache. Fourth
change in the metric-views decomposition after #427, #429 and #433.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…Path

"format" describes what the helper does — render a Zod issue path as a CLI
string — without the "humanize" framing it borrowed from the plugin-manifest
validator's humanizePath. Update its caller, tests, and doc comments to match.

Also trim redundant comments in the type-generator .d.ts shim and align the
syncMetricViewsTypes doc with the appkit source.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
`appkit mv sync` runs in describe-now mode, where a not-ready (cold or
starting) warehouse can return no schema for a metric view WITHOUT a hard
DESCRIBE failure: the appkit export writes permissive (degraded) types and
reports them via `schemas[].degraded` with an empty `failures` list. The CLI
only inspected `failures`, so this path fell through to the success message and
exited 0 with no signal, silently shipping permissive `unknown` types.

Add a degraded-schema check after the failures gate: name the affected views,
tell the user to rerun once the warehouse is available, and exit 0 — a not-ready
warehouse is transient, not a hard failure (genuine DESCRIBE failures still exit
non-zero, unchanged).

Also align the ambient `@databricks/appkit/type-generator` shim's
`SyncMetricViewsTypesResult` with the real export by adding `fatalErrors`
(always empty for the CLI's describe-now mode; declared to keep the
cross-package contract honest).

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Rename the generated metric-view artifacts for naming consistency with the
`metric-views.json` source config (and to fix the metric/metrics singular-vs-
plural split):
  - METRIC_TYPES_FILE:    metric.d.ts           -> metric-views.d.ts
  - METRIC_METADATA_FILE: metrics.metadata.json -> metric-views.metadata.json

Touches the constants, the dev/Vite generate path, the ambient type-generator
shim, the shared `mv sync` CLI, and all test assertions across appkit + shared.
The Metric Views feature is pre-release (not shipped), so no migration is needed.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
- interactive mode no longer double-prints the success line
- narrow appkit-missing detection so real sync errors surface verbatim
- match the interactive outro to the run outcome (dormant != success)
- exit 0 on interactive cancel (consistent with plugin create)
- canonical command `metric-views` with `mv` alias
- add `--wait` (blocking preflight) to fail instead of degrade in CI
- document the ambient type-generator shim's intentional narrowing

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…ignores them

dev-playground commits metric-views.d.ts + metric-views.metadata.json alongside
its already-tracked analytics.d.ts (it has no build-time typegen to regenerate
them), plus the source config metric-views.json. The scaffolding template
gitignores the two generated files, since prebuild runs `generate-types --wait`
and bakes fresh types into the bundle before deploy.

Document the commit-vs-generate policy in type-generation.md.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Remove the standalone `appkit metric-views`/`mv sync` command and emit
metric-view types additively from `generate-types` instead: when
config/queries/metric-views.json is present, generateFromEntryPoint now
delegates to syncMetricViewsTypes and writes metric-views.d.ts +
metric-views.metadata.json as siblings of the query out file, staying
dormant otherwise.

The CLI no longer calls syncMetricViewsTypes directly, so the shared
type-generator.d.ts shim narrows to the generateFromEntryPoint /
generateServingTypes surface, and the tsdown subpath-entry comment is
updated to match. Drops the metric-views command tree and its
registration.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
dev-playground gitignores metric-views.d.ts + metric-views.metadata.json
alongside its existing serving.d.ts — all are regenerated on `pnpm dev` by
AppKit's dev-server appKitTypesPlugin (generateFromEntryPoint emits the
metric-view pair additively when metric-views.json is present), so the
generated artifacts don't need to be versioned. Only the source config
metric-views.json stays tracked.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…ata bundle

The metric-view typegen shipped two artifacts: metric-views.d.ts (the
MetricRegistry augmentation) and metric-views.metadata.json (a runtime
Record<key,{measures,dimensions}> bundle). The JSON bundle is reader-less on
the current stack — nothing consumes it at runtime (its documented consumer
registerMetricsMetadata() is only prose in a doc comment), and the entire
runtime metric path — route, SQL builder, validator, useMetricView hook —
lands in PR2/PR5, neither on main yet. It shipped dormant in #433.

Strip it clean rather than pre-position an artifact PR2 may not want in this
shape. The single-source end-state (types + runtime validation from one
generated Zod/Standard-Schema module) is deferred to the runtime PRs, where the
consumer contract is real and can drive the artifact's shape.

- delete mv-registry/metadata.ts (buildMetricsMetadataBundle,
  generateMetricsMetadataJson)
- remove mvMetadataOutFile/metricMetadataOutFile wiring from index.ts and
  vite-plugin.ts, the METRIC_METADATA_FILE const + registerMetricsMetadata doc
  prose, the ambient-shim mirror, and the CLI's metadata report line
- drop the now-unused export on compareKeys (metadata.ts was its only external
  consumer; config.ts still uses it internally)
- keep metric-views.d.ts unchanged, including its type-level metadata block
  (SQL type/display_name/format/time_grain) — the sole carrier of that data now
- re-point metadata-bundle test assertions at the .d.ts where a type-level
  equivalent exists; drop the rest
- docs + both gitignores (template, dev-playground) drop the .metadata.json line

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Reverses the earlier flip that gitignored it. Track it alongside the
already-tracked analytics.d.ts so the metric-view types are visible in-repo;
serving.d.ts stays ignored (per-developer, endpoint-specific). The
.metadata.json line was already removed in the strip (cf8009f). The template
still gitignores the .d.ts — scaffolded apps regenerate it at build via
prebuild --wait.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
@atilafassina atilafassina force-pushed the mv-cli branch 2 times, most recently from 69c476c to 8d70d57 Compare July 3, 2026 07:52
Comment thread packages/shared/src/cli/commands/type-generator.d.ts
Comment thread packages/appkit/tsdown.config.ts Outdated
A degraded metric-view outcome (DESCRIBE skipped behind a not-running or
deleted warehouse, unanswered, or a per-key failure) was written to the
typegen cache with a `retry` flag. A deterministic failure — a genuinely bad
`source` — was pinned `retry: false` (sticky), and sticky degraded entries
were served as cache hits even under `--wait`. So a misconfigured view, once
cached sticky by any non-blocking pass, made `--wait`/CI green-build with
permissive types instead of failing — contradicting the stated contract that
`--wait` must not ship permissive types for a misconfigured view.

Mirror the query path, which caches a describe result and "Never persists
`result: unknown`": cache ONLY a successful (non-degraded) describe. A degraded
outcome is still rendered into the emitted `.d.ts` (so the file always exists
and tsc never breaks), but is never cached — any stale entry for the key is
deleted. The key stays uncached, so the next eligible pass simply re-describes
it. This makes the invariant "the cache holds only good schemas" hold after
every pass, so no sticky degraded entry can be served and `--wait` re-describes
a still-bad source every run (failing the build) instead of serving it green.

DELETED-warehouse handling is unchanged where it matters: the blocking
preflight still throws TypegenFatalError; non-blocking still degrades and stays
resilient per the non-blocking typegen contract — it just no longer pins a
sticky entry.

- index.ts: cache only non-degraded describes; delete any stale entry on
  degrade; drop the derived `retry` (write constant false, vestigial for
  on-disk shape compat, mirroring the query path)
- remove now-dead machinery: terminalSkip, stickyDegradedHits, the sticky-hit
  notice, and the serveDegraded cache-hit special-case
- simplify the hit predicate to "revivable + hash match + not degraded" (the
  degraded guard also rejects a stale degraded entry left by an older writer)
- cache.ts: rewrite the MetricCacheEntry doc to the new contract
- tests: assert the new invariant (degraded ⇒ uncached ⇒ re-described); add a
  regression test that `--wait` over a still-bad source fails the build rather
  than serving a cached degrade

Found by Isaac Review (multi-model debate, correctness consensus).

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Condense the 21-line ambient-mirror doc to the two load-bearing facts
(leaf-package/no-static-dep rationale + hand-resync drift warning) and
drop the symbol-by-symbol enumeration that restated the declarations
below it.

Addresses review comment #3520857783.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…iews.d.ts in CLI

The `./type-generator` tsdown entry was added only to preserve
METRIC_TYPES_FILE across the CLI's cross-package dynamic import. But the
functions the CLI actually calls (generateFromEntryPoint,
generateServingTypes) are already anchored in the bundle by appkit's own
Vite plugins, exactly as on main — the entry protected nothing but a
string constant. Hardcode "metric-views.d.ts" in the CLI (consistent with
its already-hardcoded analytics.d.ts / serving.d.ts siblings), drop it
from the ambient mirror and test mock, and revert the entry to main's
proven ["src/index.ts", "src/beta.ts"].

Verified against the built dist: generateFromEntryPoint and
generateServingTypes survive tree-shaking without the entry; the built
CLI emits all three type artifacts end-to-end.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
@atilafassina atilafassina merged commit 74b5b02 into main Jul 7, 2026
12 checks passed
@atilafassina atilafassina deleted the mv-cli branch July 7, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants