Skip to content

feat: type-safe .riv schema system#292

Closed
mfazekas wants to merge 18 commits into
mainfrom
fix/android-autoplay-before-databind
Closed

feat: type-safe .riv schema system#292
mfazekas wants to merge 18 commits into
mainfrom
fix/android-autoplay-before-databind

Conversation

@mfazekas

@mfazekas mfazekas commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Overview

Codegen pipeline that extracts artboard/state machine/ViewModel schemas from .riv files and surfaces them as TypeScript phantom types — no runtime overhead.

How it works

Step 1 — generate .riv.d.ts alongside each asset (committed to git):

yarn rive-gen-types example/assets/rive/rewards.riv
yarn rive-gen-types --all example/assets/rive/  # batch

Uses @rive-app/canvas WASM to introspect the file and emits a declaration:

declare const asset: RiveAsset<{
  artboards: 'Main' | 'Lives 2';
  defaultArtboard: 'Main';
  stateMachines: { 'Main': 'State Machine 1' };
  viewModels: {
    'Rewards': {
      'Price_Value': 'number';
      'favourite_pet': 'enum:chipmunk|rat|frog|owl|cat|dog';
      'Coin': 'viewModel:Item_Icon_Value';
    };
  };
}>;
export default asset;

Step 2 — use the typed asset:

import rewardsRiv from './assets/rive/rewards.riv';

// TypedRiveFile<T> — accepts RiveAsset directly, schema flows through
const { riveFile } = useRiveFile(rewardsRiv);

// Artboard/SM names constrained to schema
<RiveView file={riveFile} artboardName="Main" stateMachineName="State Machine 1" />

// viewModelName constrained to schema VMs
const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Rewards' });

// Property paths constrained to the correct kind
useRiveNumber('Price_Value', instance);          // ✓
useRiveNumber('Coin', instance);                 // ✗ — Coin is a viewModel ref, not number
useRiveNumber('Coin/Item_Value', instance);      // ✓ — nested path
useRiveTrigger('Coin/Item_Value', instance);     // ✗ — Item_Value is number, not trigger

// Enum values constrained to the declared set
const { value } = useRiveEnum('favourite_pet', instance);
//    value: 'chipmunk' | 'rat' | 'frog' | 'owl' | 'cat' | 'dog'

// Inline types — no intermediate type aliases needed
function MyComponent({ file }: { file: TypedRiveFile<typeof rewardsRiv> }) { ... }
type RewardsInstance = TypedViewModelOf<typeof rewardsRiv, 'Rewards'>;

What's included

  • scripts/rive-extract-schema.ts — WASM-based extractor; resolves nested VM refs and extracts enum values
  • scripts/rive-gen-types.ts — generates .riv.d.ts, single file or --all batch
  • src/core/TypedRiveFile.tsRiveAsset<T>, TypedRiveFile<T>, SchemaOf<T>
  • src/core/TypedViewModelInstance.tsTypedViewModelInstance<T,N>, TypedViewModelOf<T,N>, UntypedViewModelInstance, PathsOfKind, EnumValuesOf
  • Typed overloads on RiveView, useRiveFile, useViewModelInstance, useRiveNumber/String/Boolean/Color/Enum/Trigger
  • __vmBrand phantom property on TypedViewModelInstance — prevents typed instances from silently matching untyped hook overloads
  • src/__tests__/typed-rive.test-d.ts — tsd type-level tests against real .riv.d.ts files
  • CI step that regenerates all schemas and fails if any .riv.d.ts is out of date

Notes

  • .riv.d.ts files are committed (same as nitrogen-generated bindings); CI enforces they stay in sync
  • Requires allowArbitraryExtensions: true in tsconfig for .riv imports to resolve their .riv.d.ts
  • Enum names are not accessible from the WASM JS layer (only values are); a native schemaToJSON() in the C++ runtime would fix this cleanly

@mfazekas mfazekas requested a review from HayesGordon June 15, 2026 05:50
mfazekas added 3 commits June 15, 2026 07:56
Adds a codegen pipeline that extracts artboard/SM/ViewModel schemas from .riv files at build time and exposes them as TypeScript phantom types. RiveView, useRiveFile, useRiveNumber/String/Boolean/Color/Trigger, and useViewModelInstance all gain typed overloads so wrong artboard names, state machine names, or property paths are caught at compile time.

Scripts: rive-extract-schema.ts (WASM-based extractor), rive-gen-types.ts (generates .riv.d.ts alongside assets), with bun tests for both. Type-level tests via tsd in src/__tests__/typed-rive.test-d.ts.
@mfazekas mfazekas force-pushed the fix/android-autoplay-before-databind branch from 71c5e59 to e932f41 Compare June 15, 2026 05:56
mfazekas added 6 commits June 15, 2026 08:15
…lInstance

