1use konjure_lang::inspection::{InspectionError, InspectionTarget};
3use konjure_lang::{
4 Value,
5 live::{LiveCommand, LiveEvent, LiveHistory, LiveRuntime as CoreLiveRuntime},
6 spatial::SpatialRuntime,
7};
8use konjure_sdk::presentation::UiEvent;
9use std::collections::BTreeMap;
10use wasm_bindgen::prelude::*;
11
12const MAX_MODULE_BYTES: usize = 1024 * 1024;
13
14#[wasm_bindgen]
17pub fn presentation_plan(document_json: &str, target_json: &str) -> Result<String, JsValue> {
18 use konjure_sdk::presentation::{
19 DeviceTarget, PresentationContext, PresentationPlan, UiDocument,
20 };
21 if document_json.len() > 1024 * 1024 || target_json.len() > 4096 {
22 return Err(JsValue::from_str("presentation input exceeds its bound"));
23 }
24 let document: UiDocument = serde_json::from_str(document_json).map_err(super::json_error)?;
25 let target: DeviceTarget = serde_json::from_str(target_json).map_err(super::json_error)?;
26 let plan = PresentationPlan::build(&document, &target, &PresentationContext::default())
27 .map_err(|error| JsValue::from_str(&error.to_string()))?;
28 super::encode(&plan)
29}
30
31#[wasm_bindgen]
33pub fn live_pairing_qr_svg(join_url: &str) -> Result<String, JsValue> {
34 konjure_lang::live::pairing::qr_svg(join_url)
35 .map_err(|error| JsValue::from_str(&error.to_string()))
36}
37
38#[wasm_bindgen]
41pub fn live_pairing_parse_json(join_url: &str) -> Result<String, JsValue> {
42 let link = konjure_lang::live::pairing::PairingLink::parse(join_url)
43 .map_err(|error| JsValue::from_str(&error.to_string()))?;
44 super::encode(&link)
45}
46
47fn modules(json: &str) -> Result<BTreeMap<String, String>, JsValue> {
48 if json.len() > MAX_MODULE_BYTES {
49 return Err(JsValue::from_str("module bundle exceeds 1 MiB"));
50 }
51 serde_json::from_str(json).map_err(super::json_error)
52}
53fn error(value: &impl serde::Serialize) -> JsValue {
54 JsValue::from_str(
55 &serde_json::to_string(value)
56 .unwrap_or_else(|_| "language diagnostic encoding failed".into()),
57 )
58}
59
60#[wasm_bindgen]
62pub fn language_catalog() -> Result<String, JsValue> {
63 super::encode(&konjure_lang::catalog::catalog())
64}
65
66#[wasm_bindgen]
69pub fn language_analyze(source: &str, modules_json: &str) -> Result<String, JsValue> {
70 let supplied = modules(modules_json)?;
71 let (symbols, methods, references, diagnostics) = match konjure_lang::compile(source, &supplied) {
72 Ok(program) => (program.metadata().symbols.iter().map(|s| serde_json::json!({
73 "name":s.name, "kind":s.kind, "start":s.span.start, "end":s.span.end,
74 "module":s.span.module, "signature":s.signature, "qualified_name":s.qualified_name,
75 })).collect::<Vec<_>>(), program.metadata().methods.clone(), program.metadata().references.clone(), Vec::new()),
76 Err(diagnostics) => (Vec::new(), Vec::new(), Vec::new(), diagnostics),
77 };
78 let tokens = konjure_lang::parser::tokenize(source, "main").unwrap_or_default();
79 super::encode(&serde_json::json!({
80 "tokens":tokens.iter().map(|t| serde_json::json!({
81 "start":t.span.start,"end":t.span.end,"kind":t.kind,"text":t.text,
82 })).collect::<Vec<_>>(), "symbols":symbols, "methods":methods, "references":references, "diagnostics":diagnostics,
83 }))
84}
85
86#[wasm_bindgen]
89pub struct LanguageRuntime {
90 inner: SpatialRuntime,
91}
92
93#[wasm_bindgen]
96#[derive(Default)]
97pub struct LiveRuntime {
98 inner: CoreLiveRuntime,
99}
100
101#[wasm_bindgen]
102impl LiveRuntime {
103 pub fn ui_json(&self) -> Result<String, JsValue> {
105 super::encode(&self.inner.ui_document().map_err(|value| error(&value))?)
106 }
107 #[wasm_bindgen(constructor)]
109 pub fn new() -> Self {
110 Self {
111 inner: CoreLiveRuntime::new(),
112 }
113 }
114
115 pub fn apply_json(&mut self, command_json: &str) -> Result<String, JsValue> {
117 if command_json.len() > 4 * 1024 * 1024 {
118 return Err(JsValue::from_str("live command exceeds 4 MiB"));
119 }
120 let command: LiveCommand = serde_json::from_str(command_json).map_err(super::json_error)?;
121 super::encode(&self.inner.apply(command).map_err(|value| error(&value))?)
122 }
123
124 pub fn apply_ui_json(&mut self, event_json: &str) -> Result<String, JsValue> {
127 if event_json.len() > 8 * 1024 {
128 return Err(JsValue::from_str("UI event exceeds 8 KiB"));
129 }
130 let event: UiEvent = serde_json::from_str(event_json).map_err(super::json_error)?;
131 let command = self
132 .inner
133 .ui_command(&event)
134 .map_err(|value| error(&value))?;
135 super::encode(&self.inner.apply(command).map_err(|value| error(&value))?)
136 }
137
138 pub fn accept_json(&mut self, event_json: &str) -> Result<(), JsValue> {
140 if event_json.len() > 4 * 1024 * 1024 {
141 return Err(JsValue::from_str("live event exceeds 4 MiB"));
142 }
143 let event: LiveEvent = serde_json::from_str(event_json).map_err(super::json_error)?;
144 self.inner.accept(event).map_err(|value| error(&value))
145 }
146
147 pub fn restore_json(&mut self, history_json: &str) -> Result<(), JsValue> {
149 if history_json.len() > konjure_lang::live::MAX_JOURNAL_BYTES {
150 return Err(JsValue::from_str("live history exceeds 16 MiB"));
151 }
152 let history: LiveHistory = serde_json::from_str(history_json).map_err(super::json_error)?;
153 self.inner.restore(history).map_err(|value| error(&value))
154 }
155
156 pub fn history_json(&self) -> Result<String, JsValue> {
158 super::encode(self.inner.history())
159 }
160
161 pub fn snapshot_json(&self) -> Result<String, JsValue> {
163 super::encode(&self.inner.snapshot())
164 }
165
166 pub fn render_json(&self) -> Result<String, JsValue> {
168 super::encode(&self.inner.render().map_err(|value| error(&value))?)
169 }
170
171 pub fn inspect_tensor_json(&self, target_json: &str) -> String {
173 let result = if target_json.len() > 4096 {
174 Err(InspectionError::InvalidTarget)
175 } else {
176 self.inner
177 .snapshot()
178 .ok_or(InspectionError::InvalidTarget)
179 .and_then(|snapshot| {
180 serde_json::from_str::<InspectionTarget>(target_json)
181 .map_err(|_| InspectionError::InvalidTarget)
182 .and_then(|target| snapshot.tensor_inspection(&target))
183 })
184 };
185 serde_json::to_string(&result).unwrap_or_else(|_| "{\"Err\":\"encoding\"}".into())
186 }
187}
188
189#[wasm_bindgen]
190impl LanguageRuntime {
191 pub fn ui_json(&self) -> Result<String, JsValue> {
193 super::encode(
194 &self
195 .inner
196 .ui_document()
197 .map_err(|value| JsValue::from_str(&value.to_string()))?,
198 )
199 }
200
201 pub fn invoke_ui(&mut self, event_json: &str) -> Result<String, JsValue> {
203 if event_json.len() > 8192 {
204 return Err(JsValue::from_str("UI event exceeds 8 KiB"));
205 }
206 let event = serde_json::from_str(event_json).map_err(super::json_error)?;
207 super::encode(
208 &self
209 .inner
210 .invoke_ui(&event)
211 .map_err(|value| error(&value))?,
212 )
213 }
214 #[wasm_bindgen(constructor)]
216 pub fn new(source: &str, modules_json: &str) -> Result<Self, JsValue> {
217 Ok(Self {
218 inner: SpatialRuntime::new(source, &modules(modules_json)?).map_err(|e| error(&e))?,
219 })
220 }
221 pub fn step(&mut self, count: f64) -> Result<String, JsValue> {
223 if !count.is_finite() || count.fract() != 0.0 || !(0.0..=600.0).contains(&count) {
224 return Err(JsValue::from_str(
225 "step count must be an integer between 0 and 600",
226 ));
227 }
228 super::encode(&self.inner.step(count as u32).map_err(|e| error(&e))?)
229 }
230 pub fn finish(&mut self) -> Result<String, JsValue> {
233 super::encode(&self.inner.finish().map_err(|e| error(&e))?)
234 }
235 pub fn snapshot_json(&self) -> Result<String, JsValue> {
237 super::encode(&self.inner.snapshot())
238 }
239 pub fn inspect_tensor_json(&self, target_json: &str) -> String {
246 let result = if target_json.len() > 4096 {
247 Err(InspectionError::InvalidTarget)
248 } else {
249 serde_json::from_str::<InspectionTarget>(target_json)
250 .map_err(|_| InspectionError::InvalidTarget)
251 .and_then(|target| self.inner.snapshot().tensor_inspection(&target))
252 };
253 serde_json::to_string(&result).unwrap_or_else(|_| "{\"Err\":\"encoding\"}".into())
254 }
255 pub fn tensor_bytes(&self, name: &str) -> Result<Vec<u8>, JsValue> {
261 let snapshot = self.inner.snapshot();
262 let Some(Value::Tensor(tensor)) = snapshot.globals.get(name) else {
263 return Err(JsValue::from_str("global must name a Tensor"));
264 };
265 Ok(tensor.to_le_bytes())
266 }
267 pub fn tensor_metadata_json(&self, name: &str) -> Result<String, JsValue> {
269 let snapshot = self.inner.snapshot();
270 let Some(Value::Tensor(tensor)) = snapshot.globals.get(name) else {
271 return Err(JsValue::from_str("global must name a Tensor"));
272 };
273 super::encode(&serde_json::json!({
274 "dtype": tensor.dtype(), "shape": tensor.shape(),
275 "byte_length": tensor.byte_len(), "byte_order": "little", "order": "row-major",
276 }))
277 }
278 pub fn render_json(&self) -> Result<String, JsValue> {
280 super::encode(&self.inner.render().map_err(|e| error(&e))?)
281 }
282 pub fn invoke_entity(
284 &mut self,
285 entity: f64,
286 method: &str,
287 args_json: &str,
288 ) -> Result<String, JsValue> {
289 if args_json.len() > 64 * 1024 {
290 return Err(JsValue::from_str("arguments exceed 64 KiB"));
291 }
292 if !entity.is_finite()
293 || entity.fract() != 0.0
294 || !(1.0..=9_007_199_254_740_991.0).contains(&entity)
295 {
296 return Err(JsValue::from_str(
297 "entity ID must be a positive exact integer",
298 ));
299 }
300 let args: Vec<Value> = serde_json::from_str(args_json).map_err(super::json_error)?;
301 super::encode(
302 &self
303 .inner
304 .invoke_entity(entity as u64, method, &args)
305 .map_err(|e| error(&e))?,
306 )
307 }
308 pub fn invoke_component(
311 &mut self,
312 entity: f64,
313 component: &str,
314 field: &str,
315 args_json: &str,
316 ) -> Result<String, JsValue> {
317 if args_json.len() > 64 * 1024 {
318 return Err(JsValue::from_str("arguments exceed 64 KiB"));
319 }
320 if !entity.is_finite()
321 || entity.fract() != 0.0
322 || !(1.0..=9_007_199_254_740_991.0).contains(&entity)
323 {
324 return Err(JsValue::from_str(
325 "entity ID must be a positive exact integer",
326 ));
327 }
328 let args: Vec<Value> = serde_json::from_str(args_json).map_err(super::json_error)?;
329 super::encode(
330 &self
331 .inner
332 .invoke_component(entity as u64, component, field, &args)
333 .map_err(|e| error(&e))?,
334 )
335 }
336 pub fn invoke(&mut self, name: &str, args_json: &str) -> Result<String, JsValue> {
338 if args_json.len() > 64 * 1024 {
339 return Err(JsValue::from_str("arguments exceed 64 KiB"));
340 }
341 let args: Vec<Value> = serde_json::from_str(args_json).map_err(super::json_error)?;
342 super::encode(&self.inner.invoke(name, &args).map_err(|e| error(&e))?)
343 }
344}