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        fn load(module_specifier: &ModuleSpecifier) -> Result<ModuleSource, ModuleLoaderError> {
117            let path = module_specifier
118                .to_file_path()
119                .map_err(|_| JsErrorBox::generic("Only file:// URLs are supported."))?;
120
121            let code = std::fs::read_to_string(&path).map_err(JsErrorBox::from_err)?;
122            let path = module_specifier
123                .to_file_path()
124                .map_err(|_| JsErrorBox::generic("Only file:// URLs are supported."))?;
125            let media_type = MediaType::from_path(&path);
126            let (module_type, code) = transpile_code_to_js(module_specifier, media_type, &code)?;
127
128            Ok(ModuleSource::new(
129                module_type,
130                ModuleSourceCode::String(code.into()),
131                module_specifier,
132                None,
133            ))
134        }
135
136        ModuleLoadResponse::Sync(load(module_specifier))
137    }
138}
139
140struct JSRuntimeState<CTX, Client: FHIRClient<CTX, OperationOutcomeError>> {
141    fhir_client: Arc<Client>,
142    ctx: CTX,
143    return_value: Option<serde_json::Value>,
144    input: Rc<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 fhir_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::to_value(&resource)
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
258#[op2]
259#[serde]
260pub async fn get_input_value<
261    CTX: Clone + 'static,
262    Client: FHIRClient<CTX, OperationOutcomeError> + 'static,
263>(
264    state: Rc<RefCell<OpState>>,
265) -> Result<serde_json::Value, deno_error::JsErrorBox> {
266    let app_state = {
267        let state = state.borrow();
268        // Use the state
269        state
270            .borrow::<Arc<Mutex<JSRuntimeState<CTX, Client>>>>()
271            .clone()
272    };
273
274    let state = app_state.lock().await;
275
276    Ok(serde_json::json!({
277        "request": state.input.clone(),
278    }))
279}
280
281async fn run_code<
282    CTX: Clone + 'static,
283    Client: FHIRClient<CTX, OperationOutcomeError> + 'static,
284>(
285    ctx: CTX,
286    client: Arc<Client>,
287    media_type: PluginCodeType,
288    code: &str,
289    input: serde_json::Value,
290) -> Result<Option<serde_json::Value>, AnyError> {
291    // let main_module = deno_core::resolve_path(file_path, &std::env::current_dir()?)?;
292
293    let runjs = Extension {
294        name: "runjs",
295        ops: std::borrow::Cow::Owned(vec![
296            fhir_read_resource::<CTX, Client>(),
297            set_return_value::<CTX, Client>(),
298            get_input_value::<CTX, Client>(),
299        ]),
300        ..Default::default()
301    };
302
303    let mut deno_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions {
304        module_loader: Some(Rc::new(TsModuleLoader)),
305        extensions: vec![runjs],
306        startup_snapshot: Some(RUNTIME_SNAPSHOT),
307        // startup_snapshot: Some(RUNTIME_SNAPSHOT),
308        ..Default::default()
309    });
310
311    let js_runtime_state = Arc::new(Mutex::new(JSRuntimeState {
312        fhir_client: client,
313        ctx,
314        return_value: None,
315        input: Rc::new(input),
316    }));
317
318    {
319        let op_state = deno_runtime.op_state();
320        let mut op_state = op_state.borrow_mut();
321        op_state.put(js_runtime_state.clone());
322    }
323
324    let user_module_specifier = ModuleSpecifier::parse("memo://user.ts").unwrap();
325
326    let (_module_type, js_code) =
327        transpile_code_to_js(&user_module_specifier, media_type.into(), &code).unwrap();
328
329    let user_mod_id = deno_runtime
330        .load_side_es_module_from_code(&user_module_specifier, js_code)
331        .await?;
332
333    let main_mod_id = deno_runtime
334        .load_main_es_module_from_code(
335            &ModuleSpecifier::parse("memo://main.ts").unwrap(),
336            "import userFunction from 'memo://user.ts'; _internal_.setReturnValue(await userFunction(await _internal_.getInputValue()));"
337                .to_string(),
338        )
339        .await?;
340
341    // let mod_id = deno_runtime.load_main_es_module(&main_module).await?;
342    let user_module_load = deno_runtime.mod_evaluate(user_mod_id);
343    let main_module_load = deno_runtime.mod_evaluate(main_mod_id);
344
345    deno_runtime.run_event_loop(Default::default()).await?;
346
347    user_module_load.await?;
348    main_module_load.await?;
349    // Clean up the JSRuntimeState from the op state
350    // Allows unwrapping RC in next call to have owned_values.
351    {
352        let op_state = deno_runtime.op_state();
353        let mut op_state = op_state.borrow_mut();
354
355        op_state.take::<Arc<Mutex<JSRuntimeState<CTX, Client>>>>();
356    }
357
358    let owned_runetime_state = Arc::try_unwrap(js_runtime_state)
359        .map_err(|_| deno_error::JsErrorBox::type_error("Failed to unwrap JSRuntimeState"))?
360        .into_inner();
361
362    Ok(owned_runetime_state.return_value)
363}