Skip to content

fix: harden type-safe .riv schema system (review findings) - #355

Merged
mfazekas merged 3 commits into
feat/type-safe-riv-schema-2from
fix/riv-schema-review-fixes
Jul 27, 2026
Merged

fix: harden type-safe .riv schema system (review findings)#355
mfazekas merged 3 commits into
feat/type-safe-riv-schema-2from
fix/riv-schema-review-fixes

Conversation

@mfazekas

Copy link
Copy Markdown
Collaborator

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:

  1. 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.
  2. Schemas with no ViewModels emit viewModels: {} — omitting the key failed the RiveFileSchema constraint and silently degraded those assets (7 of them) to fully untyped.
  3. 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.

mfazekas added 3 commits July 27, 2026 09:24
…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
mfazekas merged commit 9aee375 into feat/type-safe-riv-schema-2 Jul 27, 2026
15 checks passed
@mfazekas
mfazekas deleted the fix/riv-schema-review-fixes branch July 27, 2026 12:55
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant