Skip to main content

haste_operation_executor/providers/deno_embedded/
mod.rs

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