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};
8use 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
24static 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 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 #[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 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 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 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 ..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 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 {
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}