Add new VeilidTable and VeilidClient to WASM API
These new interfaces have been reworked (compared to the original API) to emit (mostly) proper TypeScript types.
This commit is contained in:
		
							
								
								
									
										6
									
								
								veilid-wasm/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								veilid-wasm/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| # veilid-wasm | ||||
|  | ||||
| ## Notes | ||||
|  | ||||
| - [`wasm_bindgen`](https://rustwasm.github.io/wasm-bindgen/) is used to generate interop code between JavaScript and Rust, as well as basic TypeScript types. | ||||
| - [`tsify`](https://github.com/madonoharu/tsify) is used to export TypeScript types along-side [`wasm_bindgen`](https://rustwasm.github.io/wasm-bindgen/) and [`serde_wasm_bindgen`](https://github.com/cloudflare/serde-wasm-bindgen), and enables serialization/deserialization. | ||||
| @@ -25,6 +25,9 @@ use veilid_core::*; | ||||
| use wasm_bindgen::prelude::*; | ||||
| use wasm_bindgen_futures::*; | ||||
|  | ||||
| pub mod veilid_client_js; | ||||
| pub mod veilid_table_js; | ||||
|  | ||||
| // Allocator | ||||
| extern crate wee_alloc; | ||||
| #[global_allocator] | ||||
| @@ -385,7 +388,7 @@ pub fn routing_context_app_message(id: u32, target_string: String, message: Stri | ||||
|         let routing_context = { | ||||
|             let rc = (*ROUTING_CONTEXTS).borrow(); | ||||
|             let Some(routing_context) = rc.get(&id) else { | ||||
|                 return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", id)); | ||||
|                 return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_message", "id", id)); | ||||
|             }; | ||||
|             routing_context.clone() | ||||
|         }; | ||||
| @@ -1460,7 +1463,8 @@ pub fn veilid_version_string() -> String { | ||||
|     veilid_core::veilid_version_string() | ||||
| } | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| #[derive(Serialize, Tsify)] | ||||
| #[tsify(into_wasm_abi)] | ||||
| pub struct VeilidVersion { | ||||
|     pub major: u32, | ||||
|     pub minor: u32, | ||||
|   | ||||
							
								
								
									
										152
									
								
								veilid-wasm/src/veilid_client_js.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								veilid-wasm/src/veilid_client_js.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| #![allow(non_snake_case)] | ||||
| use super::*; | ||||
|  | ||||
| #[wasm_bindgen(typescript_custom_section)] | ||||
| const IUPDATE_VEILID_FUNCTION: &'static str = r#" | ||||
| type UpdateVeilidFunction = (event: VeilidUpdate) => void; | ||||
| "#; | ||||
|  | ||||
| #[wasm_bindgen] | ||||
| extern "C" { | ||||
|     #[wasm_bindgen(extends = Function, typescript_type = "UpdateVeilidFunction")] | ||||
|     pub type UpdateVeilidFunction; | ||||
| } | ||||
|  | ||||
| #[wasm_bindgen(js_name = veilidClient)] | ||||
| pub struct VeilidClient {} | ||||
|  | ||||
| // Since this implementation doesn't contain a `new` fn that's marked as a constructor, | ||||
| // and none of the member fns take a &self arg, | ||||
| // this is just a namespace/class of static functions. | ||||
| #[wasm_bindgen(js_class = veilidClient)] | ||||
| impl VeilidClient { | ||||
|     pub async fn initializeCore(platformConfig: VeilidWASMConfig) { | ||||
|         if INITIALIZED.swap(true, Ordering::Relaxed) { | ||||
|             return; | ||||
|         } | ||||
|         console_error_panic_hook::set_once(); | ||||
|  | ||||
|         // Set up subscriber and layers | ||||
|         let subscriber = Registry::default(); | ||||
|         let mut layers = Vec::new(); | ||||
|         let mut filters = (*FILTERS).borrow_mut(); | ||||
|  | ||||
|         // Performance logger | ||||
|         if platformConfig.logging.performance.enabled { | ||||
|             let filter = | ||||
|                 veilid_core::VeilidLayerFilter::new(platformConfig.logging.performance.level, None); | ||||
|             let layer = WASMLayer::new( | ||||
|                 WASMLayerConfigBuilder::new() | ||||
|                     .set_report_logs_in_timings(platformConfig.logging.performance.logs_in_timings) | ||||
|                     .set_console_config(if platformConfig.logging.performance.logs_in_console { | ||||
|                         ConsoleConfig::ReportWithConsoleColor | ||||
|                     } else { | ||||
|                         ConsoleConfig::NoReporting | ||||
|                     }) | ||||
|                     .build(), | ||||
|             ) | ||||
|             .with_filter(filter.clone()); | ||||
|             filters.insert("performance", filter); | ||||
|             layers.push(layer.boxed()); | ||||
|         }; | ||||
|  | ||||
|         // API logger | ||||
|         if platformConfig.logging.api.enabled { | ||||
|             let filter = | ||||
|                 veilid_core::VeilidLayerFilter::new(platformConfig.logging.api.level, None); | ||||
|             let layer = veilid_core::ApiTracingLayer::get().with_filter(filter.clone()); | ||||
|             filters.insert("api", filter); | ||||
|             layers.push(layer.boxed()); | ||||
|         } | ||||
|  | ||||
|         let subscriber = subscriber.with(layers); | ||||
|         subscriber | ||||
|             .try_init() | ||||
|             .map_err(|e| format!("failed to initialize logging: {}", e)) | ||||
|             .expect("failed to initalize WASM platform"); | ||||
|     } | ||||
|  | ||||
|     pub async fn startupCore( | ||||
|         update_callback_js: UpdateVeilidFunction, | ||||
|         json_config: String, | ||||
|     ) -> Result<(), VeilidAPIError> { | ||||
|         let update_callback_js = SendWrapper::new(update_callback_js); | ||||
|         let update_callback = Arc::new(move |update: VeilidUpdate| { | ||||
|             let _ret = match Function::call1( | ||||
|                 &update_callback_js, | ||||
|                 &JsValue::UNDEFINED, | ||||
|                 &to_jsvalue(update), | ||||
|             ) { | ||||
|                 Ok(v) => v, | ||||
|                 Err(e) => { | ||||
|                     console_log(&format!("calling update callback failed: {:?}", e)); | ||||
|                     return; | ||||
|                 } | ||||
|             }; | ||||
|         }); | ||||
|  | ||||
|         if VEILID_API.borrow().is_some() { | ||||
|             return Err(veilid_core::VeilidAPIError::AlreadyInitialized); | ||||
|         } | ||||
|  | ||||
|         let veilid_api = veilid_core::api_startup_json(update_callback, json_config).await?; | ||||
|         VEILID_API.replace(Some(veilid_api)); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     // TODO: can we refine the TS type of `layer`? | ||||
|     pub fn changeLogLevel(layer: String, log_level: VeilidConfigLogLevel) { | ||||
|         let layer = if layer == "all" { "".to_owned() } else { layer }; | ||||
|         let filters = (*FILTERS).borrow(); | ||||
|         if layer.is_empty() { | ||||
|             // Change all layers | ||||
|             for f in filters.values() { | ||||
|                 f.set_max_level(log_level); | ||||
|             } | ||||
|         } else { | ||||
|             // Change a specific layer | ||||
|             let f = filters.get(layer.as_str()).unwrap(); | ||||
|             f.set_max_level(log_level); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn shutdownCore() -> Result<(), VeilidAPIError> { | ||||
|         let veilid_api = take_veilid_api()?; | ||||
|         veilid_api.shutdown().await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub async fn getState() -> Result<VeilidState, VeilidAPIError> { | ||||
|         let veilid_api = get_veilid_api()?; | ||||
|         let core_state = veilid_api.get_state().await?; | ||||
|         Ok(core_state) | ||||
|     } | ||||
|  | ||||
|     pub async fn attach() -> Result<(), VeilidAPIError> { | ||||
|         let veilid_api = get_veilid_api()?; | ||||
|         veilid_api.attach().await?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub async fn detach() -> Result<(), VeilidAPIError> { | ||||
|         let veilid_api = get_veilid_api()?; | ||||
|         veilid_api.detach().await?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub async fn debug(command: String) -> Result<String, VeilidAPIError> { | ||||
|         let veilid_api = get_veilid_api()?; | ||||
|         let out = veilid_api.debug(command).await?; | ||||
|         APIResult::Ok(out) | ||||
|     } | ||||
|  | ||||
|     pub fn version() -> VeilidVersion { | ||||
|         let (major, minor, patch) = veilid_core::veilid_version(); | ||||
|         let vv = super::VeilidVersion { | ||||
|             major, | ||||
|             minor, | ||||
|             patch, | ||||
|         }; | ||||
|         vv | ||||
|     } | ||||
| } | ||||
							
								
								
									
										182
									
								
								veilid-wasm/src/veilid_table_js.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								veilid-wasm/src/veilid_table_js.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,182 @@ | ||||
| #![allow(non_snake_case)] | ||||
| use super::*; | ||||
|  | ||||
| #[wasm_bindgen()] | ||||
| pub struct VeilidTable { | ||||
|     id: u32, | ||||
|     tableName: String, | ||||
|     columnCount: u32, | ||||
| } | ||||
|  | ||||
| #[wasm_bindgen()] | ||||
| impl VeilidTable { | ||||
|     #[wasm_bindgen(constructor)] | ||||
|     pub fn new(tableName: String, columnCount: u32) -> VeilidTable { | ||||
|         VeilidTable { | ||||
|             id: 0, | ||||
|             tableName, | ||||
|             columnCount, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn openTable(&mut self) -> Result<u32, VeilidAPIError> { | ||||
|         let veilid_api = get_veilid_api()?; | ||||
|         let tstore = veilid_api.table_store()?; | ||||
|         let table_db = tstore | ||||
|             .open(&self.tableName, self.columnCount) | ||||
|             .await | ||||
|             .map_err(veilid_core::VeilidAPIError::generic)?; | ||||
|         let new_id = add_table_db(table_db); | ||||
|         self.id = new_id; | ||||
|         APIResult::Ok(new_id) | ||||
|     } | ||||
|  | ||||
|     pub fn releaseTable(&mut self) -> bool { | ||||
|         let mut tdbs = (*TABLE_DBS).borrow_mut(); | ||||
|         let status = tdbs.remove(&self.id); | ||||
|         self.id = 0; | ||||
|         if status.is_none() { | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     pub async fn deleteTable(&mut self) -> Result<bool, VeilidAPIError> { | ||||
|         self.releaseTable(); | ||||
|  | ||||
|         let veilid_api = get_veilid_api()?; | ||||
|         let tstore = veilid_api.table_store()?; | ||||
|         let deleted = tstore | ||||
|             .delete(&self.tableName) | ||||
|             .await | ||||
|             .map_err(veilid_core::VeilidAPIError::generic)?; | ||||
|         APIResult::Ok(deleted) | ||||
|     } | ||||
|  | ||||
|     async fn ensureOpen(&mut self) { | ||||
|         if self.id == 0 { | ||||
|             let _ = self.openTable().await; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn load( | ||||
|         &mut self, | ||||
|         columnId: u32, | ||||
|         key: String, | ||||
|     ) -> Result<Option<String>, VeilidAPIError> { | ||||
|         self.ensureOpen().await; | ||||
|  | ||||
|         let table_db = { | ||||
|             let table_dbs = (*TABLE_DBS).borrow(); | ||||
|             let Some(table_db) = table_dbs.get(&self.id) else { | ||||
|                 return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_load", "id", self.id)); | ||||
|             }; | ||||
|             table_db.clone() | ||||
|         }; | ||||
|  | ||||
|         let out = table_db.load(columnId, key.as_bytes()).await?.unwrap(); | ||||
|         let out = Some(str::from_utf8(&out).unwrap().to_owned()); | ||||
|         // let out = serde_wasm_bindgen::to_value(&out) | ||||
|         //     .expect("Could not parse using serde_wasm_bindgen"); | ||||
|         APIResult::Ok(out) | ||||
|     } | ||||
|  | ||||
|     pub async fn store( | ||||
|         &mut self, | ||||
|         columnId: u32, | ||||
|         key: String, | ||||
|         value: String, | ||||
|     ) -> Result<(), VeilidAPIError> { | ||||
|         self.ensureOpen().await; | ||||
|  | ||||
|         let table_db = { | ||||
|             let table_dbs = (*TABLE_DBS).borrow(); | ||||
|             let Some(table_db) = table_dbs.get(&self.id) else { | ||||
|                 return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", self.id)); | ||||
|             }; | ||||
|             table_db.clone() | ||||
|         }; | ||||
|  | ||||
|         table_db | ||||
|             .store(columnId, key.as_bytes(), value.as_bytes()) | ||||
|             .await?; | ||||
|         APIRESULT_UNDEFINED | ||||
|     } | ||||
|  | ||||
|     pub async fn delete( | ||||
|         &mut self, | ||||
|         columnId: u32, | ||||
|         key: String, | ||||
|     ) -> Result<Option<String>, VeilidAPIError> { | ||||
|         self.ensureOpen().await; | ||||
|  | ||||
|         let table_db = { | ||||
|             let table_dbs = (*TABLE_DBS).borrow(); | ||||
|             let Some(table_db) = table_dbs.get(&self.id) else { | ||||
|                 return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_delete", "id", self.id)); | ||||
|             }; | ||||
|             table_db.clone() | ||||
|         }; | ||||
|  | ||||
|         // TODO: will crash when trying to .unwrap() of None (trying to delete key that doesn't exist) | ||||
|         let out = table_db.delete(columnId, key.as_bytes()).await?.unwrap(); | ||||
|         let out = Some(str::from_utf8(&out).unwrap().to_owned()); | ||||
|         APIResult::Ok(out) | ||||
|     } | ||||
|  | ||||
|     // TODO try and figure out how to result a String[], maybe Box<[String]>? | ||||
|     pub async fn getKeys(&mut self, columnId: u32) -> Result<JsValue, VeilidAPIError> { | ||||
|         self.ensureOpen().await; | ||||
|  | ||||
|         let table_db = { | ||||
|             let table_dbs = (*TABLE_DBS).borrow(); | ||||
|             let Some(table_db) = table_dbs.get(&self.id) else { | ||||
|                 return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", self.id)); | ||||
|             }; | ||||
|             table_db.clone() | ||||
|         }; | ||||
|  | ||||
|         let keys = table_db.clone().get_keys(columnId).await?; | ||||
|         let out: Vec<String> = keys | ||||
|             .into_iter() | ||||
|             .map(|k| str::from_utf8(&k).unwrap().to_owned()) | ||||
|             .collect(); | ||||
|         let out = | ||||
|             serde_wasm_bindgen::to_value(&out).expect("Could not parse using serde_wasm_bindgen"); | ||||
|  | ||||
|         APIResult::Ok(out) | ||||
|     } | ||||
|  | ||||
|     pub async fn transact(&mut self) -> u32 { | ||||
|         self.ensureOpen().await; | ||||
|  | ||||
|         let table_dbs = (*TABLE_DBS).borrow(); | ||||
|         let Some(table_db) = table_dbs.get(&self.id) else { | ||||
|             return 0; | ||||
|         }; | ||||
|         let tdbt = table_db.clone().transact(); | ||||
|         let tdbtid = add_table_db_transaction(tdbt); | ||||
|         return tdbtid; | ||||
|     } | ||||
|  | ||||
|     // TODO: placeholders for transaction functions | ||||
|     // pub async fn releaseTransaction(&mut self) { | ||||
|     //     self.ensureOpen().await; | ||||
|     // } | ||||
|  | ||||
|     // pub async fn commitTransaction(&mut self) { | ||||
|     //     self.ensureOpen().await; | ||||
|     // } | ||||
|  | ||||
|     // pub async fn rollbackTransaction(&mut self) { | ||||
|     //     self.ensureOpen().await; | ||||
|     // } | ||||
|  | ||||
|     // pub async fn storeTransaction(&mut self, tableId: u32, key: String, value: String) { | ||||
|     //     self.ensureOpen().await; | ||||
|     // } | ||||
|  | ||||
|     // pub async fn deleteTransaction(&mut self) { | ||||
|     //     self.ensureOpen().await; | ||||
|     // } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user