Skip to main content

haste_deno_executor/
lib.rs

1use deno_core::cppgc::GcCell;
2use deno_core::error::ModuleLoaderError;
3
4use deno_core::{
5    Extension, GarbageCollected, ModuleLoadOptions, ModuleLoadReferrer, ModuleLoader, ModuleSource,
6    ModuleType, OpState, op2, resolve_import, serde_json, v8,
7};
8// main.rs
9use deno_core::error::AnyError;
10use haste_fhir_client::FHIRClient;
11use haste_fhir_model::r4::generated::resources::ResourceType;
12use haste_fhir_operation_error::OperationOutcomeError;
13use std::cell::RefCell;
14use std::rc::Rc;
15use std::sync::Arc;
16use tokio::sync::Mutex;
17
18use deno_ast::{MediaType, ModuleSpecifier};
19use deno_ast::{ParseParams, SourceMapOption};
20use deno_core::ModuleLoadResponse;
21use deno_core::ModuleSourceCode;
22use deno_error::JsErrorBox;
23
24// Load the snapshot generated by build.rs:
25static RUNTIME_SNAPSHOT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/RUNJS_SNAPSHOT.bin"));
26
27fn transpile_code_to_js(
28    module_specifier: &ModuleSpecifier,
29    media_type: MediaType,
30    code: &str,
31) -> Result<(ModuleType, String), JsErrorBox> {
32    let (module_type, should_transpile) = match media_type {
33        MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => (ModuleType::JavaScript, false),
34        MediaType::Jsx => (ModuleType::JavaScript, true),
35        MediaType::TypeScript
36        | MediaType::Mts
37        | MediaType::Cts
38        | MediaType::Dts
39        | MediaType::Dmts
40        | MediaType::Dcts
41        | MediaType::Tsx => (ModuleType::JavaScript, true),
42        MediaType::Json => (ModuleType::Json, false),
43        _ => {
44            return Err(JsErrorBox::generic(format!(
45                "Unknown extension {:?}",
46                media_type
47            )));
48        }
49    };
50    if should_transpile {
51        let parsed = deno_ast::parse_module(ParseParams {
52            specifier: module_specifier.clone(),
53            text: code.into(),
54            media_type,
55            capture_tokens: false,
56            scope_analysis: false,
57            maybe_syntax: None,
58        })
59        .map_err(JsErrorBox::from_err)?;
60        let res = parsed
61            .transpile(
62                &deno_ast::TranspileOptions {
63                    imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Remove,
64                    decorators: deno_ast::DecoratorsTranspileOption::Ecma,
65                    ..Default::default()
66                },
67                &deno_ast::TranspileModuleOptions { module_kind: None },
68                &deno_ast::EmitOptions {
69                    source_map: SourceMapOption::Separate,
70                    inline_sources: true,
71                    ..Default::default()
72                },
73            )
74            .map_err(JsErrorBox::from_err)?;
75        let res = res.into_source();
76        // let source_map = res.source_map.unwrap().into_bytes();
77
78        Ok((module_type, res.text))
79    } else {
80        Ok((module_type, code.to_string()))
81    }
82}
83
84struct TsModuleLoader;
85impl ModuleLoader for TsModuleLoader {
86    fn resolve(
87        &self,
88        specifier: &str,
89        referrer: &str,
90        _kind: deno_core::ResolutionKind,
91    ) -> Result<deno_core::ModuleSpecifier, ModuleLoaderError> {
92        resolve_import(specifier, referrer).map_err(JsErrorBox::from_err)
93    }
94
95    fn load(
96        &self,
97        module_specifier: &ModuleSpecifier,
98        _maybe_referrer: Option<&ModuleLoadReferrer>,
99        _options: ModuleLoadOptions,
100    ) -> ModuleLoadResponse {
101        println!("Loading module: {}", module_specifier);
102        fn load(module_specifier: &ModuleSpecifier) -> Result<ModuleSource, ModuleLoaderError> {
103            let path = module_specifier
104                .to_file_path()
105                .map_err(|_| JsErrorBox::generic("Only file:// URLs are supported."))?;
106
107            let code = std::fs::read_to_string(&path).map_err(JsErrorBox::from_err)?;
108            let path = module_specifier
109                .to_file_path()
110                .map_err(|_| JsErrorBox::generic("Only file:// URLs are supported."))?;
111            let media_type = MediaType::from_path(&path);
112            let (module_type, code) = transpile_code_to_js(module_specifier, media_type, &code)?;
113
114            Ok(ModuleSource::new(
115                module_type,
116                ModuleSourceCode::String(code.into()),
117                module_specifier,
118                None,
119            ))
120        }
121
122        ModuleLoadResponse::Sync(load(module_specifier))
123    }
124}
125
126struct JSRuntimeState<CTX, Client: FHIRClient<CTX, OperationOutcomeError>> {
127    fhir_client: Arc<Client>,
128    ctx: CTX,
129    return_value: Option<serde_json::Value>,
130}
131
132#[repr(C)]
133pub struct InteropObject {
134    value: GcCell<f64>,
135}
136
137unsafe impl GarbageCollected for InteropObject {
138    fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
139    fn get_name(&self) -> &'static std::ffi::CStr {
140        c"InteropObject"
141    }
142}
143
144#[op2]
145impl InteropObject {
146    #[constructor]
147    #[cppgc]
148    fn new(value: f64) -> InteropObject {
149        InteropObject {
150            value: GcCell::new(value),
151        }
152    }
153
154    #[getter]
155    fn value(&self, isolate: &v8::Isolate) -> f64 {
156        *self.value.get(isolate)
157    }
158
159    #[setter]
160    fn value(&self, isolate: &mut v8::Isolate, value: f64) {
161        self.value.set(isolate, value);
162    }
163
164    // #[fast]
165    // fn double_value(&self, isolate: &v8::Isolate) -> f64 {
166    //     *self.value.get(isolate) * 2.0
167    // }
168
169    #[static_method]
170    #[cppgc]
171    fn create(value: f64) -> InteropObject {
172        InteropObject {
173            value: GcCell::new(value),
174        }
175    }
176}
177
178#[op2]
179#[serde]
180pub async fn read_resource<
181    CTX: Clone + 'static,
182    Client: FHIRClient<CTX, OperationOutcomeError> + 'static,
183>(
184    state: Rc<RefCell<OpState>>,
185    #[string] resource_type: String,
186    #[string] id: String,
187) -> Result<serde_json::Value, deno_error::JsErrorBox> {
188    let state = state.borrow();
189    // Use the state
190
191    let app_state = state
192        .borrow::<Arc<Mutex<JSRuntimeState<CTX, Client>>>>()
193        .lock()
194        .await;
195
196    let patient = app_state
197        .fhir_client
198        .read(
199            app_state.ctx.clone(),
200            ResourceType::try_from(resource_type)
201                .map_err(|_| deno_error::JsErrorBox::type_error("Invalid resource type"))?,
202            id,
203        )
204        .await
205        .map_err(|_| deno_error::JsErrorBox::type_error("Failed to read resource"))?;
206
207    serde_json::from_str(&haste_fhir_serialization_json::to_string(&patient).unwrap())
208        .map_err(|_| deno_error::JsErrorBox::type_error("Failed to serialize resource"))
209}
210
211#[op2]
212#[serde]
213pub async fn set_return_value<
214    CTX: Clone + 'static,
215    Client: FHIRClient<CTX, OperationOutcomeError> + 'static,
216>(
217    state: Rc<RefCell<OpState>>,
218    #[serde] value: serde_json::Value,
219) -> Result<(), deno_error::JsErrorBox> {
220    let state = state.borrow();
221    // Use the state
222    let app_state = state.borrow::<Arc<Mutex<JSRuntimeState<CTX, Client>>>>();
223    let mut mutable_state = app_state.lock().await;
224
225    mutable_state.return_value = Some(value);
226    Ok(())
227}
228
229pub enum PluginCodeType {
230    JavaScript,
231    TypeScript,
232}
233
234impl From<PluginCodeType> for MediaType {
235    fn from(value: PluginCodeType) -> Self {
236        match value {
237            PluginCodeType::JavaScript => MediaType::JavaScript,
238            PluginCodeType::TypeScript => MediaType::TypeScript,
239        }
240    }
241}
242
243pub async fn run_code<
244    CTX: Clone + 'static,
245    Client: FHIRClient<CTX, OperationOutcomeError> + 'static,
246>(
247    ctx: CTX,
248    client: Arc<Client>,
249    media_type: PluginCodeType,
250    code: String,
251) -> Result<Option<serde_json::Value>, AnyError> {
252    // let main_module = deno_core::resolve_path(file_path, &std::env::current_dir()?)?;
253
254    let runjs = Extension {
255        name: "runjs",
256        ops: std::borrow::Cow::Owned(vec![
257            read_resource::<CTX, Client>(),
258            set_return_value::<CTX, Client>(),
259        ]),
260        ..Default::default()
261    };
262
263    let mut deno_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions {
264        module_loader: Some(Rc::new(TsModuleLoader)),
265        extensions: vec![runjs],
266        startup_snapshot: Some(RUNTIME_SNAPSHOT),
267        // startup_snapshot: Some(RUNTIME_SNAPSHOT),
268        ..Default::default()
269    });
270
271    let js_runtime_state = Arc::new(Mutex::new(JSRuntimeState {
272        fhir_client: client,
273        ctx,
274        return_value: None,
275    }));
276
277    {
278        let op_state = deno_runtime.op_state();
279        let mut op_state = op_state.borrow_mut();
280        op_state.put(js_runtime_state.clone());
281    }
282
283    let user_module_specifier = ModuleSpecifier::parse("memo://user.ts").unwrap();
284
285    let (_module_type, js_code) =
286        transpile_code_to_js(&user_module_specifier, media_type.into(), &code).unwrap();
287
288    let user_mod_id = deno_runtime
289        .load_side_es_module_from_code(&user_module_specifier, js_code)
290        .await?;
291
292    let main_mod_id = deno_runtime
293        .load_main_es_module_from_code(
294            &ModuleSpecifier::parse("memo://main.ts").unwrap(),
295            "import userFunction from 'memo://user.ts'; _internal_.setReturnValue(await userFunction());"
296                .to_string(),
297        )
298        .await?;
299
300    // let mod_id = deno_runtime.load_main_es_module(&main_module).await?;
301    let user_module_load = deno_runtime.mod_evaluate(user_mod_id);
302    let main_module_load = deno_runtime.mod_evaluate(main_mod_id);
303
304    deno_runtime.run_event_loop(Default::default()).await?;
305
306    user_module_load.await?;
307    main_module_load.await?;
308    // Clean up the JSRuntimeState from the op state
309    // Allows unwrapping RC in next call to have owned_values.
310    {
311        let op_state = deno_runtime.op_state();
312        let mut op_state = op_state.borrow_mut();
313
314        op_state.take::<Arc<Mutex<JSRuntimeState<CTX, Client>>>>();
315    }
316
317    let owned_runetime_state = Arc::try_unwrap(js_runtime_state)
318        .map_err(|_| deno_error::JsErrorBox::type_error("Failed to unwrap JSRuntimeState"))?
319        .into_inner();
320
321    Ok(owned_runetime_state.return_value)
322}