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 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 #[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 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 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 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 ..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 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 {
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}