chore: CLI typegen for Metric Views#459
Conversation
|
b0ca822 to
bef7429
Compare
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>
There was a problem hiding this comment.
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 synccommand with interactive/non-interactive flows, schema validation, dormancy behavior, and cache controls. - Introduce/extend
syncMetricViewsTypesin the appkit type-generator and refactorgenerateFromEntryPointto delegate Metric View emission to it. - Standardize Metric View artifact filenames to
metric-views.d.tsandmetric-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.
pkosiec
left a comment
There was a problem hiding this comment.
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.jsonaren't cleaned up on the rename tometric-views.*(removeOldGeneratedTypesonly removesappKitTypes.d.ts) — low impact for a pre-release feature, but an upgraded app could keep an orphanedmetric.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.
- 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>
| * are widened to `unknown[]`, options the CLI never passes (e.g. `metricFetcher`) | ||
| * are omitted, and only the surface the CLI actually uses is declared. | ||
| * | ||
| * DRIFT WARNING: there is NO compile-time link to appkit's real types — if the |
There was a problem hiding this comment.
maybe we should move the cli to the appkit package?
…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>
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 690 KB (+879 B) | 241 KB (+116 B) |
| Type declarations | 284 KB (+16 KB) | 98 KB (+6.7 KB) |
| Source maps | 1.3 MB (+1.7 KB) | 449 KB (+854 B) |
| Other | 11 KB | 3.7 KB |
| Total | 2.3 MB (+18 KB) | 791 KB (+7.6 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 (-79 B) | 2.5 KB | 76 KB (-79 B) | external | 243 KB (-257 B) |
./beta |
39 KB | 231 B | 39 KB | external | 117 KB |
./type-generator |
19 KB (-25 B) | 0 B | 19 KB (-25 B) | external | 53 KB (-131 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 | 19 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 |
Metric-view types are generated by
generate-typesitself — the same command (and Vite plugin) that produces query types. When aconfig/queries/metric-views.jsonis present, the run DESCRIBEs each declared UC Metric View and emitsmetric-views.d.tsalongside the query types; when it's absent the path stays dormant and nothing is emitted.Note
Dormant feature, still not user-facing. Apps opt in only by adding
metric-views.json. The runtime metric path (route, SQL engine,useMetricViewhook) lands in later PRs of the stack — this PR is the type generator only.What changed
This PR started as a standalone
appkit mv synccommand and was folded intogenerate-typesinstead — there is no separate metric-view command. This keeps one entry point for all typegen (queries, serving, metric-views), one warehouse round-trip, and one cache.generateFromEntryPointdelegates metric emission to appkit'ssyncMetricViewsTypesexport (readMetricConfig → resolveMetricConfig → syncMetrics → write metric-views.d.ts), reusing the existing metric writers, the adaptive DESCRIBE fetcher, and the post-#433 persistent cache helpers (loadCache/saveCache/metricCacheHash). ThesharedCLI reaches it through the same dynamicimport("@databricks/appkit/type-generator")patterngenerate-typesalready uses, sosharedkeeps no static dependency onappkit; the ambient shim narrows to justgenerateFromEntryPoint/generateServingTypes.One emitted artifact:
metric-views.d.tsThe generator emits a single file —
metric-views.d.ts— which augments theMetricRegistryinterface souseMetricView('<key>', …)is autocompleted and type-checked. It is compile-time only (erased at build). Each view's measures, dimensions, key unions (measureKeys/dimensionKeys/timeGrains), and per-column semantic metadata (SQLtype,display_name,format,time_grain) are all encoded at the type level, including in a type-levelmetadata:block.Why no runtime metadata bundle
An earlier iteration also emitted a
metric-views.metadata.jsonruntime bundle (aRecord<key, { measures, dimensions }>value shipped in the client bundle). That was dropped. It was a reader-less artifact on the current stack: nothing consumes it at runtime — its intended consumer lands with the runtime metric path in a later PR, not here. Rather than pre-position a runtime artifact whose shape the (not-yet-existent) consumer should drive, this PR ships types only. The single-source end-state — one generated artifact from which both the compile-time types and runtime validation derive (e.g. a generated Zod/Standard-Schema module) — is deferred to the runtime PR, where the consumer contract is real. See the removal ofmv-registry/metadata.ts.Behaviour
metric-views.json⇒ nothing emitted, exit 0. Adopting metric views is purely additive; apps that don't use them see no new files and no noise.--waitthat same situation fails the build so CI never ships incomplete metric types.metric-views.json(invalid JSON, or asourcethat isn't a three-part UC FQN) throws in every mode; it's the only way the metric path throws.metric-views.jsonis keyed by metric key; each entry names the three-part UC FQN of the view and, optionally, the executor it runs as:{ "metricViews": { "revenue": { "source": "catalog.schema.revenue_metrics" }, "customers": { "source": "catalog.schema.customer_metrics", "executor": "user" } } }Versioning policy (committed vs generated)
Everything under
shared/appkit-types/is a build artifact; the versioned input isconfig/queries/*(includingmetric-views.json).metric-views.d.ts(next toserving.d.ts) — scaffolded apps regenerate it at build viaprebuild: generate-types --waitbeforevite buildbakes the types into the bundle.metric-views.d.tsalongside its already-trackedanalytics.d.ts, so the metric-view types are visible in-repo as a demo/reference.serving.d.tsstays gitignored (per-developer, endpoint-specific). The sourcemetric-views.jsonis tracked either way.Documented in
docs/docs/development/type-generation.md.