Adds __vmBrand to TypedViewModelInstance so TypeScript rejects passing a typed
instance to untyped hook overloads, ensuring wrong property paths are caught at
compile time. Also adds allowArbitraryExtensions to tsconfig for .riv imports.
@mfazekas mfazekas changed the title feat: type-safe .riv schema system (POC) feat: type-safe .riv schema system Jun 15, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Introduces a POC build-time codegen pipeline that extracts .riv schemas and surfaces them as TypeScript phantom types, enabling type-safe artboard/state machine/view model usage with no runtime overhead.

Changes:

  • Adds RiveAsset<T>, TypedRiveFile<T>, and TypedViewModelInstance<T, VMName> plus typed path utilities to constrain hook/component inputs.
  • Adds Bun-based schema extraction + .riv.d.ts generator scripts, along with Bun tests for the scripts.
  • Adds tsd type-level tests and updates examples/config to support typed .riv imports.

Reviewed changes

Copilot reviewed 24 out of 56 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
yarn.lock Adds lock entries for @rive-app/canvas and tsd dependencies.
tsconfig.json Enables arbitrary extension imports and excludes type-test/script-test files from standard TS checking.
src/index.tsx Re-exports new typed schema/instance utility types from the public entrypoint.
src/hooks/useViewModelInstance.ts Adds a typed overload returning a TypedViewModelInstance when schema + literal VM name are provided.
src/hooks/useRiveTrigger.ts Adds typed overload for trigger property paths based on schema.
src/hooks/useRiveString.ts Adds typed overload for string property paths based on schema.
src/hooks/useRiveNumber.ts Adds typed overload for number property paths based on schema.
src/hooks/useRiveFile.ts Adds typed overload so importing a typed .riv asset yields a TypedRiveFile<T>.
src/hooks/useRiveEnum.ts Adds typed overload for enum properties (typed enum values).
src/hooks/useRiveColor.ts Adds typed overload for color property paths based on schema.
src/hooks/useRiveBoolean.ts Adds typed overload for boolean property paths based on schema.
src/core/TypedViewModelInstance.ts Introduces core type utilities for typed view model instances, paths, enums, lists, and branding.
src/core/TypedRiveFile.ts Introduces schema + branded asset/file phantom types (RiveFileSchema, RiveAsset, TypedRiveFile, SchemaOf).
src/core/RiveView.tsx Types RiveViewProps to constrain artboard/state machine names when a schema is present.
src/core/RiveFile.ts Makes factory methods generic over schema types and attempts to type fromSource for branded assets.
src/tests/typed-rive.test-d.ts Adds tsd type tests validating typed assets, RiveViewProps, typed instances, and typed hooks.
scripts/rive-gen-types.ts Adds generator CLI to emit .riv.d.ts declarations (and optional standalone schema types).
scripts/rive-extract-schema.ts Adds WASM-based schema extraction script using @rive-app/canvas.
scripts/tests/rive-gen-types.test.ts Adds Bun tests verifying .riv.d.ts generation output.
scripts/tests/rive-extract-schema.test.ts Adds Bun tests verifying extracted artboards/SMs/view models (including nested refs + enums).
package.json Adds rive-gen-types, typetest (tsd), and script tests; adds deps for generator/testing; configures Jest ignore for .test-d.ts.
example/src/exercisers/RiveDataBindingExample.tsx Updates example to use typed .riv import + typed view model instance/property paths.
example/src/exercisers/FileSwitcher.tsx Adds exerciser to rapidly switch files/fits to stress dispose/render race conditions.
example/assets/rive/vm_value_change_test.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/viewmodelproperty.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/style_fallback_fonts.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/rewards.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/rating.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/quick_start.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/out_of_band.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/on_entry_test.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/nodefaultbouncing.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/movecircle.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/many_viewmodels.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/layouts_demo.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/layout_test.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/ios_android_layouts_demo_v01.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/inputglow.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/hello_world_text.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/GradientBorder.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/font_fallback.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/fallback_fonts.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/databinding.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/databinding_lists.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/databinding_images.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/counter.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/bouncing_ball.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/blinko.riv.d.ts Adds generated schema typing for the asset.
example/assets/rive/artboard_db_test.riv.d.ts Adds generated schema typing for the asset.
eslint.config.mjs Excludes .test-d.ts files from ESLint.

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

Comment thread src/core/RiveFile.ts
Comment thread scripts/rive-gen-types.ts Outdated
Comment thread scripts/rive-gen-types.ts
Comment thread src/hooks/useRiveEnum.ts
@mfazekas mfazekas closed this Jun 15, 2026
@mfazekas mfazekas deleted the fix/android-autoplay-before-databind branch June 15, 2026 11:26
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.

2 participants