diff --git a/example/assets/rive/GradientBorder.riv.d.ts b/example/assets/rive/GradientBorder.riv.d.ts
index 8e9d41ec..02de2ce8 100644
--- a/example/assets/rive/GradientBorder.riv.d.ts
+++ b/example/assets/rive/GradientBorder.riv.d.ts
@@ -3,6 +3,7 @@
// Source: GradientBorder.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Layout';
defaultArtboard: 'Layout';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
isFocused: 'boolean';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/arbtboards-models-instances.riv.d.ts b/example/assets/rive/arbtboards-models-instances.riv.d.ts
index 225a4f90..f266b571 100644
--- a/example/assets/rive/arbtboards-models-instances.riv.d.ts
+++ b/example/assets/rive/arbtboards-models-instances.riv.d.ts
@@ -3,6 +3,7 @@
// Source: arbtboards-models-instances.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'artboard1' | 'artboard3' | 'artboard2' | 'artboardc1';
defaultArtboard: 'artboard1';
stateMachines: {
@@ -23,5 +24,6 @@ declare const asset: RiveAsset<{
_id: 'string';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/artboard_db_test.riv.d.ts b/example/assets/rive/artboard_db_test.riv.d.ts
index 4c3eb1e0..066cb76c 100644
--- a/example/assets/rive/artboard_db_test.riv.d.ts
+++ b/example/assets/rive/artboard_db_test.riv.d.ts
@@ -3,6 +3,7 @@
// Source: artboard_db_test.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Main' | 'ArtboardGreen' | 'ArtboardBlue' | 'ArtboardRed';
defaultArtboard: 'Main';
stateMachines: {
@@ -18,5 +19,6 @@ declare const asset: RiveAsset<{
artboard_2: 'artboard';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/blinko.riv.d.ts b/example/assets/rive/blinko.riv.d.ts
index bc077aca..c25767ca 100644
--- a/example/assets/rive/blinko.riv.d.ts
+++ b/example/assets/rive/blinko.riv.d.ts
@@ -3,6 +3,7 @@
// Source: blinko.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Main' | 'StoreItem' | 'Token' | 'Indicator' | 'Puck' | 'StoreIcon' | 'Peg' | 'scoreBox';
defaultArtboard: 'Main';
stateMachines: {
@@ -97,5 +98,6 @@ declare const asset: RiveAsset<{
'property of gameLogicVM': 'viewModel:gameLogicVM';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/bouncing_ball.riv.d.ts b/example/assets/rive/bouncing_ball.riv.d.ts
index 7e193b35..0cf65904 100644
--- a/example/assets/rive/bouncing_ball.riv.d.ts
+++ b/example/assets/rive/bouncing_ball.riv.d.ts
@@ -3,6 +3,7 @@
// Source: bouncing_ball.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
ypos: 'number';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/click-count.riv.d.ts b/example/assets/rive/click-count.riv.d.ts
index b7f43555..92a34c83 100644
--- a/example/assets/rive/click-count.riv.d.ts
+++ b/example/assets/rive/click-count.riv.d.ts
@@ -3,6 +3,7 @@
// Source: click-count.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
clickCount: 'number';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/counter.riv.d.ts b/example/assets/rive/counter.riv.d.ts
index 82927ffe..5a0771ed 100644
--- a/example/assets/rive/counter.riv.d.ts
+++ b/example/assets/rive/counter.riv.d.ts
@@ -3,6 +3,7 @@
// Source: counter.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
cnt: 'number';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/databinding.riv.d.ts b/example/assets/rive/databinding.riv.d.ts
index f3aae26d..943470e6 100644
--- a/example/assets/rive/databinding.riv.d.ts
+++ b/example/assets/rive/databinding.riv.d.ts
@@ -3,6 +3,7 @@
// Source: databinding.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -27,5 +28,6 @@ declare const asset: RiveAsset<{
name: 'string';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/databinding_images.riv.d.ts b/example/assets/rive/databinding_images.riv.d.ts
index 1ecb298a..9d8e690f 100644
--- a/example/assets/rive/databinding_images.riv.d.ts
+++ b/example/assets/rive/databinding_images.riv.d.ts
@@ -3,6 +3,7 @@
// Source: databinding_images.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -11,8 +12,9 @@ declare const asset: RiveAsset<{
enums: {};
viewModels: {
MyViewModel: {
- bound_image: 'image';
+ bound_image: 'assetImage';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/databinding_lists.riv.d.ts b/example/assets/rive/databinding_lists.riv.d.ts
index b11c0ec1..786e52b1 100644
--- a/example/assets/rive/databinding_lists.riv.d.ts
+++ b/example/assets/rive/databinding_lists.riv.d.ts
@@ -3,6 +3,7 @@
// Source: databinding_lists.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -17,5 +18,6 @@ declare const asset: RiveAsset<{
team: 'list';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/fallback_fonts.riv.d.ts b/example/assets/rive/fallback_fonts.riv.d.ts
index dfbf4cb5..62daec5d 100644
--- a/example/assets/rive/fallback_fonts.riv.d.ts
+++ b/example/assets/rive/fallback_fonts.riv.d.ts
@@ -3,6 +3,7 @@
// Source: fallback_fonts.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -10,5 +11,6 @@ declare const asset: RiveAsset<{
};
enums: {};
viewModels: {};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/finished_trigger.riv.d.ts b/example/assets/rive/finished_trigger.riv.d.ts
index b1b61298..76fc5161 100644
--- a/example/assets/rive/finished_trigger.riv.d.ts
+++ b/example/assets/rive/finished_trigger.riv.d.ts
@@ -3,6 +3,7 @@
// Source: finished_trigger.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
finished: 'trigger';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/font_fallback.riv.d.ts b/example/assets/rive/font_fallback.riv.d.ts
index c7df2170..26892c14 100644
--- a/example/assets/rive/font_fallback.riv.d.ts
+++ b/example/assets/rive/font_fallback.riv.d.ts
@@ -3,6 +3,7 @@
// Source: font_fallback.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
text: 'string';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/hello_world_text.riv.d.ts b/example/assets/rive/hello_world_text.riv.d.ts
index 2d6a1e5a..3bf20587 100644
--- a/example/assets/rive/hello_world_text.riv.d.ts
+++ b/example/assets/rive/hello_world_text.riv.d.ts
@@ -3,6 +3,7 @@
// Source: hello_world_text.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'New Artboard';
defaultArtboard: 'New Artboard';
stateMachines: {
@@ -10,5 +11,6 @@ declare const asset: RiveAsset<{
};
enums: {};
viewModels: {};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/inputglow.riv.d.ts b/example/assets/rive/inputglow.riv.d.ts
index 12ce5ed9..6b718751 100644
--- a/example/assets/rive/inputglow.riv.d.ts
+++ b/example/assets/rive/inputglow.riv.d.ts
@@ -3,6 +3,7 @@
// Source: inputglow.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Layout';
defaultArtboard: 'Layout';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
isFocused: 'boolean';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/ios_android_layouts_demo_v01.riv.d.ts b/example/assets/rive/ios_android_layouts_demo_v01.riv.d.ts
index 3e3bc792..0fd8c4eb 100644
--- a/example/assets/rive/ios_android_layouts_demo_v01.riv.d.ts
+++ b/example/assets/rive/ios_android_layouts_demo_v01.riv.d.ts
@@ -3,6 +3,7 @@
// Source: ios_android_layouts_demo_v01.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'iOS_Android_Layouts_demo_v01';
defaultArtboard: 'iOS_Android_Layouts_demo_v01';
stateMachines: {
@@ -10,5 +11,6 @@ declare const asset: RiveAsset<{
};
enums: {};
viewModels: {};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/layout_test.riv.d.ts b/example/assets/rive/layout_test.riv.d.ts
index dc5b67de..7981af24 100644
--- a/example/assets/rive/layout_test.riv.d.ts
+++ b/example/assets/rive/layout_test.riv.d.ts
@@ -3,6 +3,7 @@
// Source: layout_test.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/layouts_demo.riv.d.ts b/example/assets/rive/layouts_demo.riv.d.ts
index 9443f4e1..6cbcbe4f 100644
--- a/example/assets/rive/layouts_demo.riv.d.ts
+++ b/example/assets/rive/layouts_demo.riv.d.ts
@@ -3,6 +3,7 @@
// Source: layouts_demo.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'iOS_Android_Layouts_demo_v01';
defaultArtboard: 'iOS_Android_Layouts_demo_v01';
stateMachines: {
@@ -10,5 +11,6 @@ declare const asset: RiveAsset<{
};
enums: {};
viewModels: {};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/many_viewmodels.riv.d.ts b/example/assets/rive/many_viewmodels.riv.d.ts
index bf0191f9..4569d6a9 100644
--- a/example/assets/rive/many_viewmodels.riv.d.ts
+++ b/example/assets/rive/many_viewmodels.riv.d.ts
@@ -3,6 +3,7 @@
// Source: many_viewmodels.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -11,10 +12,11 @@ declare const asset: RiveAsset<{
enums: {};
viewModels: {
First: {
- imageValue: 'image';
+ imageValue: 'assetImage';
colorValue: 'color';
textValue: 'string';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/movecircle.riv.d.ts b/example/assets/rive/movecircle.riv.d.ts
index b156f66e..075c1cc7 100644
--- a/example/assets/rive/movecircle.riv.d.ts
+++ b/example/assets/rive/movecircle.riv.d.ts
@@ -3,6 +3,7 @@
// Source: movecircle.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
posY: 'number';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/nodefaultbouncing.riv.d.ts b/example/assets/rive/nodefaultbouncing.riv.d.ts
index 63856505..de3df2ba 100644
--- a/example/assets/rive/nodefaultbouncing.riv.d.ts
+++ b/example/assets/rive/nodefaultbouncing.riv.d.ts
@@ -3,6 +3,7 @@
// Source: nodefaultbouncing.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -14,5 +15,6 @@ declare const asset: RiveAsset<{
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/on_entry_test.riv.d.ts b/example/assets/rive/on_entry_test.riv.d.ts
index 93f933ea..8d386cee 100644
--- a/example/assets/rive/on_entry_test.riv.d.ts
+++ b/example/assets/rive/on_entry_test.riv.d.ts
@@ -3,6 +3,7 @@
// Source: on_entry_test.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -15,5 +16,8 @@ declare const asset: RiveAsset<{
inputOnEntry: 'number';
};
};
+ referencedAssets: {
+ 'Inter-4228759': 'font';
+ };
}>;
export default asset;
diff --git a/example/assets/rive/out_of_band.riv.d.ts b/example/assets/rive/out_of_band.riv.d.ts
index 766bc851..8b49379c 100644
--- a/example/assets/rive/out_of_band.riv.d.ts
+++ b/example/assets/rive/out_of_band.riv.d.ts
@@ -3,6 +3,7 @@
// Source: out_of_band.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -10,5 +11,13 @@ declare const asset: RiveAsset<{
};
enums: {};
viewModels: {};
+ referencedAssets: {
+ 'Inter-594377': 'font';
+ 'referenced-image-2929282': 'image';
+ 'referenced_audio-2929340': 'audio';
+ 'hosted_audio-2989208': 'audio';
+ 'Damion-593587': 'font';
+ 'cdn-image-2989123': 'image';
+ };
}>;
export default asset;
diff --git a/example/assets/rive/quick_start.riv.d.ts b/example/assets/rive/quick_start.riv.d.ts
index ac069c38..3402de57 100644
--- a/example/assets/rive/quick_start.riv.d.ts
+++ b/example/assets/rive/quick_start.riv.d.ts
@@ -3,6 +3,7 @@
// Source: quick_start.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'health_bar_v01';
defaultArtboard: 'health_bar_v01';
stateMachines: {
@@ -18,5 +19,6 @@ declare const asset: RiveAsset<{
health: 'number';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/rating.riv.d.ts b/example/assets/rive/rating.riv.d.ts
index 7b7c9c4a..86173763 100644
--- a/example/assets/rive/rating.riv.d.ts
+++ b/example/assets/rive/rating.riv.d.ts
@@ -3,6 +3,7 @@
// Source: rating.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'New Artboard';
defaultArtboard: 'New Artboard';
stateMachines: {
@@ -10,5 +11,6 @@ declare const asset: RiveAsset<{
};
enums: {};
viewModels: {};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/rewards.riv.d.ts b/example/assets/rive/rewards.riv.d.ts
index c90a9cfa..ad339e67 100644
--- a/example/assets/rive/rewards.riv.d.ts
+++ b/example/assets/rive/rewards.riv.d.ts
@@ -3,6 +3,7 @@
// Source: rewards.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Main' | 'Lives 2' | 'Item Value' | 'Chest' | 'Button' | 'Heart' | 'Item';
defaultArtboard: 'Main';
stateMachines: {
@@ -50,5 +51,6 @@ declare const asset: RiveAsset<{
Item_Selection: 'enum:Item_Selection';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/style_fallback_fonts.riv.d.ts b/example/assets/rive/style_fallback_fonts.riv.d.ts
index fbffcf2e..b4a55589 100644
--- a/example/assets/rive/style_fallback_fonts.riv.d.ts
+++ b/example/assets/rive/style_fallback_fonts.riv.d.ts
@@ -3,6 +3,7 @@
// Source: style_fallback_fonts.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -10,5 +11,6 @@ declare const asset: RiveAsset<{
};
enums: {};
viewModels: {};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/tabtest.riv.d.ts b/example/assets/rive/tabtest.riv.d.ts
index e2a706d9..13ad33dc 100644
--- a/example/assets/rive/tabtest.riv.d.ts
+++ b/example/assets/rive/tabtest.riv.d.ts
@@ -3,6 +3,7 @@
// Source: tabtest.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -16,5 +17,6 @@ declare const asset: RiveAsset<{
enumProperty: 'enum:Selected';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/viewmodelproperty.riv.d.ts b/example/assets/rive/viewmodelproperty.riv.d.ts
index e7398f8d..05def55f 100644
--- a/example/assets/rive/viewmodelproperty.riv.d.ts
+++ b/example/assets/rive/viewmodelproperty.riv.d.ts
@@ -3,6 +3,7 @@
// Source: viewmodelproperty.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -18,5 +19,6 @@ declare const asset: RiveAsset<{
name: 'string';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/assets/rive/vm_value_change_test.riv.d.ts b/example/assets/rive/vm_value_change_test.riv.d.ts
index 62ee2ff1..0bdc7283 100644
--- a/example/assets/rive/vm_value_change_test.riv.d.ts
+++ b/example/assets/rive/vm_value_change_test.riv.d.ts
@@ -3,6 +3,7 @@
// Source: vm_value_change_test.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
@@ -15,5 +16,6 @@ declare const asset: RiveAsset<{
textValue: 'string';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/example/src/exercisers/OutOfBandAssets.tsx b/example/src/exercisers/OutOfBandAssets.tsx
index 0ad9d93d..8b72eaea 100644
--- a/example/src/exercisers/OutOfBandAssets.tsx
+++ b/example/src/exercisers/OutOfBandAssets.tsx
@@ -13,30 +13,30 @@ import {
} from '@rive-app/react-native';
import { Picker } from '@react-native-picker/picker';
import { type Metadata } from '../shared/metadata';
+// Typed via the generated out_of_band.riv.d.ts — the referencedAssets keys
+// and each entry's `type` below are checked against the file's actual assets.
+import outOfBandRiv from '../../assets/rive/out_of_band.riv';
export default function OutOfBandAssetsExample() {
const [uri, setUri] = React.useState('https://picsum.photos/id/372/500/500');
- const { riveFile, isLoading, error } = useRiveFile(
- require('../../assets/rive/out_of_band.riv'),
- {
- referencedAssets: {
- 'Inter-594377': {
- source: require('../../assets/fonts/Inter-594377.ttf'),
- type: 'font',
- },
- 'referenced-image-2929282': {
- source: {
- uri: uri,
- },
- type: 'image',
- },
- 'referenced_audio-2929340': {
- source: require('../../assets/audio/referenced_audio-2929340.wav'),
- type: 'audio',
+ const { riveFile, isLoading, error } = useRiveFile(outOfBandRiv, {
+ referencedAssets: {
+ 'Inter-594377': {
+ source: require('../../assets/fonts/Inter-594377.ttf'),
+ type: 'font',
+ },
+ 'referenced-image-2929282': {
+ source: {
+ uri: uri,
},
+ type: 'image',
},
- }
- );
+ 'referenced_audio-2929340': {
+ source: require('../../assets/audio/referenced_audio-2929340.wav'),
+ type: 'audio',
+ },
+ },
+ });
if (isLoading) {
return ;
diff --git a/expo-example/assets/rive/rating.riv.d.ts b/expo-example/assets/rive/rating.riv.d.ts
index 7b7c9c4a..86173763 100644
--- a/expo-example/assets/rive/rating.riv.d.ts
+++ b/expo-example/assets/rive/rating.riv.d.ts
@@ -3,6 +3,7 @@
// Source: rating.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'New Artboard';
defaultArtboard: 'New Artboard';
stateMachines: {
@@ -10,5 +11,6 @@ declare const asset: RiveAsset<{
};
enums: {};
viewModels: {};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/expo-example/assets/rive/rewards_source.riv.d.ts b/expo-example/assets/rive/rewards_source.riv.d.ts
index ae121d9c..44645991 100644
--- a/expo-example/assets/rive/rewards_source.riv.d.ts
+++ b/expo-example/assets/rive/rewards_source.riv.d.ts
@@ -3,6 +3,7 @@
// Source: rewards_source.riv
import type { RiveAsset } from '@rive-app/react-native';
declare const asset: RiveAsset<{
+ schemaVersion: 1;
artboards: 'Main' | 'Lives 2' | 'Item Value' | 'Chest' | 'Button' | 'Heart' | 'Item';
defaultArtboard: 'Main';
stateMachines: {
@@ -50,5 +51,6 @@ declare const asset: RiveAsset<{
Item_Selection: 'enum:Item_Selection';
};
};
+ referencedAssets: {};
}>;
export default asset;
diff --git a/scripts/__tests__/rive-extract-schema.test.ts b/scripts/__tests__/rive-extract-schema.test.ts
index d9aec246..2c20659b 100644
--- a/scripts/__tests__/rive-extract-schema.test.ts
+++ b/scripts/__tests__/rive-extract-schema.test.ts
@@ -54,6 +54,24 @@ describe('rive-extract-schema', () => {
expect(vms.Energy_Bar!.Bar_Color).toBe('color');
});
+ test('extracts non-embedded assets by uniqueName and kind', () => {
+ const oob = extract(
+ resolve(import.meta.dirname, '../../example/assets/rive/out_of_band.riv')
+ );
+ expect(oob.referencedAssets).toEqual({
+ 'Inter-594377': 'font',
+ 'referenced-image-2929282': 'image',
+ 'referenced_audio-2929340': 'audio',
+ 'hosted_audio-2989208': 'audio',
+ 'Damion-593587': 'font',
+ 'cdn-image-2989123': 'image',
+ });
+ });
+
+ test('embedded-only files report no referenced assets', () => {
+ expect(schema.referencedAssets).toEqual({});
+ });
+
test('enum properties reference a file-level enum definition', () => {
const databinding = extract(
resolve(import.meta.dirname, '../../example/assets/rive/databinding.riv')
diff --git a/scripts/__tests__/rive-gen-emit.test.ts b/scripts/__tests__/rive-gen-emit.test.ts
index 6920218d..7c8fddb3 100644
--- a/scripts/__tests__/rive-gen-emit.test.ts
+++ b/scripts/__tests__/rive-gen-emit.test.ts
@@ -7,7 +7,10 @@ import {
unionRecord,
vmRecord,
schemaBody,
+ assetsRecord,
+ classifyAsset,
enumPropTypeString,
+ propertyTypeString,
collectEnums,
viewModelRefTypeString,
type Schema,
@@ -50,6 +53,7 @@ describe('emit escaping', () => {
viewModels: {
"It's VM": { "quote'": "enum:Pet's" },
},
+ referencedAssets: { "Font's-123": 'font' },
};
const body = schemaBody(schema);
expect(parseErrors(`declare const asset: {\n${body}\n};`)).toEqual([]);
@@ -84,10 +88,21 @@ describe('schemaBody', () => {
stateMachines: { Main: ['SM'] },
enums: {},
viewModels: {},
+ referencedAssets: {},
};
- test('always emits viewModels, empty object when none', () => {
+ test('always emits viewModels and referencedAssets, empty objects when none', () => {
expect(schemaBody(base)).toContain('viewModels: {};');
+ expect(schemaBody(base)).toContain('referencedAssets: {};');
+ });
+
+ test('emits referencedAssets record when present', () => {
+ const body = schemaBody({
+ ...base,
+ referencedAssets: { 'Inter-594377': 'font', 'img-1': 'image' },
+ });
+ expect(body).toContain("'Inter-594377': 'font';");
+ expect(body).toContain("'img-1': 'image';");
});
test('always emits enums, empty object when none', () => {
@@ -193,3 +208,60 @@ describe('viewModelRefTypeString', () => {
expect(viewModelRefTypeString(inst, 'Coin')).toBe('viewModel');
});
});
+
+describe('classifyAsset', () => {
+ const font = {
+ name: 'Inter',
+ uniqueFilename: 'Inter-594377.ttf',
+ fileExtension: 'ttf',
+ isFont: true,
+ };
+
+ test('referenced asset → unique id (extension stripped) + kind', () => {
+ expect(classifyAsset(font, 0)).toEqual({
+ id: 'Inter-594377',
+ kind: 'font',
+ });
+ expect(
+ classifyAsset(
+ { uniqueFilename: 'pic-1.png', fileExtension: 'png', isImage: true },
+ 0
+ )
+ ).toEqual({ id: 'pic-1', kind: 'image' });
+ expect(
+ classifyAsset(
+ { uniqueFilename: 'a-2.wav', fileExtension: 'wav', isAudio: true },
+ 0
+ )
+ ).toEqual({ id: 'a-2', kind: 'audio' });
+ });
+
+ test('embedded assets are excluded', () => {
+ expect(classifyAsset(font, 8680)).toBeNull();
+ });
+
+ test('unknown asset kinds are excluded', () => {
+ expect(classifyAsset({ uniqueFilename: 'x-1.bin' }, 0)).toBeNull();
+ });
+
+ test('falls back to name when uniqueFilename is missing', () => {
+ expect(classifyAsset({ name: 'Inter', isFont: true }, 0)).toEqual({
+ id: 'Inter',
+ kind: 'font',
+ });
+ });
+});
+
+describe('assetsRecord', () => {
+ test('escapes hostile identifiers', () => {
+ expect(assetsRecord({ "It's-1": 'font' })).toBe(" 'It\\'s-1': 'font';");
+ });
+});
+
+describe('propertyTypeString', () => {
+ test("names image properties 'assetImage', passes the rest through", () => {
+ expect(propertyTypeString('image')).toBe('assetImage');
+ expect(propertyTypeString('number')).toBe('number');
+ expect(propertyTypeString('artboard')).toBe('artboard');
+ });
+});
diff --git a/scripts/__tests__/rive-gen-fuzz.test.ts b/scripts/__tests__/rive-gen-fuzz.test.ts
index fba72d08..caee15ed 100644
Binary files a/scripts/__tests__/rive-gen-fuzz.test.ts and b/scripts/__tests__/rive-gen-fuzz.test.ts differ
diff --git a/scripts/rive-extract-schema.ts b/scripts/rive-extract-schema.ts
index ed183709..10214557 100644
--- a/scripts/rive-extract-schema.ts
+++ b/scripts/rive-extract-schema.ts
@@ -1,143 +1,31 @@
/**
- * Extracts artboard names and state machines from a .riv file and prints JSON to stdout.
+ * Prints the schema of a .riv file as JSON: artboards, state machines, enums,
+ * view models and referenced assets — the same data rive-gen-types emits as
+ * a .riv.d.ts.
*
* Usage:
* node scripts/rive-extract-schema.ts path/to/file.riv
* node scripts/rive-extract-schema.ts https://example.com/file.riv
- *
- * Output: { "artboards": ["Main", "Intro"], "stateMachines": { "Main": ["State Machine 1"], "Intro": ["Idle"] } }
*/
-import { readFileSync } from 'fs';
-// Default-import + destructure: @rive-app/canvas is CJS, and Node's ESM
-// loader cannot statically see its named exports (bun's interop can).
-import riveCanvas from '@rive-app/canvas';
-const { RuntimeLoader } = riveCanvas;
-import {
- collectEnums,
- enumPropTypeString,
- nameMap,
- viewModelRefTypeString,
- type RuntimeProperty,
-} from './rive-gen-types.ts';
+import { extractSchema, setupWasmShims } from './rive-gen-types.ts';
-// noUncheckedIndexedAccess: process.argv destructuring yields string | undefined
const input: string | undefined = process.argv[2];
-// @rive-app/canvas checks for browser globals during WASM initialisation.
-// We only call file-inspection APIs (no rendering), so minimal shims are enough.
-(globalThis as any).document = {
- createElement: () => ({ getContext: () => null }),
-};
-(globalThis as any).Image = class {};
-
-// The WASM runtime prints warnings (e.g. "No WebGL support") via console.
-// Redirect them to stderr so our JSON output stays clean.
-console.log = (...args: unknown[]) =>
- process.stderr.write(args.join(' ') + '\n');
-console.warn = (...args: unknown[]) =>
- process.stderr.write(args.join(' ') + '\n');
-
-// Catch errors from microtasks/unhandled rejections that would otherwise cause
-// a silent exit with code 0 on some Bun versions (e.g. when img.la() throws in
-// a queueMicrotask callback because WebGL is unavailable on Linux CI).
-process.on('uncaughtException', (err) => {
- process.stderr.write(`uncaughtException: ${err.stack ?? err.message}\n`);
- process.exit(1);
-});
-process.on('unhandledRejection', (reason) => {
- process.stderr.write(`unhandledRejection: ${reason}\n`);
- process.exit(1);
-});
-
if (!input) {
- process.stderr.write('Usage: bun rive-extract-schema.ts \n');
+ process.stderr.write('Usage: node rive-extract-schema.ts \n');
process.exit(1);
}
-const source: string = input;
-
-async function loadBytes(source: string): Promise {
- if (source.startsWith('http://') || source.startsWith('https://')) {
- const res = await fetch(source);
- if (!res.ok) throw new Error(`HTTP ${res.status}: ${source}`);
- return new Uint8Array(await res.arrayBuffer());
- }
- return new Uint8Array(readFileSync(source));
-}
-
-async function main() {
- const bytes = await loadBytes(source);
- const runtime = await RuntimeLoader.awaitInstance();
- // Claim every referenced asset without decoding it. We only introspect
- // names/schemas — decoding (images especially) goes through render paths
- // that stall load() forever without WebGL; a pending load() then drains the
- // event loop and the process exits 0 without output.
- const assetLoader = new (runtime as any).CustomFileAssetLoader({
- loadContents: () => true,
+// Also routes the WASM runtime's console output to stderr, keeping stdout
+// pure JSON.
+setupWasmShims();
+
+extractSchema(input)
+ .then((schema) => {
+ process.stdout.write(JSON.stringify(schema, null, 2) + '\n');
+ })
+ .catch((err: Error) => {
+ process.stderr.write(err.message + '\n');
+ process.exit(1);
});
-
- let timer: ReturnType | undefined;
- const riveFile = await Promise.race([
- runtime.load(bytes, assetLoader, false),
- new Promise((_, reject) => {
- timer = setTimeout(
- () => reject(new Error('load() timed out after 30000ms')),
- 30_000
- );
- }),
- ]).finally(() => clearTimeout(timer));
-
- // load() resolves null (rather than rejecting) for unparseable bytes.
- if (!riveFile) {
- throw new Error('not a valid .riv file (load() returned null)');
- }
-
- const artboards: string[] = [];
- const stateMachines = nameMap();
- for (let i = 0; i < riveFile.artboardCount(); i++) {
- const artboard = riveFile.artboardByIndex(i);
- artboards.push(artboard.name);
- const sms: string[] = [];
- for (let j = 0; j < artboard.stateMachineCount(); j++) {
- sms.push(artboard.stateMachineByIndex(j).name);
- }
- stateMachines[artboard.name] = sms;
- }
-
- const enums = collectEnums(riveFile);
-
- const viewModels = nameMap>();
- const vmCount = (riveFile as any).viewModelCount() as number;
- for (let i = 0; i < vmCount; i++) {
- const vm = (riveFile as any).viewModelByIndex(i);
- const properties = vm.getProperties() as RuntimeProperty[];
- // Create a blank instance to resolve viewModel property references
- const inst = vm.instance?.() as any;
- const props = nameMap();
- for (const p of properties) {
- if (p.type === 'viewModel') {
- props[p.name] = viewModelRefTypeString(inst, p.name);
- } else if (p.type === 'enumType') {
- props[p.name] = enumPropTypeString(p, enums);
- } else {
- props[p.name] = p.type;
- }
- }
- viewModels[vm.name] = props;
- }
-
- const defaultArtboard = artboards[0] ?? '';
- process.stdout.write(
- JSON.stringify(
- { artboards, defaultArtboard, stateMachines, enums, viewModels },
- null,
- 2
- ) + '\n'
- );
-}
-
-main().catch((err: Error) => {
- process.stderr.write(err.message + '\n');
- process.exit(1);
-});
diff --git a/scripts/rive-gen-types.ts b/scripts/rive-gen-types.ts
index c9707978..78b25593 100644
--- a/scripts/rive-gen-types.ts
+++ b/scripts/rive-gen-types.ts
@@ -28,7 +28,7 @@ import { pathToFileURL } from 'url';
// Called from main() so that importing this module (for unit-testing the
// exported emit helpers) has no global side effects.
-function setupWasmShims(): void {
+export function setupWasmShims(): void {
// Browser shims required by the @rive-app/canvas WASM runtime.
(globalThis as any).document = {
createElement: () => ({ getContext: () => null }),
@@ -49,6 +49,45 @@ export interface Schema {
/** File-level enum definitions, name → values. */
enums: Record;
viewModels: Record>;
+ /** Non-embedded (referenced or hosted) assets: runtime `uniqueName` → kind. */
+ referencedAssets: Record;
+}
+
+/**
+ * Classify a file asset reported by the WASM asset loader. Only non-embedded
+ * assets (referenced or hosted) are schema-relevant: they are the ones an
+ * app must supply via `referencedAssets` — the new runtime does not fetch
+ * hosted assets itself. The key is the asset's `uniqueName` (`uniqueFilename`
+ * minus extension, e.g. 'Inter-594377'), the recommended `referencedAssets`
+ * key.
+ */
+export function classifyAsset(
+ asset: {
+ name?: string;
+ uniqueFilename?: string;
+ fileExtension?: string;
+ isImage?: boolean;
+ isFont?: boolean;
+ isAudio?: boolean;
+ },
+ embeddedByteCount: number
+): { id: string; kind: string } | null {
+ if (embeddedByteCount > 0) return null;
+ const kind = asset.isImage
+ ? 'image'
+ : asset.isFont
+ ? 'font'
+ : asset.isAudio
+ ? 'audio'
+ : null;
+ if (!kind) return null;
+ const unique = asset.uniqueFilename ?? '';
+ const ext = asset.fileExtension ?? '';
+ const id =
+ ext && unique.endsWith(`.${ext}`)
+ ? unique.slice(0, -(ext.length + 1))
+ : unique || (asset.name ?? '');
+ return id ? { id, kind } : null;
}
export interface RuntimeProperty {
@@ -87,7 +126,7 @@ async function getRuntime(): Promise {
/** Per-file guard: a stalled WASM load() must fail loudly, never hang the batch. */
const LOAD_TIMEOUT_MS = 30_000;
-async function extractSchema(input: string): Promise {
+export async function extractSchema(input: string): Promise {
let bytes: Uint8Array;
if (input.startsWith('http://') || input.startsWith('https://')) {
const res = await fetch(input);
@@ -103,9 +142,7 @@ async function extractSchema(input: string): Promise {
// names/schemas — decoding (images especially) goes through render paths
// that stall load() forever without WebGL, silently truncating the batch:
// a pending load() drains bun's event loop and the process exits 0.
- const assetLoader = new (runtime as any).CustomFileAssetLoader({
- loadContents: () => true,
- });
+ const { assetLoader, assets } = createAssetCollector(runtime);
let timer: ReturnType | undefined;
const riveFile = await Promise.race([
@@ -150,7 +187,7 @@ async function extractSchema(input: string): Promise {
} else if (p.type === 'enumType') {
props[p.name] = enumPropTypeString(p, enums);
} else {
- props[p.name] = p.type;
+ props[p.name] = propertyTypeString(p.type);
}
}
viewModels[vm.name] = props;
@@ -162,6 +199,7 @@ async function extractSchema(input: string): Promise {
stateMachines,
enums,
viewModels,
+ referencedAssets: assets,
};
}
@@ -209,23 +247,59 @@ export function vmRecord(
.join('\n');
}
+/**
+ * Asset loader that claims every asset without decoding it and records the
+ * non-embedded ones — load() callbacks are the only place asset metadata is
+ * visible.
+ */
+export function createAssetCollector(runtime: any): {
+ assetLoader: any;
+ assets: Record;
+} {
+ const assets = nameMap();
+ const assetLoader = new runtime.CustomFileAssetLoader({
+ loadContents: (asset: any, embeddedBytes: Uint8Array | undefined) => {
+ const classified = classifyAsset(asset ?? {}, embeddedBytes?.length ?? 0);
+ if (classified) assets[classified.id] = classified.kind;
+ return true;
+ },
+ });
+ return { assetLoader, assets };
+}
+
+export function assetsRecord(assets: Record): string {
+ const keys = Object.keys(assets);
+ const force = keys.some(needsQuote);
+ return Object.entries(assets)
+ .map(([id, kind]) => ` ${quoteKey(id, force)}: ${strLit(kind)};`)
+ .join('\n');
+}
+
+/** Must match `RiveFileSchema['schemaVersion']` in src/core/TypedRiveFile.ts. */
+export const SCHEMA_VERSION = 1;
+
export function schemaBody(schema: Schema): string {
const enumSection =
Object.keys(schema.enums).length > 0
? `\n enums: {\n${unionRecord(schema.enums)}\n };`
: '\n enums: {};';
- // Always emit viewModels — omitting it would fail the RiveFileSchema
- // constraint and silently degrade the whole asset to untyped.
+ // Always emit viewModels/referencedAssets — omitting either fails the
+ // RiveFileSchema check, and the import turns into an "out of date" error.
const vmSection =
Object.keys(schema.viewModels).length > 0
? `\n viewModels: {\n${vmRecord(schema.viewModels)}\n };`
: '\n viewModels: {};';
+ const assetSection =
+ Object.keys(schema.referencedAssets).length > 0
+ ? `\n referencedAssets: {\n${assetsRecord(schema.referencedAssets)}\n };`
+ : '\n referencedAssets: {};';
return `\
+ schemaVersion: ${SCHEMA_VERSION};
artboards: ${schema.artboards.map(strLit).join(' | ')};
defaultArtboard: ${strLit(schema.defaultArtboard)};
stateMachines: {
${unionRecord(schema.stateMachines)}
- };${enumSection}${vmSection}`;
+ };${enumSection}${vmSection}${assetSection}`;
}
function dtsContent(input: string, schema: Schema): string {
@@ -344,6 +418,16 @@ export function collectEnums(riveFile: any): Record {
return enums;
}
+/**
+ * Schema type string for a primitive property. The web runtime reports image
+ * properties as `'image'`; the schema uses `'assetImage'`, matching RML's
+ * `ViewModelPropertyAssetImage` and the native `ViewModelPropertyType`, and
+ * keeping it distinct from the `'image'` referenced-asset kind.
+ */
+export function propertyTypeString(runtimeType: string): string {
+ return runtimeType === 'image' ? 'assetImage' : runtimeType;
+}
+
/**
* Schema type string for an enum property: a reference to the file-level
* enum it uses (`'enum:Pets'`), or untyped `'enum'` when the runtime reports
diff --git a/src/__tests__/typed-rive.test-d.ts b/src/__tests__/typed-rive.test-d.ts
index 807d251a..51e7c3a5 100644
--- a/src/__tests__/typed-rive.test-d.ts
+++ b/src/__tests__/typed-rive.test-d.ts
@@ -26,10 +26,15 @@ import { useRiveNumber } from '../../src/hooks/useRiveNumber';
import { useRiveEnum } from '../../src/hooks/useRiveEnum';
import { useViewModelInstance } from '../../src/hooks/useViewModelInstance';
import { RiveView, type RiveViewProps } from '../../src/core/RiveView';
+import { useRiveFile } from '../../src/hooks/useRiveFile';
+import { RiveFileFactory } from '../../src/core/RiveFile';
+import type { RiveImage } from '../../src/specs/RiveImage.nitro';
import gradientBorderRiv from '../../example/assets/rive/GradientBorder.riv';
+import outOfBandRiv from '../../example/assets/rive/out_of_band.riv';
import blinkoRiv from '../../example/assets/rive/blinko.riv';
import rewardsRiv from '../../example/assets/rive/rewards.riv';
import fallbackFontsRiv from '../../example/assets/rive/fallback_fonts.riv';
+import databindingImagesRiv from '../../example/assets/rive/databinding_images.riv';
// Infer schemas from the generated .riv.d.ts assets
type GradientBorderSchema = typeof gradientBorderRiv extends RiveAsset
@@ -262,20 +267,24 @@ expectError(null as unknown as EnumValues);
// An untyped 'enum' property (a built-in enum) accepts any string.
type BuiltInEnumSchema = {
+ schemaVersion: 1;
artboards: 'Main';
defaultArtboard: 'Main';
stateMachines: { Main: 'SM' };
enums: {};
viewModels: { VM: { blend: 'enum' } };
+ referencedAssets: {};
};
declare const builtInVM: TypedViewModelInstance;
expectType>(useRiveEnum('blend', builtInVM));
// A named reference missing from `enums` is never — not the enum's name.
type DanglingSchema = {
+ schemaVersion: 1;
artboards: 'Main';
defaultArtboard: 'Main';
stateMachines: { Main: 'SM' };
+ referencedAssets: {};
enums: {};
viewModels: { VM: { pet: 'enum:Pets' } };
};
@@ -283,9 +292,11 @@ expectType(null as unknown as EnumValuesOf);
// An enum with no values degrades to an untyped (string) property.
type EmptyEnumSchema = {
+ schemaVersion: 1;
artboards: 'Main';
defaultArtboard: 'Main';
stateMachines: { Main: 'SM' };
+ referencedAssets: {};
enums: { E: never };
viewModels: { VM: { pet: 'enum:E' } };
};
@@ -495,3 +506,139 @@ expectAssignable(
rewardsVM.numberProperty('Item_Value_Icon/Item_Value')
);
expectError(rewardsVM.numberProperty('Item_Value_Icon/DoesNotExist'));
+
+
+// ============================================================
+// Typed referencedAssets (out-of-band assets from the schema)
+// ============================================================
+
+declare const riveImage: RiveImage;
+
+// Valid keys with correct types compile; RiveImage allowed for image assets
+useRiveFile(outOfBandRiv, {
+ referencedAssets: {
+ 'Inter-594377': { source: 1, type: 'font' },
+ 'referenced-image-2929282': { source: { uri: 'https://x/i.png' } },
+ 'hosted_audio-2989208': { source: 2, type: 'audio' },
+ },
+});
+useRiveFile(outOfBandRiv, {
+ referencedAssets: { 'cdn-image-2989123': riveImage },
+});
+
+// Unknown asset key is a hard error (must not fall through to the untyped
+// number-input signature)
+expectError(
+ useRiveFile(outOfBandRiv, {
+ referencedAssets: { 'Inter-59437': { source: 1, type: 'font' } },
+ })
+);
+
+// Declared type must match the asset's actual kind
+expectError(
+ useRiveFile(outOfBandRiv, {
+ referencedAssets: { 'Inter-594377': { source: 1, type: 'image' } },
+ })
+);
+
+// RiveImage objects are only accepted for image assets
+expectError(
+ useRiveFile(outOfBandRiv, {
+ referencedAssets: { 'Inter-594377': riveImage },
+ })
+);
+
+// A schema with no referenced assets accepts no keys at all
+expectError(
+ useRiveFile(rewardsRiv, {
+ referencedAssets: { anything: { source: 1, type: 'font' } },
+ })
+);
+
+// Untyped inputs keep accepting arbitrary keys (backward compat) — every
+// legacy input form: url string, uri object, require() (any), plain number.
+useRiveFile('https://example.com/a.riv', {
+ referencedAssets: { anything: { source: 1, type: 'font' } },
+});
+useRiveFile({ uri: 'file:///a.riv' }, {
+ referencedAssets: { whatever: { source: 1 } },
+});
+declare const requireResult: any;
+useRiveFile(requireResult, {
+ referencedAssets: { anything: { source: 1, type: 'font' } },
+});
+declare const plainAssetId: number;
+useRiveFile(plainAssetId, {
+ referencedAssets: { anything: { source: 1, type: 'audio' } },
+});
+
+// RiveFileFactory.fromSource applies the same checks (resolved asset form)
+// instead of falling through to an untyped overload.
+expectError(
+ RiveFileFactory.fromSource(outOfBandRiv, {
+ 'Inter-59437': { sourceUrl: 'https://x/f.ttf' },
+ })
+);
+expectError(
+ RiveFileFactory.fromSource(outOfBandRiv, {
+ 'Inter-594377': { sourceUrl: 'https://x/f.ttf', type: 'image' },
+ })
+);
+expectError(
+ RiveFileFactory.fromSource(outOfBandRiv, {
+ 'Inter-594377': { image: riveImage },
+ })
+);
+expectError(
+ RiveFileFactory.fromSource(rewardsRiv, {
+ anything: { sourceUrl: 'https://x/f.ttf' },
+ })
+);
+expectType>>(
+ RiveFileFactory.fromSource(outOfBandRiv, {
+ 'Inter-594377': { sourceUrl: 'https://x/f.ttf', type: 'font' },
+ 'cdn-image-2989123': { image: riveImage },
+ })
+);
+RiveFileFactory.fromSource(plainAssetId, { anything: { sourceUrl: 'x' } });
+RiveFileFactory.fromSource({ uri: 'https://x/a.riv' }, { anything: {} });
+
+// A standalone hand-written schema (literal ViewModels, string artboards)
+// keeps its ViewModel typing through useRiveFile.
+type HandWrittenSchema = {
+ schemaVersion: 1;
+ artboards: string;
+ defaultArtboard: string;
+ stateMachines: Record;
+ enums: {};
+ referencedAssets: {};
+ viewModels: { VM: { n: 'number' } };
+};
+declare const handWrittenAsset: RiveAsset;
+{
+ const { riveFile } = useRiveFile(handWrittenAsset);
+ expectError(
+ useViewModelInstance(riveFile, { viewModelName: 'NotAVM', async: true })
+ );
+}
+
+// Image properties are 'assetImage' (RML ViewModelPropertyAssetImage), distinct
+// from the 'image' referenced-asset kind.
+type ImagesSchema =
+ typeof databindingImagesRiv extends RiveAsset ? T : never;
+declare const imagesVM: TypedViewModelInstance;
+imagesVM.imageProperty('bound_image');
+expectError(imagesVM.numberProperty('bound_image'));
+
+// A .riv.d.ts from an older generator (no or a different schemaVersion)
+// fails at the use site instead of silently turning untyped.
+declare const staleAsset: RiveAsset<{
+ artboards: 'Main';
+ defaultArtboard: 'Main';
+ stateMachines: { Main: 'SM' };
+ enums: {};
+ viewModels: {};
+ referencedAssets: {};
+}>;
+expectError(useRiveFile(staleAsset));
+expectError(RiveFileFactory.fromSource(staleAsset, undefined));
diff --git a/src/core/ReferencedAssets.ts b/src/core/ReferencedAssets.ts
index 48d11ea5..48033853 100644
--- a/src/core/ReferencedAssets.ts
+++ b/src/core/ReferencedAssets.ts
@@ -3,6 +3,7 @@ import type {
RiveAssetType,
} from '../specs/RiveFile.nitro';
import type { RiveImage } from '../specs/RiveImage.nitro';
+import type { RiveFileSchema } from './TypedRiveFile';
export type ReferencedAssetSource = {
source: number | { uri: string };
@@ -24,3 +25,58 @@ export interface ReferencedAssets {
export type ResolvedReferencedAssets = {
[assetName: string]: ResolvedReferencedAsset;
};
+
+type SchemaAssetKeys = Extract<
+ keyof S['referencedAssets'],
+ string
+>;
+
+/** Error-message literal: a `never` value would reject entries without saying why. */
+type NoReferencedAssets = Record<
+ string,
+ 'this .riv file has no referenced (out-of-band) assets'
+>;
+
+/** Untyped when the schema is unknown, rejecting when the file has no referenced assets. */
+type ForSchemaAssets =
+ string extends SchemaAssetKeys
+ ? Untyped
+ : [SchemaAssetKeys] extends [never]
+ ? NoReferencedAssets
+ : Typed;
+
+/**
+ * `referencedAssets` constrained to a generated schema: keys must be asset
+ * `uniqueName`s from the .riv file, and each entry's declared `type`
+ * must match the asset's actual kind. `RiveImage` objects are only accepted
+ * for image assets. Degrades to the untyped {@link ReferencedAssets} when the
+ * schema is not statically known.
+ */
+export type TypedReferencedAssets = ForSchemaAssets<
+ S,
+ ReferencedAssets,
+ {
+ [K in SchemaAssetKeys]?: S['referencedAssets'][K] extends 'image'
+ ? (ReferencedAssetSource & { type?: 'image' }) | RiveImage
+ : ReferencedAssetSource & {
+ type?: S['referencedAssets'][K] & RiveAssetType;
+ };
+ }
+>;
+
+/**
+ * Resolved (post-`Image.resolveAssetSource`) form of
+ * {@link TypedReferencedAssets}, used by the RiveFileFactory methods.
+ */
+export type TypedResolvedReferencedAssets =
+ ForSchemaAssets<
+ S,
+ ResolvedReferencedAssets,
+ {
+ [K in SchemaAssetKeys]?: S['referencedAssets'][K] extends 'image'
+ ? ResolvedReferencedAsset & { type?: 'image' }
+ : Omit & {
+ type?: S['referencedAssets'][K] & RiveAssetType;
+ };
+ }
+ >;
diff --git a/src/core/RiveFile.ts b/src/core/RiveFile.ts
index 75e114ae..a75f9c72 100644
--- a/src/core/RiveFile.ts
+++ b/src/core/RiveFile.ts
@@ -1,9 +1,16 @@
import { NitroModules } from 'react-native-nitro-modules';
import type { RiveFileFactory as RiveFileFactoryInternal } from '../specs/RiveFile.nitro';
-import type { RiveAsset, RiveFileSchema, TypedRiveFile } from './TypedRiveFile';
+import type {
+ RiveFileSchema,
+ TypedRiveFile,
+ SchemaBranded,
+} from './TypedRiveFile';
import { Image } from 'react-native';
-import type { ResolvedReferencedAssets } from './ReferencedAssets';
+import type {
+ ResolvedReferencedAssets,
+ TypedResolvedReferencedAssets,
+} from './ReferencedAssets';
const RiveFileInternal =
NitroModules.createHybridObject('RiveFileFactory');
@@ -154,21 +161,17 @@ export namespace RiveFileFactory {
* config.resolver.assetExts = [...config.resolver.assetExts, 'riv'];
* ```
*/
- export async function fromSource(
- source: RiveAsset,
- referencedAssets: ResolvedReferencedAssets | undefined,
- loadCdn?: boolean
- ): Promise>;
- export async function fromSource(
- source: number | { uri: string },
- referencedAssets: ResolvedReferencedAssets | undefined,
- loadCdn?: boolean
- ): Promise;
+ // Single generic signature for the same reason as useRiveFile: with a
+ // typed + untyped overload pair, a bad key on a typed asset falls through to
+ // the untyped overload instead of erroring.
export async function fromSource(
- source: number | { uri: string },
- referencedAssets: ResolvedReferencedAssets | undefined,
+ source: SchemaBranded | { uri: string },
+ typedReferencedAssets: TypedResolvedReferencedAssets | undefined,
loadCdn?: boolean
): Promise> {
+ const referencedAssets = typedReferencedAssets as
+ | ResolvedReferencedAssets
+ | undefined;
const assetID = typeof source === 'number' ? source : null;
const sourceURI = typeof source === 'object' ? source.uri : null;
diff --git a/src/core/TypedRiveFile.ts b/src/core/TypedRiveFile.ts
index e5f169a4..9464b8a0 100644
--- a/src/core/TypedRiveFile.ts
+++ b/src/core/TypedRiveFile.ts
@@ -9,12 +9,24 @@ import type { RiveFile } from '../specs/RiveFile.nitro';
* signatures make every artboard, view model and enum name valid.
*/
export interface RiveFileSchema {
+ /**
+ * Format version of generated schemas. Bumped on every breaking format
+ * change, so a `.riv.d.ts` from an older generator is reported as out of
+ * date instead of silently checking the wrong thing.
+ */
+ schemaVersion: 1;
artboards: string;
defaultArtboard: string;
stateMachines: Record;
/** File-level enum definitions, name → value union. */
enums: Record;
viewModels: Record>;
+ /**
+ * Non-embedded (referenced or hosted) assets that the app must supply via
+ * the `referencedAssets` option: the asset's runtime `uniqueName`
+ * (e.g. 'Inter-594377') → 'image' | 'font' | 'audio'.
+ */
+ referencedAssets: Record;
}
/**
@@ -26,9 +38,17 @@ export interface RiveFileSchema {
*
* Pass to `RiveFileFactory.fromSource(asset)` — TypeScript infers `T` automatically.
*/
-export type RiveAsset = number & {
- readonly __riveSchema?: T;
-};
+// An outdated schema resolves to an object keyed by the fix, so the message
+// shows up in the compiler error at the use site; `skipLibCheck` hides the
+// schema's own mismatch inside the `.d.ts`.
+export type RiveAsset = [T] extends [RiveFileSchema]
+ ? SchemaBranded
+ : {
+ readonly 'This .riv.d.ts is out of date: regenerate it with npx rive-gen-types': true;
+ };
+
+/** A Metro asset number carrying schema `T` as a phantom property. */
+export type SchemaBranded = number & { readonly __riveSchema?: T };
/**
* Extracts the RiveFileSchema from a RiveAsset, TypedRiveFile, or a bare RiveFileSchema.
diff --git a/src/core/TypedViewModelInstance.ts b/src/core/TypedViewModelInstance.ts
index a4a576d8..4dc44703 100644
--- a/src/core/TypedViewModelInstance.ts
+++ b/src/core/TypedViewModelInstance.ts
@@ -230,7 +230,7 @@ export interface TypedViewModelInstance<
| undefined;
imageProperty(
- path: PathsOfKind
+ path: PathsOfKind
): ViewModelImageProperty | undefined;
listProperty(
diff --git a/src/hooks/useRiveFile.ts b/src/hooks/useRiveFile.ts
index 77449754..ad4df84a 100644
--- a/src/hooks/useRiveFile.ts
+++ b/src/hooks/useRiveFile.ts
@@ -11,11 +11,12 @@ import type {
ReferencedAsset,
ReferencedAssets,
ResolvedReferencedAssets,
+ TypedReferencedAssets,
} from '../core/ReferencedAssets';
import type {
- RiveAsset,
RiveFileSchema,
TypedRiveFile,
+ SchemaBranded,
} from '../core/TypedRiveFile';
export type { ReferencedAssets, ResolvedReferencedAssets };
@@ -97,17 +98,25 @@ export type UseRiveFileResult =
| { riveFile: null; isLoading: false; error: Error }
| { riveFile: undefined; isLoading: true; error: null };
-export function useRiveFile(
- input: RiveAsset,
- options?: UseRiveFileOptions
-):
- | { riveFile: TypedRiveFile; isLoading: false; error: null }
- | { riveFile: null; isLoading: false; error: Error }
- | { riveFile: undefined; isLoading: true; error: null };
-export function useRiveFile(
- input: RiveFileInput | undefined,
- options?: UseRiveFileOptions
-): UseRiveFileResult;
+/**
+ * Result of {@link useRiveFile}: typed when the input is a generated
+ * RiveAsset, the plain untyped result otherwise.
+ */
+type UseRiveFileResultFor = RiveFileSchema extends T
+ ? UseRiveFileResult
+ :
+ | { riveFile: TypedRiveFile; isLoading: false; error: null }
+ | { riveFile: null; isLoading: false; error: Error }
+ | { riveFile: undefined; isLoading: true; error: null };
+
+// Single generic signature (not a typed + untyped overload pair): an invalid
+// referencedAssets key or type on a schema-typed asset must be a hard error
+// here — with separate overloads it would silently fall through to the
+// untyped one, because a RiveAsset is assignable to the plain number input.
+export function useRiveFile(
+ input: SchemaBranded | Exclude | undefined,
+ options?: { referencedAssets?: TypedReferencedAssets }
+): UseRiveFileResultFor;
export function useRiveFile(
input: RiveFileInput | undefined,
options: UseRiveFileOptions = {}
diff --git a/src/index.tsx b/src/index.tsx
index ff78476b..d04fb48d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -89,6 +89,10 @@ export {
type UseViewModelInstanceRequiredResult,
} from './hooks/useViewModelInstance';
export { useRiveFile, type UseRiveFileResult } from './hooks/useRiveFile';
+export type {
+ TypedReferencedAssets,
+ TypedResolvedReferencedAssets,
+} from './core/ReferencedAssets';
export { type RiveFileInput } from './hooks/useRiveFile';
export { type SetValueAction } from './types';
export { RiveRuntime } from './core/RiveRuntime';