1pub mod auth;
12#[cfg(feature = "cli")]
13pub mod cli;
14pub mod convert;
15#[cfg(feature = "database")]
16pub mod database;
17pub mod export;
18#[cfg(feature = "git")]
19pub mod git;
20pub mod import;
21pub mod model;
22pub mod models;
23pub mod storage;
24pub mod validation;
25pub mod workspace;
26
27#[cfg(feature = "api-backend")]
29pub use storage::api::ApiStorageBackend;
30#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
31pub use storage::browser::BrowserStorageBackend;
32#[cfg(feature = "native-fs")]
33pub use storage::filesystem::FileSystemStorageBackend;
34pub use storage::{StorageBackend, StorageError};
35
36pub use convert::{ConversionError, convert_to_odcs};
37#[cfg(feature = "png-export")]
38pub use export::PNGExporter;
39pub use export::{
40 AvroExporter, ExportError, ExportResult, JSONSchemaExporter, ODCSExporter, ProtobufExporter,
41 SQLExporter,
42};
43pub use import::{
44 AvroImporter, ImportError, ImportResult, JSONSchemaImporter, ODCSImporter, ProtobufImporter,
45 SQLImporter,
46};
47#[cfg(feature = "api-backend")]
48pub use model::ApiModelLoader;
49pub use model::{ModelLoader, ModelSaver};
50pub use validation::{
51 RelationshipValidationError, RelationshipValidationResult, TableValidationError,
52 TableValidationResult,
53};
54
55pub use models::enums::*;
57pub use models::{Column, ContactDetails, DataModel, ForeignKey, Relationship, SlaProperty, Table};
58
59pub use auth::{
61 AuthMode, AuthState, GitHubEmail, InitiateOAuthRequest, InitiateOAuthResponse,
62 SelectEmailRequest,
63};
64
65pub use workspace::{
67 CreateWorkspaceRequest, CreateWorkspaceResponse, ListProfilesResponse, LoadProfileRequest,
68 ProfileInfo, WorkspaceInfo,
69};
70
71#[cfg(feature = "git")]
73pub use git::{GitError, GitService, GitStatus};
74
75#[cfg(all(target_arch = "wasm32", feature = "wasm"))]
77mod wasm {
78 use crate::export::ExportError;
79 use crate::import::{ImportError, ImportResult};
80 use crate::models::DataModel;
81 use js_sys;
82 use serde::{Deserialize, Serialize};
83 use serde_json;
84 use serde_yaml;
85 use uuid;
86 use wasm_bindgen::prelude::*;
87 use wasm_bindgen_futures;
88
89 #[derive(Debug, Clone, Serialize, Deserialize)]
92 pub struct WasmError {
93 pub error_type: String,
95 pub message: String,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub code: Option<String>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub details: Option<serde_json::Value>,
103 }
104
105 impl WasmError {
106 fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
108 Self {
109 error_type: error_type.into(),
110 message: message.into(),
111 code: None,
112 details: None,
113 }
114 }
115
116 fn with_code(mut self, code: impl Into<String>) -> Self {
118 self.code = Some(code.into());
119 self
120 }
121
122 fn to_js_value(&self) -> JsValue {
124 match serde_json::to_string(self) {
126 Ok(json) => JsValue::from_str(&json),
127 Err(_) => JsValue::from_str(&self.message),
129 }
130 }
131 }
132
133 fn import_error_to_js(err: ImportError) -> JsValue {
135 WasmError::new("ImportError", err.to_string())
136 .with_code("IMPORT_FAILED")
137 .to_js_value()
138 }
139
140 fn export_error_to_js(err: ExportError) -> JsValue {
142 WasmError::new("ExportError", err.to_string())
143 .with_code("EXPORT_FAILED")
144 .to_js_value()
145 }
146
147 fn serialization_error(err: impl std::fmt::Display) -> JsValue {
149 WasmError::new(
150 "SerializationError",
151 format!("Serialization error: {}", err),
152 )
153 .with_code("SERIALIZATION_FAILED")
154 .to_js_value()
155 }
156
157 fn deserialization_error(err: impl std::fmt::Display) -> JsValue {
159 WasmError::new(
160 "DeserializationError",
161 format!("Deserialization error: {}", err),
162 )
163 .with_code("DESERIALIZATION_FAILED")
164 .to_js_value()
165 }
166
167 fn parse_error(err: impl std::fmt::Display) -> JsValue {
169 WasmError::new("ParseError", format!("Parse error: {}", err))
170 .with_code("PARSE_FAILED")
171 .to_js_value()
172 }
173
174 fn validation_error(err: impl std::fmt::Display) -> JsValue {
176 WasmError::new("ValidationError", err.to_string())
177 .with_code("VALIDATION_FAILED")
178 .to_js_value()
179 }
180
181 fn invalid_input_error(field: &str, err: impl std::fmt::Display) -> JsValue {
183 WasmError::new("InvalidInputError", format!("Invalid {}: {}", field, err))
184 .with_code("INVALID_INPUT")
185 .to_js_value()
186 }
187
188 fn conversion_error(err: impl std::fmt::Display) -> JsValue {
190 WasmError::new("ConversionError", format!("Conversion error: {}", err))
191 .with_code("CONVERSION_FAILED")
192 .to_js_value()
193 }
194
195 fn storage_error(err: impl std::fmt::Display) -> JsValue {
197 WasmError::new("StorageError", format!("Storage error: {}", err))
198 .with_code("STORAGE_FAILED")
199 .to_js_value()
200 }
201
202 fn serialize_import_result(result: &ImportResult) -> Result<String, JsValue> {
204 serde_json::to_string(result).map_err(serialization_error)
205 }
206
207 fn flatten_struct_columns(result: ImportResult) -> ImportResult {
215 use crate::import::{ColumnData, ODCSImporter, TableData};
216
217 let importer = ODCSImporter::new();
218
219 let tables = result
220 .tables
221 .into_iter()
222 .map(|table_data| {
223 let mut all_columns = Vec::new();
224
225 for col_data in table_data.columns {
226 let data_type_upper = col_data.data_type.to_uppercase();
227 let is_map = data_type_upper.starts_with("MAP<");
228
229 if is_map {
231 all_columns.push(col_data);
232 continue;
233 }
234
235 let is_struct = data_type_upper.contains("STRUCT<");
237 if is_struct {
238 let field_data = serde_json::Map::new();
239 if let Ok(nested_cols) = importer.parse_struct_type_from_string(
240 &col_data.name,
241 &col_data.data_type,
242 &field_data,
243 ) {
244 if !nested_cols.is_empty() {
245 let parent_data_type =
247 if col_data.data_type.to_uppercase().starts_with("ARRAY<") {
248 "ARRAY<STRUCT<...>>".to_string()
249 } else {
250 "STRUCT<...>".to_string()
251 };
252
253 all_columns.push(ColumnData {
254 name: col_data.name.clone(),
255 data_type: parent_data_type,
256 physical_type: col_data.physical_type.clone(),
257 nullable: col_data.nullable,
258 primary_key: col_data.primary_key,
259 description: col_data.description.clone(),
260 quality: col_data.quality.clone(),
261 relationships: col_data.relationships.clone(),
262 enum_values: col_data.enum_values.clone(),
263 ..Default::default()
264 });
265
266 for nested_col in nested_cols {
268 all_columns.push(
269 crate::import::odcs_shared::column_to_column_data(
270 &nested_col,
271 ),
272 );
273 }
274 continue;
275 }
276 }
277 }
278
279 all_columns.push(col_data);
281 }
282
283 TableData {
284 table_index: table_data.table_index,
285 name: table_data.name,
286 columns: all_columns,
287 }
288 })
289 .collect();
290
291 ImportResult {
292 tables,
293 tables_requiring_name: result.tables_requiring_name,
294 errors: result.errors,
295 ai_suggestions: result.ai_suggestions,
296 }
297 }
298
299 fn deserialize_workspace(json: &str) -> Result<DataModel, JsValue> {
301 serde_json::from_str(json).map_err(deserialization_error)
302 }
303
304 #[wasm_bindgen]
314 pub fn parse_odcs_yaml(yaml_content: &str) -> Result<String, JsValue> {
315 let mut importer = crate::import::ODCSImporter::new();
316 match importer.import(yaml_content) {
317 Ok(result) => {
318 let flattened = flatten_struct_columns(result);
319 serialize_import_result(&flattened)
320 }
321 Err(err) => Err(import_error_to_js(err)),
322 }
323 }
324
325 #[wasm_bindgen]
341 pub fn parse_odcl_yaml(yaml_content: &str) -> Result<String, JsValue> {
342 let mut importer = crate::import::ODCLImporter::new();
343 match importer.import(yaml_content) {
344 Ok(result) => {
345 let flattened = flatten_struct_columns(result);
346 serialize_import_result(&flattened)
347 }
348 Err(err) => Err(import_error_to_js(err)),
349 }
350 }
351
352 #[wasm_bindgen]
365 pub fn is_odcl_format(yaml_content: &str) -> bool {
366 let importer = crate::import::ODCLImporter::new();
367 importer.can_handle(yaml_content)
368 }
369
370 #[wasm_bindgen]
380 pub fn export_to_odcs_yaml(workspace_json: &str) -> Result<String, JsValue> {
381 let model = deserialize_workspace(workspace_json)?;
382
383 let exports = crate::export::ODCSExporter::export_model(&model, None, "odcs_v3_1_0");
385
386 let yaml_docs: Vec<String> = exports.values().cloned().collect();
388 Ok(yaml_docs.join("\n---\n"))
389 }
390
391 #[wasm_bindgen]
402 pub fn import_from_sql(sql_content: &str, dialect: &str) -> Result<String, JsValue> {
403 let importer = crate::import::SQLImporter::new(dialect);
404 match importer.parse(sql_content) {
405 Ok(result) => {
406 let flattened = flatten_struct_columns(result);
408 serialize_import_result(&flattened)
409 }
410 Err(err) => Err(parse_error(err)),
411 }
412 }
413
414 #[wasm_bindgen]
424 pub fn import_from_avro(avro_content: &str) -> Result<String, JsValue> {
425 let importer = crate::import::AvroImporter::new();
426 match importer.import(avro_content) {
427 Ok(result) => {
428 let flattened = flatten_struct_columns(result);
429 serialize_import_result(&flattened)
430 }
431 Err(err) => Err(import_error_to_js(err)),
432 }
433 }
434
435 #[wasm_bindgen]
445 pub fn import_from_json_schema(json_schema_content: &str) -> Result<String, JsValue> {
446 let importer = crate::import::JSONSchemaImporter::new();
447 match importer.import(json_schema_content) {
448 Ok(result) => {
449 let flattened = flatten_struct_columns(result);
450 serialize_import_result(&flattened)
451 }
452 Err(err) => Err(import_error_to_js(err)),
453 }
454 }
455
456 #[wasm_bindgen]
466 pub fn import_from_protobuf(protobuf_content: &str) -> Result<String, JsValue> {
467 let importer = crate::import::ProtobufImporter::new();
468 match importer.import(protobuf_content) {
469 Ok(result) => {
470 let flattened = flatten_struct_columns(result);
471 serialize_import_result(&flattened)
472 }
473 Err(err) => Err(import_error_to_js(err)),
474 }
475 }
476
477 #[wasm_bindgen]
488 pub fn export_to_sql(workspace_json: &str, dialect: &str) -> Result<String, JsValue> {
489 let model = deserialize_workspace(workspace_json)?;
490 let exporter = crate::export::SQLExporter;
491 match exporter.export(&model.tables, Some(dialect)) {
492 Ok(result) => Ok(result.content),
493 Err(err) => Err(export_error_to_js(err)),
494 }
495 }
496
497 #[wasm_bindgen]
507 pub fn export_to_avro(workspace_json: &str) -> Result<String, JsValue> {
508 let model = deserialize_workspace(workspace_json)?;
509 let exporter = crate::export::AvroExporter;
510 match exporter.export(&model.tables) {
511 Ok(result) => Ok(result.content),
512 Err(err) => Err(export_error_to_js(err)),
513 }
514 }
515
516 #[wasm_bindgen]
526 pub fn export_to_json_schema(workspace_json: &str) -> Result<String, JsValue> {
527 let model = deserialize_workspace(workspace_json)?;
528 let exporter = crate::export::JSONSchemaExporter;
529 match exporter.export(&model.tables) {
530 Ok(result) => Ok(result.content),
531 Err(err) => Err(export_error_to_js(err)),
532 }
533 }
534
535 #[wasm_bindgen]
545 pub fn export_to_protobuf(workspace_json: &str) -> Result<String, JsValue> {
546 let model = deserialize_workspace(workspace_json)?;
547 let exporter = crate::export::ProtobufExporter;
548 match exporter.export(&model.tables) {
549 Ok(result) => Ok(result.content),
550 Err(err) => Err(export_error_to_js(err)),
551 }
552 }
553
554 #[wasm_bindgen]
564 pub fn import_from_cads(yaml_content: &str) -> Result<String, JsValue> {
565 let importer = crate::import::CADSImporter::new();
566 match importer.import(yaml_content) {
567 Ok(asset) => serde_json::to_string(&asset).map_err(serialization_error),
568 Err(err) => Err(import_error_to_js(err)),
569 }
570 }
571
572 #[wasm_bindgen]
582 pub fn export_to_cads(asset_json: &str) -> Result<String, JsValue> {
583 let asset: crate::models::cads::CADSAsset =
584 serde_json::from_str(asset_json).map_err(deserialization_error)?;
585 let exporter = crate::export::CADSExporter;
586 match exporter.export(&asset) {
587 Ok(yaml) => Ok(yaml),
588 Err(err) => Err(export_error_to_js(err)),
589 }
590 }
591
592 #[wasm_bindgen]
602 pub fn import_from_odps(yaml_content: &str) -> Result<String, JsValue> {
603 let importer = crate::import::ODPSImporter::new();
604 match importer.import(yaml_content) {
605 Ok(product) => serde_json::to_string(&product).map_err(serialization_error),
606 Err(err) => Err(import_error_to_js(err)),
607 }
608 }
609
610 #[wasm_bindgen]
620 pub fn export_to_odps(product_json: &str) -> Result<String, JsValue> {
621 let product: crate::models::odps::ODPSDataProduct =
622 serde_json::from_str(product_json).map_err(deserialization_error)?;
623 let exporter = crate::export::ODPSExporter;
624 match exporter.export(&product) {
625 Ok(yaml) => Ok(yaml),
626 Err(err) => Err(export_error_to_js(err)),
627 }
628 }
629
630 #[cfg(feature = "odps-validation")]
640 #[wasm_bindgen]
641 pub fn validate_odps(yaml_content: &str) -> Result<(), JsValue> {
642 use crate::validation::schema::validate_odps_internal;
643 validate_odps_internal(yaml_content).map_err(validation_error)
644 }
645
646 #[cfg(not(feature = "odps-validation"))]
647 #[wasm_bindgen]
648 pub fn validate_odps(_yaml_content: &str) -> Result<(), JsValue> {
649 Ok(())
652 }
653
654 #[wasm_bindgen]
664 pub fn create_domain(name: &str) -> Result<String, JsValue> {
665 let domain = crate::models::domain::Domain::new(name.to_string());
666 serde_json::to_string(&domain).map_err(serialization_error)
667 }
668
669 #[wasm_bindgen]
679 pub fn import_from_domain(yaml_content: &str) -> Result<String, JsValue> {
680 match crate::models::domain::Domain::from_yaml(yaml_content) {
681 Ok(domain) => serde_json::to_string(&domain).map_err(serialization_error),
682 Err(e) => Err(parse_error(e)),
683 }
684 }
685
686 #[wasm_bindgen]
696 pub fn export_to_domain(domain_json: &str) -> Result<String, JsValue> {
697 let domain: crate::models::domain::Domain =
698 serde_json::from_str(domain_json).map_err(deserialization_error)?;
699 domain.to_yaml().map_err(serialization_error)
700 }
701
702 #[wasm_bindgen]
713 pub fn migrate_dataflow_to_domain(
714 dataflow_yaml: &str,
715 domain_name: Option<String>,
716 ) -> Result<String, JsValue> {
717 match crate::convert::migrate_dataflow::migrate_dataflow_to_domain(
718 dataflow_yaml,
719 domain_name.as_deref(),
720 ) {
721 Ok(domain) => serde_json::to_string(&domain).map_err(serialization_error),
722 Err(e) => Err(conversion_error(e)),
723 }
724 }
725
726 #[wasm_bindgen]
736 pub fn parse_tag(tag_str: &str) -> Result<String, JsValue> {
737 use crate::models::Tag;
738 use std::str::FromStr;
739 match Tag::from_str(tag_str) {
740 Ok(tag) => serde_json::to_string(&tag).map_err(serialization_error),
741 Err(_) => Err(parse_error("Invalid tag format")),
742 }
743 }
744
745 #[wasm_bindgen]
755 pub fn serialize_tag(tag_json: &str) -> Result<String, JsValue> {
756 use crate::models::Tag;
757 let tag: Tag = serde_json::from_str(tag_json).map_err(deserialization_error)?;
758 Ok(tag.to_string())
759 }
760
761 #[wasm_bindgen]
773 pub fn convert_to_odcs(input: &str, format: Option<String>) -> Result<String, JsValue> {
774 match crate::convert::convert_to_odcs(input, format.as_deref()) {
775 Ok(yaml) => Ok(yaml),
776 Err(e) => Err(conversion_error(e)),
777 }
778 }
779
780 #[wasm_bindgen]
791 pub fn filter_nodes_by_owner(workspace_json: &str, owner: &str) -> Result<String, JsValue> {
792 let model = deserialize_workspace(workspace_json)?;
793 let filtered = model.filter_nodes_by_owner(owner);
794 serde_json::to_string(&filtered).map_err(serialization_error)
795 }
796
797 #[wasm_bindgen]
808 pub fn filter_relationships_by_owner(
809 workspace_json: &str,
810 owner: &str,
811 ) -> Result<String, JsValue> {
812 let model = deserialize_workspace(workspace_json)?;
813 let filtered = model.filter_relationships_by_owner(owner);
814 serde_json::to_string(&filtered).map_err(serialization_error)
815 }
816
817 #[wasm_bindgen]
828 pub fn filter_nodes_by_infrastructure_type(
829 workspace_json: &str,
830 infrastructure_type: &str,
831 ) -> Result<String, JsValue> {
832 let model = deserialize_workspace(workspace_json)?;
833 let infra_type: crate::models::enums::InfrastructureType =
834 serde_json::from_str(&format!("\"{}\"", infrastructure_type))
835 .map_err(|e| invalid_input_error("infrastructure type", e))?;
836 let filtered = model.filter_nodes_by_infrastructure_type(infra_type);
837 serde_json::to_string(&filtered).map_err(serialization_error)
838 }
839
840 #[wasm_bindgen]
851 pub fn filter_relationships_by_infrastructure_type(
852 workspace_json: &str,
853 infrastructure_type: &str,
854 ) -> Result<String, JsValue> {
855 let model = deserialize_workspace(workspace_json)?;
856 let infra_type: crate::models::enums::InfrastructureType =
857 serde_json::from_str(&format!("\"{}\"", infrastructure_type))
858 .map_err(|e| invalid_input_error("infrastructure type", e))?;
859 let filtered = model.filter_relationships_by_infrastructure_type(infra_type);
860 serde_json::to_string(&filtered).map_err(serialization_error)
861 }
862
863 #[wasm_bindgen]
874 pub fn filter_by_tags(workspace_json: &str, tag: &str) -> Result<String, JsValue> {
875 let model = deserialize_workspace(workspace_json)?;
876 let (nodes, relationships) = model.filter_by_tags(tag);
877 let result = serde_json::json!({
878 "nodes": nodes,
879 "relationships": relationships
880 });
881 serde_json::to_string(&result).map_err(serialization_error)
882 }
883
884 #[wasm_bindgen]
900 pub fn add_system_to_domain(
901 workspace_json: &str,
902 domain_id: &str,
903 system_json: &str,
904 ) -> Result<String, JsValue> {
905 let mut model = deserialize_workspace(workspace_json)?;
906 let domain_uuid =
907 uuid::Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
908 let system: crate::models::domain::System =
909 serde_json::from_str(system_json).map_err(deserialization_error)?;
910 model
911 .add_system_to_domain(domain_uuid, system)
912 .map_err(|e| WasmError::new("OperationError", e).to_js_value())?;
913 serde_json::to_string(&model).map_err(serialization_error)
914 }
915
916 #[wasm_bindgen]
928 pub fn add_cads_node_to_domain(
929 workspace_json: &str,
930 domain_id: &str,
931 node_json: &str,
932 ) -> Result<String, JsValue> {
933 let mut model = deserialize_workspace(workspace_json)?;
934 let domain_uuid =
935 uuid::Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
936 let node: crate::models::domain::CADSNode =
937 serde_json::from_str(node_json).map_err(deserialization_error)?;
938 model
939 .add_cads_node_to_domain(domain_uuid, node)
940 .map_err(|e| WasmError::new("OperationError", e).to_js_value())?;
941 serde_json::to_string(&model).map_err(serialization_error)
942 }
943
944 #[wasm_bindgen]
956 pub fn add_odcs_node_to_domain(
957 workspace_json: &str,
958 domain_id: &str,
959 node_json: &str,
960 ) -> Result<String, JsValue> {
961 let mut model = deserialize_workspace(workspace_json)?;
962 let domain_uuid =
963 uuid::Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
964 let node: crate::models::domain::ODCSNode =
965 serde_json::from_str(node_json).map_err(deserialization_error)?;
966 model
967 .add_odcs_node_to_domain(domain_uuid, node)
968 .map_err(|e| WasmError::new("OperationError", e).to_js_value())?;
969 serde_json::to_string(&model).map_err(serialization_error)
970 }
971
972 #[wasm_bindgen]
986 pub fn validate_table_name(name: &str) -> Result<String, JsValue> {
987 match crate::validation::input::validate_table_name(name) {
988 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
989 Err(err) => {
990 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
991 }
992 }
993 }
994
995 #[wasm_bindgen]
1005 pub fn validate_column_name(name: &str) -> Result<String, JsValue> {
1006 match crate::validation::input::validate_column_name(name) {
1007 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1008 Err(err) => {
1009 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
1010 }
1011 }
1012 }
1013
1014 #[wasm_bindgen]
1024 pub fn validate_uuid(id: &str) -> Result<String, JsValue> {
1025 match crate::validation::input::validate_uuid(id) {
1026 Ok(uuid) => {
1027 Ok(serde_json::json!({"valid": true, "uuid": uuid.to_string()}).to_string())
1028 }
1029 Err(err) => {
1030 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
1031 }
1032 }
1033 }
1034
1035 #[wasm_bindgen]
1045 pub fn validate_data_type(data_type: &str) -> Result<String, JsValue> {
1046 match crate::validation::input::validate_data_type(data_type) {
1047 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1048 Err(err) => {
1049 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
1050 }
1051 }
1052 }
1053
1054 #[wasm_bindgen]
1064 pub fn validate_description(desc: &str) -> Result<String, JsValue> {
1065 match crate::validation::input::validate_description(desc) {
1066 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1067 Err(err) => {
1068 Ok(serde_json::json!({"valid": false, "error": err.to_string()}).to_string())
1069 }
1070 }
1071 }
1072
1073 #[wasm_bindgen]
1084 pub fn sanitize_sql_identifier(name: &str, dialect: &str) -> String {
1085 crate::validation::input::sanitize_sql_identifier(name, dialect)
1086 }
1087
1088 #[wasm_bindgen]
1098 pub fn sanitize_description(desc: &str) -> String {
1099 crate::validation::input::sanitize_description(desc)
1100 }
1101
1102 #[wasm_bindgen]
1113 pub fn detect_naming_conflicts(
1114 existing_tables_json: &str,
1115 new_tables_json: &str,
1116 ) -> Result<String, JsValue> {
1117 let existing_tables: Vec<crate::models::Table> =
1118 serde_json::from_str(existing_tables_json).map_err(deserialization_error)?;
1119 let new_tables: Vec<crate::models::Table> =
1120 serde_json::from_str(new_tables_json).map_err(deserialization_error)?;
1121
1122 let validator = crate::validation::tables::TableValidator::new();
1123 let conflicts = validator.detect_naming_conflicts(&existing_tables, &new_tables);
1124
1125 serde_json::to_string(&conflicts).map_err(serialization_error)
1126 }
1127
1128 #[wasm_bindgen]
1138 pub fn validate_pattern_exclusivity(table_json: &str) -> Result<String, JsValue> {
1139 let table: crate::models::Table =
1140 serde_json::from_str(table_json).map_err(deserialization_error)?;
1141
1142 let validator = crate::validation::tables::TableValidator::new();
1143 match validator.validate_pattern_exclusivity(&table) {
1144 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1145 Err(violation) => {
1146 Ok(serde_json::json!({"valid": false, "violation": violation}).to_string())
1147 }
1148 }
1149 }
1150
1151 #[wasm_bindgen]
1163 pub fn check_circular_dependency(
1164 relationships_json: &str,
1165 source_table_id: &str,
1166 target_table_id: &str,
1167 ) -> Result<String, JsValue> {
1168 let relationships: Vec<crate::models::Relationship> =
1169 serde_json::from_str(relationships_json).map_err(deserialization_error)?;
1170
1171 let source_id = uuid::Uuid::parse_str(source_table_id)
1172 .map_err(|e| invalid_input_error("source_table_id", e))?;
1173 let target_id = uuid::Uuid::parse_str(target_table_id)
1174 .map_err(|e| invalid_input_error("target_table_id", e))?;
1175
1176 let validator = crate::validation::relationships::RelationshipValidator::new();
1177 match validator.check_circular_dependency(&relationships, source_id, target_id) {
1178 Ok((has_cycle, cycle_path)) => {
1179 let cycle_path_strs: Vec<String> = cycle_path
1180 .map(|path| path.iter().map(|id| id.to_string()).collect())
1181 .unwrap_or_default();
1182 Ok(serde_json::json!({
1183 "has_cycle": has_cycle,
1184 "cycle_path": cycle_path_strs
1185 })
1186 .to_string())
1187 }
1188 Err(err) => Err(validation_error(err)),
1189 }
1190 }
1191
1192 #[wasm_bindgen]
1203 pub fn validate_no_self_reference(
1204 source_table_id: &str,
1205 target_table_id: &str,
1206 ) -> Result<String, JsValue> {
1207 let source_id = uuid::Uuid::parse_str(source_table_id)
1208 .map_err(|e| invalid_input_error("source_table_id", e))?;
1209 let target_id = uuid::Uuid::parse_str(target_table_id)
1210 .map_err(|e| invalid_input_error("target_table_id", e))?;
1211
1212 let validator = crate::validation::relationships::RelationshipValidator::new();
1213 match validator.validate_no_self_reference(source_id, target_id) {
1214 Ok(()) => Ok(serde_json::json!({"valid": true}).to_string()),
1215 Err(self_ref) => {
1216 Ok(serde_json::json!({"valid": false, "self_reference": self_ref}).to_string())
1217 }
1218 }
1219 }
1220
1221 #[cfg(feature = "png-export")]
1237 #[wasm_bindgen]
1238 pub fn export_to_png(workspace_json: &str, width: u32, height: u32) -> Result<String, JsValue> {
1239 let model = deserialize_workspace(workspace_json)?;
1240 let exporter = crate::export::PNGExporter::new();
1241 match exporter.export(&model.tables, width, height) {
1242 Ok(result) => Ok(result.content), Err(err) => Err(export_error_to_js(err)),
1244 }
1245 }
1246
1247 #[wasm_bindgen]
1263 pub fn load_model(db_name: &str, store_name: &str, workspace_path: &str) -> js_sys::Promise {
1264 let db_name = db_name.to_string();
1265 let store_name = store_name.to_string();
1266 let workspace_path = workspace_path.to_string();
1267
1268 wasm_bindgen_futures::future_to_promise(async move {
1269 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1270 let loader = crate::model::ModelLoader::new(storage);
1271 match loader.load_model(&workspace_path).await {
1272 Ok(result) => serde_json::to_string(&result)
1273 .map(|s| JsValue::from_str(&s))
1274 .map_err(serialization_error),
1275 Err(err) => Err(storage_error(err)),
1276 }
1277 })
1278 }
1279
1280 #[wasm_bindgen]
1293 pub fn save_model(
1294 db_name: &str,
1295 store_name: &str,
1296 workspace_path: &str,
1297 model_json: &str,
1298 ) -> js_sys::Promise {
1299 let db_name = db_name.to_string();
1300 let store_name = store_name.to_string();
1301 let workspace_path = workspace_path.to_string();
1302 let model_json = model_json.to_string();
1303
1304 wasm_bindgen_futures::future_to_promise(async move {
1305 let model: crate::models::DataModel =
1306 serde_json::from_str(&model_json).map_err(deserialization_error)?;
1307
1308 let storage = crate::storage::browser::BrowserStorageBackend::new(db_name, store_name);
1309 let saver = crate::model::ModelSaver::new(storage);
1310
1311 for table in &model.tables {
1314 let yaml = crate::export::ODCSExporter::export_table(table, "odcs_v3_1_0");
1316 let table_data = crate::model::saver::TableData {
1317 id: table.id,
1318 name: table.name.clone(),
1319 yaml_file_path: Some(format!("tables/{}.yaml", table.name)),
1320 yaml_value: serde_yaml::from_str(&yaml).map_err(parse_error)?,
1321 };
1322 saver
1323 .save_table(&workspace_path, &table_data)
1324 .await
1325 .map_err(storage_error)?;
1326 }
1327
1328 if !model.relationships.is_empty() {
1330 let rel_data: Vec<crate::model::saver::RelationshipData> = model
1331 .relationships
1332 .iter()
1333 .map(|rel| {
1334 let yaml_value = serde_json::json!({
1335 "id": rel.id.to_string(),
1336 "source_table_id": rel.source_table_id.to_string(),
1337 "target_table_id": rel.target_table_id.to_string(),
1338 });
1339 let yaml_str = serde_json::to_string(&yaml_value)
1341 .map_err(|e| format!("Failed to serialize relationship: {}", e))?;
1342 let yaml_value = serde_yaml::from_str(&yaml_str)
1343 .map_err(|e| format!("Failed to convert to YAML: {}", e))?;
1344 Ok(crate::model::saver::RelationshipData {
1345 id: rel.id,
1346 source_table_id: rel.source_table_id,
1347 target_table_id: rel.target_table_id,
1348 yaml_value,
1349 })
1350 })
1351 .collect::<Result<Vec<_>, String>>()
1352 .map_err(|e| WasmError::new("OperationError", e).to_js_value())?;
1353
1354 saver
1355 .save_relationships(&workspace_path, &rel_data)
1356 .await
1357 .map_err(|e| storage_error(e))?;
1358 }
1359
1360 Ok(JsValue::from_str("Model saved successfully"))
1361 })
1362 }
1363
1364 #[cfg(feature = "bpmn")]
1377 #[wasm_bindgen]
1378 pub fn import_bpmn_model(
1379 domain_id: &str,
1380 xml_content: &str,
1381 model_name: Option<String>,
1382 ) -> Result<String, JsValue> {
1383 use crate::import::bpmn::BPMNImporter;
1384 use uuid::Uuid;
1385
1386 let domain_uuid =
1387 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1388
1389 let mut importer = BPMNImporter::new();
1390 match importer.import(xml_content, domain_uuid, model_name.as_deref()) {
1391 Ok(model) => serde_json::to_string(&model).map_err(serialization_error),
1392 Err(e) => Err(import_error_to_js(ImportError::ParseError(e.to_string()))),
1393 }
1394 }
1395
1396 #[cfg(feature = "bpmn")]
1406 #[wasm_bindgen]
1407 pub fn export_bpmn_model(xml_content: &str) -> Result<String, JsValue> {
1408 use crate::export::bpmn::BPMNExporter;
1409 let exporter = BPMNExporter::new();
1410 exporter
1411 .export(xml_content)
1412 .map_err(|e| export_error_to_js(ExportError::SerializationError(e.to_string())))
1413 }
1414
1415 #[cfg(feature = "dmn")]
1428 #[wasm_bindgen]
1429 pub fn import_dmn_model(
1430 domain_id: &str,
1431 xml_content: &str,
1432 model_name: Option<String>,
1433 ) -> Result<String, JsValue> {
1434 use crate::import::dmn::DMNImporter;
1435 use uuid::Uuid;
1436
1437 let domain_uuid =
1438 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1439
1440 let mut importer = DMNImporter::new();
1441 match importer.import(xml_content, domain_uuid, model_name.as_deref()) {
1442 Ok(model) => serde_json::to_string(&model).map_err(serialization_error),
1443 Err(e) => Err(import_error_to_js(ImportError::ParseError(e.to_string()))),
1444 }
1445 }
1446
1447 #[cfg(feature = "dmn")]
1457 #[wasm_bindgen]
1458 pub fn export_dmn_model(xml_content: &str) -> Result<String, JsValue> {
1459 use crate::export::dmn::DMNExporter;
1460 let exporter = DMNExporter::new();
1461 exporter
1462 .export(xml_content)
1463 .map_err(|e| export_error_to_js(ExportError::SerializationError(e.to_string())))
1464 }
1465
1466 #[cfg(feature = "openapi")]
1479 #[wasm_bindgen]
1480 pub fn import_openapi_spec(
1481 domain_id: &str,
1482 content: &str,
1483 api_name: Option<String>,
1484 ) -> Result<String, JsValue> {
1485 use crate::import::openapi::OpenAPIImporter;
1486 use uuid::Uuid;
1487
1488 let domain_uuid =
1489 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1490
1491 let mut importer = OpenAPIImporter::new();
1492 match importer.import(content, domain_uuid, api_name.as_deref()) {
1493 Ok(model) => serde_json::to_string(&model).map_err(serialization_error),
1494 Err(e) => Err(import_error_to_js(ImportError::ParseError(e.to_string()))),
1495 }
1496 }
1497
1498 #[cfg(feature = "openapi")]
1510 #[wasm_bindgen]
1511 pub fn export_openapi_spec(
1512 content: &str,
1513 source_format: &str,
1514 target_format: Option<String>,
1515 ) -> Result<String, JsValue> {
1516 use crate::export::openapi::OpenAPIExporter;
1517 use crate::models::openapi::OpenAPIFormat;
1518
1519 let source_fmt = match source_format {
1520 "yaml" | "yml" => OpenAPIFormat::Yaml,
1521 "json" => OpenAPIFormat::Json,
1522 _ => {
1523 return Err(invalid_input_error("source format", "Use 'yaml' or 'json'"));
1524 }
1525 };
1526
1527 let target_fmt = if let Some(tf) = target_format {
1528 match tf.as_str() {
1529 "yaml" | "yml" => Some(OpenAPIFormat::Yaml),
1530 "json" => Some(OpenAPIFormat::Json),
1531 _ => {
1532 return Err(invalid_input_error("target format", "Use 'yaml' or 'json'"));
1533 }
1534 }
1535 } else {
1536 None
1537 };
1538
1539 let exporter = OpenAPIExporter::new();
1540 exporter
1541 .export(content, source_fmt, target_fmt)
1542 .map_err(|e| export_error_to_js(ExportError::SerializationError(e.to_string())))
1543 }
1544
1545 #[cfg(feature = "openapi")]
1557 #[wasm_bindgen]
1558 pub fn convert_openapi_to_odcs(
1559 openapi_content: &str,
1560 component_name: &str,
1561 table_name: Option<String>,
1562 ) -> Result<String, JsValue> {
1563 use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1564
1565 let converter = OpenAPIToODCSConverter::new();
1566 match converter.convert_component(openapi_content, component_name, table_name.as_deref()) {
1567 Ok(table) => serde_json::to_string(&table).map_err(serialization_error),
1568 Err(e) => Err(conversion_error(e)),
1569 }
1570 }
1571
1572 #[cfg(feature = "openapi")]
1583 #[wasm_bindgen]
1584 pub fn analyze_openapi_conversion(
1585 openapi_content: &str,
1586 component_name: &str,
1587 ) -> Result<String, JsValue> {
1588 use crate::convert::openapi_to_odcs::OpenAPIToODCSConverter;
1589
1590 let converter = OpenAPIToODCSConverter::new();
1591 match converter.analyze_conversion(openapi_content, component_name) {
1592 Ok(report) => serde_json::to_string(&report).map_err(serialization_error),
1593 Err(e) => Err(WasmError::new("AnalysisError", e.to_string())
1594 .with_code("ANALYSIS_FAILED")
1595 .to_js_value()),
1596 }
1597 }
1598
1599 #[wasm_bindgen]
1614 pub fn create_workspace(name: &str, owner_id: &str) -> Result<String, JsValue> {
1615 use crate::models::workspace::Workspace;
1616 use chrono::Utc;
1617 use uuid::Uuid;
1618
1619 let owner_uuid =
1620 Uuid::parse_str(owner_id).map_err(|e| invalid_input_error("owner ID", e))?;
1621
1622 let workspace = Workspace::new(name.to_string(), owner_uuid);
1623
1624 serde_json::to_string(&workspace).map_err(serialization_error)
1625 }
1626
1627 #[wasm_bindgen]
1637 pub fn parse_workspace_yaml(yaml_content: &str) -> Result<String, JsValue> {
1638 use crate::models::workspace::Workspace;
1639
1640 let workspace: Workspace = serde_yaml::from_str(yaml_content).map_err(parse_error)?;
1641 serde_json::to_string(&workspace).map_err(serialization_error)
1642 }
1643
1644 #[wasm_bindgen]
1654 pub fn export_workspace_to_yaml(workspace_json: &str) -> Result<String, JsValue> {
1655 use crate::models::workspace::Workspace;
1656
1657 let workspace: Workspace =
1658 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1659 serde_yaml::to_string(&workspace).map_err(serialization_error)
1660 }
1661
1662 #[wasm_bindgen]
1674 pub fn add_domain_to_workspace(
1675 workspace_json: &str,
1676 domain_id: &str,
1677 domain_name: &str,
1678 ) -> Result<String, JsValue> {
1679 use crate::models::workspace::{DomainReference, Workspace};
1680 use chrono::Utc;
1681 use uuid::Uuid;
1682
1683 let mut workspace: Workspace =
1684 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1685 let domain_uuid =
1686 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1687
1688 if workspace.domains.iter().any(|d| d.id == domain_uuid) {
1690 return Err(WasmError::new(
1691 "DuplicateError",
1692 format!("Domain {} already exists in workspace", domain_id),
1693 )
1694 .with_code("DUPLICATE_DOMAIN")
1695 .to_js_value());
1696 }
1697
1698 workspace.domains.push(DomainReference {
1699 id: domain_uuid,
1700 name: domain_name.to_string(),
1701 description: None,
1702 systems: Vec::new(),
1703 });
1704 workspace.last_modified_at = Utc::now();
1705
1706 serde_json::to_string(&workspace).map_err(serialization_error)
1707 }
1708
1709 #[wasm_bindgen]
1720 pub fn remove_domain_from_workspace(
1721 workspace_json: &str,
1722 domain_id: &str,
1723 ) -> Result<String, JsValue> {
1724 use crate::models::workspace::Workspace;
1725 use chrono::Utc;
1726 use uuid::Uuid;
1727
1728 let mut workspace: Workspace =
1729 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1730 let domain_uuid =
1731 Uuid::parse_str(domain_id).map_err(|e| invalid_input_error("domain ID", e))?;
1732
1733 let original_len = workspace.domains.len();
1734 workspace.domains.retain(|d| d.id != domain_uuid);
1735
1736 if workspace.domains.len() == original_len {
1737 return Err(WasmError::new(
1738 "NotFoundError",
1739 format!("Domain {} not found in workspace", domain_id),
1740 )
1741 .with_code("DOMAIN_NOT_FOUND")
1742 .to_js_value());
1743 }
1744
1745 workspace.last_modified_at = Utc::now();
1746 serde_json::to_string(&workspace).map_err(serialization_error)
1747 }
1748
1749 #[wasm_bindgen]
1760 pub fn add_relationship_to_workspace(
1761 workspace_json: &str,
1762 relationship_json: &str,
1763 ) -> Result<String, JsValue> {
1764 use crate::models::Relationship;
1765 use crate::models::workspace::Workspace;
1766
1767 let mut workspace: Workspace =
1768 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1769 let relationship: Relationship =
1770 serde_json::from_str(relationship_json).map_err(deserialization_error)?;
1771
1772 if workspace
1774 .relationships
1775 .iter()
1776 .any(|r| r.id == relationship.id)
1777 {
1778 return Err(WasmError::new(
1779 "DuplicateError",
1780 format!(
1781 "Relationship {} already exists in workspace",
1782 relationship.id
1783 ),
1784 )
1785 .with_code("DUPLICATE_RELATIONSHIP")
1786 .to_js_value());
1787 }
1788
1789 workspace.add_relationship(relationship);
1790 serde_json::to_string(&workspace).map_err(serialization_error)
1791 }
1792
1793 #[wasm_bindgen]
1804 pub fn remove_relationship_from_workspace(
1805 workspace_json: &str,
1806 relationship_id: &str,
1807 ) -> Result<String, JsValue> {
1808 use crate::models::workspace::Workspace;
1809 use uuid::Uuid;
1810
1811 let mut workspace: Workspace =
1812 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1813 let relationship_uuid = Uuid::parse_str(relationship_id)
1814 .map_err(|e| invalid_input_error("relationship ID", e))?;
1815
1816 if !workspace.remove_relationship(relationship_uuid) {
1817 return Err(WasmError::new(
1818 "NotFoundError",
1819 format!("Relationship {} not found in workspace", relationship_id),
1820 )
1821 .with_code("RELATIONSHIP_NOT_FOUND")
1822 .to_js_value());
1823 }
1824
1825 serde_json::to_string(&workspace).map_err(serialization_error)
1826 }
1827
1828 #[wasm_bindgen]
1839 pub fn get_workspace_relationships_for_source(
1840 workspace_json: &str,
1841 source_table_id: &str,
1842 ) -> Result<String, JsValue> {
1843 use crate::models::workspace::Workspace;
1844 use uuid::Uuid;
1845
1846 let workspace: Workspace =
1847 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1848 let source_uuid = Uuid::parse_str(source_table_id)
1849 .map_err(|e| invalid_input_error("source table ID", e))?;
1850
1851 let relationships: Vec<_> = workspace.get_relationships_for_source(source_uuid);
1852 serde_json::to_string(&relationships).map_err(serialization_error)
1853 }
1854
1855 #[wasm_bindgen]
1866 pub fn get_workspace_relationships_for_target(
1867 workspace_json: &str,
1868 target_table_id: &str,
1869 ) -> Result<String, JsValue> {
1870 use crate::models::workspace::Workspace;
1871 use uuid::Uuid;
1872
1873 let workspace: Workspace =
1874 serde_json::from_str(workspace_json).map_err(deserialization_error)?;
1875 let target_uuid = Uuid::parse_str(target_table_id)
1876 .map_err(|e| invalid_input_error("target table ID", e))?;
1877
1878 let relationships: Vec<_> = workspace.get_relationships_for_target(target_uuid);
1879 serde_json::to_string(&relationships).map_err(serialization_error)
1880 }
1881
1882 #[wasm_bindgen]
1893 pub fn create_domain_config(name: &str, workspace_id: &str) -> Result<String, JsValue> {
1894 use crate::models::domain_config::DomainConfig;
1895 use chrono::Utc;
1896 use std::collections::HashMap;
1897 use uuid::Uuid;
1898
1899 let workspace_uuid =
1900 Uuid::parse_str(workspace_id).map_err(|e| invalid_input_error("workspace ID", e))?;
1901
1902 let config = DomainConfig {
1903 id: Uuid::new_v4(),
1904 workspace_id: workspace_uuid,
1905 name: name.to_string(),
1906 description: None,
1907 created_at: Utc::now(),
1908 last_modified_at: Utc::now(),
1909 owner: None,
1910 systems: Vec::new(),
1911 tables: Vec::new(),
1912 products: Vec::new(),
1913 assets: Vec::new(),
1914 processes: Vec::new(),
1915 decisions: Vec::new(),
1916 view_positions: HashMap::new(),
1917 folder_path: None,
1918 workspace_path: None,
1919 };
1920
1921 serde_json::to_string(&config).map_err(serialization_error)
1922 }
1923
1924 #[wasm_bindgen]
1934 pub fn parse_domain_config_yaml(yaml_content: &str) -> Result<String, JsValue> {
1935 use crate::models::domain_config::DomainConfig;
1936
1937 let config: DomainConfig = serde_yaml::from_str(yaml_content).map_err(parse_error)?;
1938 serde_json::to_string(&config).map_err(serialization_error)
1939 }
1940
1941 #[wasm_bindgen]
1951 pub fn export_domain_config_to_yaml(config_json: &str) -> Result<String, JsValue> {
1952 use crate::models::domain_config::DomainConfig;
1953
1954 let config: DomainConfig =
1955 serde_json::from_str(config_json).map_err(deserialization_error)?;
1956 serde_yaml::to_string(&config).map_err(serialization_error)
1957 }
1958
1959 #[wasm_bindgen]
1969 pub fn get_domain_config_id(config_json: &str) -> Result<String, JsValue> {
1970 use crate::models::domain_config::DomainConfig;
1971
1972 let config: DomainConfig =
1973 serde_json::from_str(config_json).map_err(deserialization_error)?;
1974 Ok(config.id.to_string())
1975 }
1976
1977 #[wasm_bindgen]
1988 pub fn update_domain_view_positions(
1989 config_json: &str,
1990 positions_json: &str,
1991 ) -> Result<String, JsValue> {
1992 use crate::models::domain_config::{DomainConfig, ViewPosition};
1993 use chrono::Utc;
1994 use std::collections::HashMap;
1995
1996 let mut config: DomainConfig =
1997 serde_json::from_str(config_json).map_err(deserialization_error)?;
1998 let positions: HashMap<String, HashMap<String, ViewPosition>> =
1999 serde_json::from_str(positions_json).map_err(deserialization_error)?;
2000
2001 config.view_positions = positions;
2002 config.last_modified_at = Utc::now();
2003
2004 serde_json::to_string(&config).map_err(serialization_error)
2005 }
2006
2007 #[wasm_bindgen]
2019 pub fn add_entity_to_domain_config(
2020 config_json: &str,
2021 entity_type: &str,
2022 entity_id: &str,
2023 ) -> Result<String, JsValue> {
2024 use crate::models::domain_config::DomainConfig;
2025 use chrono::Utc;
2026 use uuid::Uuid;
2027
2028 let mut config: DomainConfig =
2029 serde_json::from_str(config_json).map_err(deserialization_error)?;
2030 let entity_uuid =
2031 Uuid::parse_str(entity_id).map_err(|e| invalid_input_error("entity ID", e))?;
2032
2033 let entities = match entity_type {
2034 "system" => &mut config.systems,
2035 "table" => &mut config.tables,
2036 "product" => &mut config.products,
2037 "asset" => &mut config.assets,
2038 "process" => &mut config.processes,
2039 "decision" => &mut config.decisions,
2040 _ => {
2041 return Err(invalid_input_error(
2042 "entity type",
2043 "Use 'system', 'table', 'product', 'asset', 'process', or 'decision'",
2044 ));
2045 }
2046 };
2047
2048 if entities.contains(&entity_uuid) {
2049 return Err(WasmError::new(
2050 "DuplicateError",
2051 format!(
2052 "{} {} already exists in domain config",
2053 entity_type, entity_id
2054 ),
2055 )
2056 .with_code("DUPLICATE_ENTITY")
2057 .to_js_value());
2058 }
2059
2060 entities.push(entity_uuid);
2061 config.last_modified_at = Utc::now();
2062
2063 serde_json::to_string(&config).map_err(serialization_error)
2064 }
2065
2066 #[wasm_bindgen]
2078 pub fn remove_entity_from_domain_config(
2079 config_json: &str,
2080 entity_type: &str,
2081 entity_id: &str,
2082 ) -> Result<String, JsValue> {
2083 use crate::models::domain_config::DomainConfig;
2084 use chrono::Utc;
2085 use uuid::Uuid;
2086
2087 let mut config: DomainConfig =
2088 serde_json::from_str(config_json).map_err(deserialization_error)?;
2089 let entity_uuid =
2090 Uuid::parse_str(entity_id).map_err(|e| invalid_input_error("entity ID", e))?;
2091
2092 let entities = match entity_type {
2093 "system" => &mut config.systems,
2094 "table" => &mut config.tables,
2095 "product" => &mut config.products,
2096 "asset" => &mut config.assets,
2097 "process" => &mut config.processes,
2098 "decision" => &mut config.decisions,
2099 _ => {
2100 return Err(invalid_input_error(
2101 "entity type",
2102 "Use 'system', 'table', 'product', 'asset', 'process', or 'decision'",
2103 ));
2104 }
2105 };
2106
2107 let original_len = entities.len();
2108 entities.retain(|id| *id != entity_uuid);
2109
2110 if entities.len() == original_len {
2111 return Err(WasmError::new(
2112 "NotFoundError",
2113 format!("{} {} not found in domain config", entity_type, entity_id),
2114 )
2115 .with_code("ENTITY_NOT_FOUND")
2116 .to_js_value());
2117 }
2118
2119 config.last_modified_at = Utc::now();
2120 serde_json::to_string(&config).map_err(serialization_error)
2121 }
2122
2123 #[wasm_bindgen]
2137 pub fn parse_decision_yaml(yaml_content: &str) -> Result<String, JsValue> {
2138 use crate::import::decision::DecisionImporter;
2139
2140 let importer = DecisionImporter::new();
2141 match importer.import(yaml_content) {
2142 Ok(decision) => serde_json::to_string(&decision).map_err(serialization_error),
2143 Err(e) => Err(import_error_to_js(e)),
2144 }
2145 }
2146
2147 #[wasm_bindgen]
2157 pub fn parse_decision_index_yaml(yaml_content: &str) -> Result<String, JsValue> {
2158 use crate::import::decision::DecisionImporter;
2159
2160 let importer = DecisionImporter::new();
2161 match importer.import_index(yaml_content) {
2162 Ok(index) => serde_json::to_string(&index).map_err(serialization_error),
2163 Err(e) => Err(import_error_to_js(e)),
2164 }
2165 }
2166
2167 #[wasm_bindgen]
2177 pub fn export_decision_to_yaml(decision_json: &str) -> Result<String, JsValue> {
2178 use crate::export::decision::DecisionExporter;
2179 use crate::models::decision::Decision;
2180
2181 let decision: Decision =
2182 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2183 let exporter = DecisionExporter::new();
2184 exporter
2185 .export_without_validation(&decision)
2186 .map_err(export_error_to_js)
2187 }
2188
2189 #[wasm_bindgen]
2199 pub fn export_decision_index_to_yaml(index_json: &str) -> Result<String, JsValue> {
2200 use crate::export::decision::DecisionExporter;
2201 use crate::models::decision::DecisionIndex;
2202
2203 let index: DecisionIndex =
2204 serde_json::from_str(index_json).map_err(deserialization_error)?;
2205 let exporter = DecisionExporter::new();
2206 exporter.export_index(&index).map_err(export_error_to_js)
2207 }
2208
2209 #[wasm_bindgen]
2219 pub fn export_decision_to_markdown(decision_json: &str) -> Result<String, JsValue> {
2220 use crate::export::markdown::MarkdownExporter;
2221 use crate::models::decision::Decision;
2222
2223 let decision: Decision =
2224 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2225 let exporter = MarkdownExporter::new();
2226 exporter
2227 .export_decision(&decision)
2228 .map_err(export_error_to_js)
2229 }
2230
2231 #[wasm_bindgen]
2244 pub fn create_decision(
2245 number: u32,
2246 title: &str,
2247 context: &str,
2248 decision: &str,
2249 ) -> Result<String, JsValue> {
2250 use crate::models::decision::Decision;
2251
2252 let dec = Decision::new(number, title, context, decision);
2253 serde_json::to_string(&dec).map_err(serialization_error)
2254 }
2255
2256 #[wasm_bindgen]
2262 pub fn create_decision_index() -> Result<String, JsValue> {
2263 use crate::models::decision::DecisionIndex;
2264
2265 let index = DecisionIndex::new();
2266 serde_json::to_string(&index).map_err(serialization_error)
2267 }
2268
2269 #[wasm_bindgen]
2281 pub fn add_decision_to_index(
2282 index_json: &str,
2283 decision_json: &str,
2284 filename: &str,
2285 ) -> Result<String, JsValue> {
2286 use crate::models::decision::{Decision, DecisionIndex};
2287
2288 let mut index: DecisionIndex =
2289 serde_json::from_str(index_json).map_err(deserialization_error)?;
2290 let decision: Decision =
2291 serde_json::from_str(decision_json).map_err(deserialization_error)?;
2292
2293 index.add_decision(&decision, filename.to_string());
2294 serde_json::to_string(&index).map_err(serialization_error)
2295 }
2296
2297 #[wasm_bindgen]
2311 pub fn parse_knowledge_yaml(yaml_content: &str) -> Result<String, JsValue> {
2312 use crate::import::knowledge::KnowledgeImporter;
2313
2314 let importer = KnowledgeImporter::new();
2315 match importer.import(yaml_content) {
2316 Ok(article) => serde_json::to_string(&article).map_err(serialization_error),
2317 Err(e) => Err(import_error_to_js(e)),
2318 }
2319 }
2320
2321 #[wasm_bindgen]
2331 pub fn parse_knowledge_index_yaml(yaml_content: &str) -> Result<String, JsValue> {
2332 use crate::import::knowledge::KnowledgeImporter;
2333
2334 let importer = KnowledgeImporter::new();
2335 match importer.import_index(yaml_content) {
2336 Ok(index) => serde_json::to_string(&index).map_err(serialization_error),
2337 Err(e) => Err(import_error_to_js(e)),
2338 }
2339 }
2340
2341 #[wasm_bindgen]
2351 pub fn export_knowledge_to_yaml(article_json: &str) -> Result<String, JsValue> {
2352 use crate::export::knowledge::KnowledgeExporter;
2353 use crate::models::knowledge::KnowledgeArticle;
2354
2355 let article: KnowledgeArticle =
2356 serde_json::from_str(article_json).map_err(deserialization_error)?;
2357 let exporter = KnowledgeExporter::new();
2358 exporter
2359 .export_without_validation(&article)
2360 .map_err(export_error_to_js)
2361 }
2362
2363 #[wasm_bindgen]
2373 pub fn export_knowledge_index_to_yaml(index_json: &str) -> Result<String, JsValue> {
2374 use crate::export::knowledge::KnowledgeExporter;
2375 use crate::models::knowledge::KnowledgeIndex;
2376
2377 let index: KnowledgeIndex =
2378 serde_json::from_str(index_json).map_err(deserialization_error)?;
2379 let exporter = KnowledgeExporter::new();
2380 exporter.export_index(&index).map_err(export_error_to_js)
2381 }
2382
2383 #[wasm_bindgen]
2393 pub fn export_knowledge_to_markdown(article_json: &str) -> Result<String, JsValue> {
2394 use crate::export::markdown::MarkdownExporter;
2395 use crate::models::knowledge::KnowledgeArticle;
2396
2397 let article: KnowledgeArticle =
2398 serde_json::from_str(article_json).map_err(deserialization_error)?;
2399 let exporter = MarkdownExporter::new();
2400 exporter
2401 .export_knowledge(&article)
2402 .map_err(export_error_to_js)
2403 }
2404
2405 #[wasm_bindgen]
2419 pub fn create_knowledge_article(
2420 number: u32,
2421 title: &str,
2422 summary: &str,
2423 content: &str,
2424 author: &str,
2425 ) -> Result<String, JsValue> {
2426 use crate::models::knowledge::KnowledgeArticle;
2427
2428 let article = KnowledgeArticle::new(number, title, summary, content, author);
2429 serde_json::to_string(&article).map_err(serialization_error)
2430 }
2431
2432 #[wasm_bindgen]
2438 pub fn create_knowledge_index() -> Result<String, JsValue> {
2439 use crate::models::knowledge::KnowledgeIndex;
2440
2441 let index = KnowledgeIndex::new();
2442 serde_json::to_string(&index).map_err(serialization_error)
2443 }
2444
2445 #[wasm_bindgen]
2457 pub fn add_article_to_knowledge_index(
2458 index_json: &str,
2459 article_json: &str,
2460 filename: &str,
2461 ) -> Result<String, JsValue> {
2462 use crate::models::knowledge::{KnowledgeArticle, KnowledgeIndex};
2463
2464 let mut index: KnowledgeIndex =
2465 serde_json::from_str(index_json).map_err(deserialization_error)?;
2466 let article: KnowledgeArticle =
2467 serde_json::from_str(article_json).map_err(deserialization_error)?;
2468
2469 index.add_article(&article, filename.to_string());
2470 serde_json::to_string(&index).map_err(serialization_error)
2471 }
2472
2473 #[wasm_bindgen]
2484 pub fn search_knowledge_articles(articles_json: &str, query: &str) -> Result<String, JsValue> {
2485 use crate::models::knowledge::KnowledgeArticle;
2486
2487 let articles: Vec<KnowledgeArticle> =
2488 serde_json::from_str(articles_json).map_err(deserialization_error)?;
2489
2490 let query_lower = query.to_lowercase();
2491 let matches: Vec<&KnowledgeArticle> = articles
2492 .iter()
2493 .filter(|article| {
2494 article.title.to_lowercase().contains(&query_lower)
2495 || article.summary.to_lowercase().contains(&query_lower)
2496 || article.content.to_lowercase().contains(&query_lower)
2497 || article
2498 .tags
2499 .iter()
2500 .any(|tag| tag.to_string().to_lowercase().contains(&query_lower))
2501 })
2502 .collect();
2503
2504 serde_json::to_string(&matches).map_err(serialization_error)
2505 }
2506}