Deriving Full TypeScript IntelliSense for JSON Schema Forms from a Vue3 Component Registry
Schema-driven form engines are common in low-code platforms, but writing config objects where `componentProps` is `any` pushes prop errors to runtime. This technique catches them in the IDE instead, and the mapped-type-to-union pattern generalizes to any TypeScript config where a discriminant key should narrow a sibling's type.
A type-level pipeline turns a Vue3 component registry into a fully typed JSON Schema config. When a developer writes `component: 'Radio'`, the `componentProps` field narrows to Radio's actual props — and `decorator: 'FormItem'` does the same for `decoratorProps`. The chain runs from a literal `Components` object through template-literal path types (`'Input' | 'Input.Textarea'`), mapped types that build per-path data packets, and an identity `defineSchema` function that anchors the types at compile time with zero runtime cost.
Two-level component paths like `Input.Textarea` are supported via `ExtractChildren` and `infer`-based string splitting. Decorator linkage uses the "mapped type builds a table, indexed access collects a union" pattern — a reusable technique for any config where a discriminant field determines the shape of a sibling field. For Markup/TSX usage, a `defineComponent` is type-asserted as a generic constructor (`as new <T>()`) to work around Vue3's weak native generic component support.
The implementation draws inspiration from Formily but targets a gap: Formily's JSON Schema authoring lacks comprehensive type hints. Known limits include two-level path depth (deeper recursion is too expensive for the type checker), incomplete `InstanceType['$props']` extraction for some HOC/functional components, and Volar type-explosion when too many components are registered — mitigated to handle 50 components.
The mapped-type-then-indexed-access pattern (`{ [K in Paths]: {...} }[Paths]`) is underused in application code but solves a recurring problem: making two sibling fields in a config object discriminate on each other without writing explicit discriminated unions by hand.
Vue3's type system treats components inconsistently — `InstanceType<T>['$props']` works for `defineComponent` and many SFCs but breaks on functional components and complex HOCs, which means a production registry needs an adapter layer the article only gestures at.
The two-level path limit is a deliberate engineering tradeoff, not a TypeScript limitation: recursive template literal types can go deeper, but the compile-time cost becomes prohibitive for a dev-experience feature.
The `as new <T>()` assertion on a runtime `defineComponent` is a pragmatic hack that decouples IDE experience from Vue's actual generic inference, but it creates a maintenance contract — the asserted type must stay manually aligned with the runtime props definition.
Formily's JSON Schema authoring lacking comprehensive type hints is a measurable gap in an otherwise mature ecosystem; this implementation fills it specifically for Vue3, where the generic-component weakness makes the problem harder than in React.