fix: harden type-safe .riv schema system (review findings) - #355
Merged
mfazekas merged 3 commits intoJul 27, 2026
Conversation
…n typed overloads Fixes from review of the type-safe schema system: - Files without a generated schema (base RiveFileSchema — everything from RiveFileFactory.* and useRiveFile(require(...))) no longer produce TypedViewModelInstance property paths that collapse to never; all path/name constraints degrade to string so existing untyped code keeps compiling. - viewModelName on a schema-typed file is now a hard error when it names a nonexistent ViewModel, instead of silently falling through to the untyped overload and disabling all downstream path checking. - The schema-aware file overloads carry the same async/required/deprecated composition as the untyped ones: required: true keeps its non-null narrowing, and the sync path stays marked @deprecated for typed calls. - Instance accessors and useRiveEnum accept nested paths (up to two hops, matching the sibling hooks); enum value unions resolve through the path. - List elements are plain ViewModelInstance — the schema cannot know which ViewModel a list holds, and the previous all-VMs union made every accessor parameter intersect to never. - TypedViewModelEnumProperty also narrows setValueAsync. All pinned by new tsd tests, including the legacy untyped flows.
… names
- rive-gen-types --all silently validated only a prefix of the assets: an
image decode without WebGL stalls the WASM load() forever, the pending
promise drains bun's event loop, and the process exits 0 mid-batch. The
asset loader now claims assets without decoding (introspection needs no
pixels), each load() gets a 30s watchdog, and the batch continues past
failures and exits non-zero with a summary. click-count.riv.d.ts is new —
it sat in the skipped tail, so its schema was never generated.
- Schemas with no ViewModels now emit viewModels: {} — omitting the key
failed the RiveFileSchema constraint and silently degraded those assets
to fully untyped (7 committed schemas affected).
- Quotes/backslashes in artboard/VM/property/enum names are escaped in the
emitted .d.ts; an enum value containing the '|' separator falls back to
untyped 'enum' with a warning instead of corrupting the union.
- Generated header uses /* eslint-disable */ (// eslint-disable is a no-op).
- CI schema validation uses git status instead of git diff so newly
generated but untracked .riv.d.ts files also fail the check.
- rive-gen-types --all regression test: every .riv must produce exactly one Written line and a .d.ts on disk — fails on the pre-fix generator (which silently wrote 6/29 before the event loop drained) and passes now; verified differentially against the old script. - Emit helpers (strLit/quoteKey/smRecord/vmRecord/schemaBody/enumTypeString) are exported and unit-tested with hostile names; the emitted body is syntax-checked by parsing with the TypeScript compiler, including a negative control showing the unescaped form is broken. main() is guarded by import.meta.main and the WASM shims moved inside it so importing the module for tests has no side effects. - The enum '|' fallback logic is extracted into enumTypeString and covered. - tsd pin: TypedViewModelEnumProperty narrows set and setValueAsync.
mfazekas
added a commit
that referenced
this pull request
Jul 27, 2026
Stacked on #352 (git-town: main → feat/type-safe-riv-schema-2 → this). Addresses the findings from a fresh review of the schema system; every finding was reproduced with compile probes before fixing, and each fix is pinned by new tsd tests. Type level (`useViewModelInstance`, `TypedViewModelInstance`): 1. Untyped files no longer dead-end. Everything from `RiveFileFactory.*` / `useRiveFile(require(...))` carries the base `RiveFileSchema`, and the old typed overload turned their property paths into `never` while `__vmBrand` blocked the untyped hooks — existing code that compiles on main failed to compile. All name/path constraints now degrade to `string` when the schema isn't statically known. 2. `viewModelName` actually constrains: a typo on a schema-typed file is a hard error instead of silently falling through to the untyped overload (which disabled all downstream path checking). 3. `required: true` keeps its non-null narrowing on typed calls, and the sync (non-`async`) path stays `@deprecated` for typed calls too. 4. Nested paths: instance accessors and `useRiveEnum` now accept them like the sibling hooks, up to two hops (`'Coin/Property_Of_Item/Item_Selection'`), with enum value unions resolved through the path. 5. List elements are plain `ViewModelInstance` — the previous all-VMs union made every accessor parameter intersect to `never`. Generator/CI: 6. `rive-gen-types --all` was silently validating only a prefix of the assets: image decode without WebGL stalls the WASM `load()` forever, the pending promise drains bun's event loop, and the process exits 0 mid-batch. The loader now claims assets without decoding, each `load()` has a 30s watchdog, and the batch continues past failures and exits non-zero. `click-count.riv.d.ts` is new in this PR — it sat in the skipped tail, so its schema had never been generated and CI didn't notice. 7. Schemas with no ViewModels emit `viewModels: {}` — omitting the key failed the `RiveFileSchema` constraint and silently degraded those assets (7 of them) to fully untyped. 8. Names are escaped in emitted `.d.ts` files; enum values containing `|` fall back to untyped `enum` with a warning; the header uses a working `/* eslint-disable */`; the CI sync check also catches untracked `.riv.d.ts` files.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #352 (git-town: main → feat/type-safe-riv-schema-2 → this). Addresses the findings from a fresh review of the schema system; every finding was reproduced with compile probes before fixing, and each fix is pinned by new tsd tests.
Type level (
useViewModelInstance,TypedViewModelInstance):RiveFileFactory.*/useRiveFile(require(...))carries the baseRiveFileSchema, and the old typed overload turned their property paths intoneverwhile__vmBrandblocked the untyped hooks — existing code that compiles on main failed to compile. All name/path constraints now degrade tostringwhen the schema isn't statically known.viewModelNameactually constrains: a typo on a schema-typed file is a hard error instead of silently falling through to the untyped overload (which disabled all downstream path checking).required: truekeeps its non-null narrowing on typed calls, and the sync (non-async) path stays@deprecatedfor typed calls too.useRiveEnumnow accept them like the sibling hooks, up to two hops ('Coin/Property_Of_Item/Item_Selection'), with enum value unions resolved through the path.ViewModelInstance— the previous all-VMs union made every accessor parameter intersect tonever.Generator/CI:
rive-gen-types --allwas silently validating only a prefix of the assets: image decode without WebGL stalls the WASMload()forever, the pending promise drains bun's event loop, and the process exits 0 mid-batch. The loader now claims assets without decoding, eachload()has a 30s watchdog, and the batch continues past failures and exits non-zero.click-count.riv.d.tsis new in this PR — it sat in the skipped tail, so its schema had never been generated and CI didn't notice.viewModels: {}— omitting the key failed theRiveFileSchemaconstraint and silently degraded those assets (7 of them) to fully untyped..d.tsfiles; enum values containing|fall back to untypedenumwith a warning; the header uses a working/* eslint-disable */; the CI sync check also catches untracked.riv.d.tsfiles.