haste_operation_executor/providers/deno_embedded/
mod.rs1use 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};
10use 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
39static 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 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 #[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 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 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 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 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 ..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 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 {
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}