33use super :: module_common:: { build_common_module_from_raw, ModuleCommon } ;
44use super :: module_host:: { CallReducerParams , DynModule , Module , ModuleInfo , ModuleInstance , ModuleRuntime } ;
55use super :: UpdateDatabaseResult ;
6- use crate :: host:: v8:: error:: { exception_already_thrown, Throwable } ;
6+ use crate :: host:: v8:: error:: { exception_already_thrown, ExceptionThrown , Throwable } ;
7+ use crate :: host:: v8:: ser:: serialize_to_js;
78use crate :: host:: wasm_common:: module_host_actor:: InstanceCommon ;
9+ use crate :: host:: ArgsTuple ;
810use crate :: { host:: Scheduler , module_host_context:: ModuleCreationContext , replica_context:: ReplicaContext } ;
911use anyhow:: anyhow;
1012use de:: deserialize_js;
@@ -13,9 +15,9 @@ use from_value::cast;
1315use key_cache:: get_or_create_key_cache;
1416use spacetimedb_datastore:: locking_tx_datastore:: MutTxId ;
1517use spacetimedb_datastore:: traits:: Program ;
16- use spacetimedb_lib:: RawModuleDef ;
18+ use spacetimedb_lib:: { ConnectionId , Identity , RawModuleDef } ;
1719use std:: sync:: { Arc , LazyLock } ;
18- use v8:: { Function , HandleScope } ;
20+ use v8:: { Function , HandleScope , Local , Value } ;
1921
2022mod de;
2123mod error;
@@ -141,21 +143,66 @@ impl ModuleInstance for JsInstance {
141143 }
142144}
143145
146+ /// Returns the global property `key`.
147+ fn get_global_property < ' scope > (
148+ scope : & mut HandleScope < ' scope > ,
149+ key : Local < ' scope , v8:: String > ,
150+ ) -> Result < Local < ' scope , Value > , ExceptionThrown > {
151+ scope
152+ . get_current_context ( )
153+ . global ( scope)
154+ . get ( scope, key. into ( ) )
155+ . ok_or_else ( exception_already_thrown)
156+ }
157+
158+ // Calls the `__call_reducer__` function on the global proxy object.
159+ fn call_call_reducer (
160+ scope : & mut HandleScope < ' _ > ,
161+ reducer_id : u32 ,
162+ sender : & Identity ,
163+ conn_id : & ConnectionId ,
164+ timestamp : u64 ,
165+ reducer_args : & ArgsTuple ,
166+ ) -> anyhow:: Result < Result < ( ) , Box < str > > > {
167+ // Get a cached version of the `__call_reducer__` property.
168+ let key_cache = get_or_create_key_cache ( scope) ;
169+ let call_reducer_key = key_cache. borrow_mut ( ) . call_reducer ( scope) ;
170+
171+ catch_exception ( scope, |scope| {
172+ // Serialize the arguments.
173+ let reducer_id = serialize_to_js ( scope, & reducer_id) ?;
174+ let sender = serialize_to_js ( scope, & sender. to_u256 ( ) ) ?;
175+ let conn_id: v8:: Local < ' _ , v8:: Value > = serialize_to_js ( scope, & conn_id. to_u128 ( ) ) ?;
176+ let timestamp = serialize_to_js ( scope, & timestamp) ?;
177+ let reducer_args = serialize_to_js ( scope, & reducer_args. tuple . elements ) ?;
178+ let args = & [ reducer_id, sender, conn_id, timestamp, reducer_args] ;
179+
180+ // Get the function on the global proxy object and convert to a function.
181+ let object = get_global_property ( scope, call_reducer_key) ?;
182+ let fun =
183+ cast ! ( scope, object, Function , "function export for `__call_reducer__`" ) . map_err ( |e| e. throw ( scope) ) ?;
184+
185+ // Call the function.
186+ let receiver = v8:: undefined ( scope) . into ( ) ;
187+ let ret = fun. call ( scope, receiver, args) . ok_or_else ( exception_already_thrown) ?;
188+
189+ // Deserialize the user result.
190+ let user_res = deserialize_js ( scope, ret) ?;
191+
192+ Ok ( user_res)
193+ } )
194+ . map_err ( Into :: into)
195+ }
196+
144197// Calls the `__describe_module__` function on the global proxy object to extract a [`RawModuleDef`].
145198fn call_describe_module ( scope : & mut HandleScope < ' _ > ) -> anyhow:: Result < RawModuleDef > {
146199 // Get a cached version of the `__describe_module__` property.
147200 let key_cache = get_or_create_key_cache ( scope) ;
148- let describe_module_key = key_cache. borrow_mut ( ) . describe_module ( scope) . into ( ) ;
201+ let describe_module_key = key_cache. borrow_mut ( ) . describe_module ( scope) ;
149202
150203 catch_exception ( scope, |scope| {
151- // Get the function on the global proxy object.
152- let object = scope
153- . get_current_context ( )
154- . global ( scope)
155- . get ( scope, describe_module_key)
156- . ok_or_else ( exception_already_thrown) ?;
157-
158- // Convert to a function.
204+ // Get the function on the global proxy object and convert to a function.
205+ let object = get_global_property ( scope, describe_module_key) ?;
159206 let fun =
160207 cast ! ( scope, object, Function , "function export for `__describe_module__`" ) . map_err ( |e| e. throw ( scope) ) ?;
161208
@@ -187,6 +234,63 @@ mod test {
187234 } )
188235 }
189236
237+ #[ test]
238+ fn call_call_reducer_works ( ) {
239+ let call = |code| {
240+ with_script ( code, |scope, _| {
241+ call_call_reducer (
242+ scope,
243+ 42 ,
244+ & Identity :: ONE ,
245+ & ConnectionId :: ZERO ,
246+ 24 ,
247+ & ArgsTuple :: nullary ( ) ,
248+ )
249+ } )
250+ } ;
251+
252+ // Test the trap case.
253+ let ret = call (
254+ r#"
255+ function __call_reducer__(reducer_id, sender, conn_id, timestamp, args) {
256+ throw new Error("foobar");
257+ }
258+ "# ,
259+ ) ;
260+ let actual = format ! ( "{}" , ret. expect_err( "should trap" ) ) . replace ( "\t " , " " ) ;
261+ let expected = r#"
262+ js error Uncaught Error: foobar
263+ at __call_reducer__ (<unknown location>:3:23)
264+ "# ;
265+ assert_eq ! ( actual. trim( ) , expected. trim( ) ) ;
266+
267+ // Test the error case.
268+ let ret = call (
269+ r#"
270+ function __call_reducer__(reducer_id, sender, conn_id, timestamp, args) {
271+ return {
272+ "tag": "err",
273+ "value": "foobar",
274+ };
275+ }
276+ "# ,
277+ ) ;
278+ assert_eq ! ( & * ret. expect( "should not trap" ) . expect_err( "should error" ) , "foobar" ) ;
279+
280+ // Test the error case.
281+ let ret = call (
282+ r#"
283+ function __call_reducer__(reducer_id, sender, conn_id, timestamp, args) {
284+ return {
285+ "tag": "ok",
286+ "value": {},
287+ };
288+ }
289+ "# ,
290+ ) ;
291+ ret. expect ( "should not trap" ) . expect ( "should not error" ) ;
292+ }
293+
190294 #[ test]
191295 fn call_describe_module_works ( ) {
192296 let code = r#"
0 commit comments