more unit tests & fixes

feat/orm-diffs
Laurin Weger 2 days ago
parent d2d9c665f6
commit ce82867563
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 2
      engine/net/src/orm.rs
  2. 42
      engine/verifier/src/orm/add_remove_triples.rs
  3. 197
      engine/verifier/src/orm/handle_backend_update.rs
  4. 56
      engine/verifier/src/orm/handle_frontend_update.rs
  5. 188
      engine/verifier/src/orm/process_changes.rs
  6. 49
      engine/verifier/src/orm/shape_validation.rs
  7. 11
      engine/verifier/src/orm/types.rs
  8. 4
      sdk/js/examples/multi-framework-signals/src/app/pages/index.astro
  9. 25
      sdk/js/examples/multi-framework-signals/src/frontends/react/HelloWorld.tsx
  10. 39
      sdk/js/examples/multi-framework-signals/src/frontends/svelte/HelloWorld.svelte
  11. 5
      sdk/js/examples/multi-framework-signals/src/frontends/vue/HelloWorld.vue
  12. 4
      sdk/js/examples/multi-framework-signals/src/shapes/orm/basic.schema.ts
  13. 2
      sdk/js/examples/multi-framework-signals/src/shapes/orm/basic.shapeTypes.ts
  14. 2
      sdk/js/examples/multi-framework-signals/src/shapes/orm/basic.typings.ts
  15. 10
      sdk/js/examples/multi-framework-signals/src/shapes/orm/catShape.schema.ts
  16. 2
      sdk/js/examples/multi-framework-signals/src/shapes/orm/catShape.shapeTypes.ts
  17. 2
      sdk/js/examples/multi-framework-signals/src/shapes/orm/catShape.typings.ts
  18. 10
      sdk/js/examples/multi-framework-signals/src/shapes/orm/personShape.schema.ts
  19. 2
      sdk/js/examples/multi-framework-signals/src/shapes/orm/personShape.shapeTypes.ts
  20. 2
      sdk/js/examples/multi-framework-signals/src/shapes/orm/personShape.typings.ts
  21. 16
      sdk/js/examples/multi-framework-signals/src/shapes/orm/testShape.schema.ts
  22. 2
      sdk/js/examples/multi-framework-signals/src/shapes/orm/testShape.shapeTypes.ts
  23. 2
      sdk/js/examples/multi-framework-signals/src/shapes/orm/testShape.typings.ts
  24. 2
      sdk/js/examples/multi-framework-signals/src/shapes/shex/basic.shex
  25. 2
      sdk/js/examples/multi-framework-signals/src/shapes/shex/catShape.shex
  26. 2
      sdk/js/examples/multi-framework-signals/src/shapes/shex/personShape.shex
  27. 2
      sdk/js/examples/multi-framework-signals/src/shapes/shex/testShape.shex
  28. 542
      sdk/js/shex-orm/dist/ShexJTypes.d.ts
  29. 1
      sdk/js/shex-orm/dist/ShexJTypes.d.ts.map
  30. 1
      sdk/js/shex-orm/dist/ShexJTypes.js
  31. 8
      sdk/js/shex-orm/dist/build.d.ts
  32. 1
      sdk/js/shex-orm/dist/build.d.ts.map
  33. 62
      sdk/js/shex-orm/dist/build.js
  34. 3
      sdk/js/shex-orm/dist/cli.d.ts
  35. 1
      sdk/js/shex-orm/dist/cli.d.ts.map
  36. 15
      sdk/js/shex-orm/dist/cli.js
  37. 2
      sdk/js/shex-orm/dist/index.d.ts
  38. 1
      sdk/js/shex-orm/dist/index.d.ts.map
  39. 1
      sdk/js/shex-orm/dist/index.js
  40. 12
      sdk/js/shex-orm/dist/schema-converter/converter.d.ts
  41. 1
      sdk/js/shex-orm/dist/schema-converter/converter.d.ts.map
  42. 69
      sdk/js/shex-orm/dist/schema-converter/converter.js
  43. 8
      sdk/js/shex-orm/dist/schema-converter/templates/schema.ejs
  44. 14
      sdk/js/shex-orm/dist/schema-converter/templates/shapeTypes.ejs
  45. 14
      sdk/js/shex-orm/dist/schema-converter/templates/typings.ejs
  46. 348
      sdk/js/shex-orm/dist/schema-converter/transformers/ShexJSchemaTransformer.d.ts
  47. 1
      sdk/js/shex-orm/dist/schema-converter/transformers/ShexJSchemaTransformer.d.ts.map
  48. 208
      sdk/js/shex-orm/dist/schema-converter/transformers/ShexJSchemaTransformer.js
  49. 366
      sdk/js/shex-orm/dist/schema-converter/transformers/ShexJTypingTransformer.d.ts
  50. 1
      sdk/js/shex-orm/dist/schema-converter/transformers/ShexJTypingTransformer.d.ts.map
  51. 550
      sdk/js/shex-orm/dist/schema-converter/transformers/ShexJTypingTransformer.js
  52. 5
      sdk/js/shex-orm/dist/schema-converter/util/ShapeInterfaceDeclaration.d.ts
  53. 1
      sdk/js/shex-orm/dist/schema-converter/util/ShapeInterfaceDeclaration.d.ts.map
  54. 1
      sdk/js/shex-orm/dist/schema-converter/util/ShapeInterfaceDeclaration.js
  55. 8
      sdk/js/shex-orm/dist/schema-converter/util/annotateReadablePredicates.d.ts
  56. 1
      sdk/js/shex-orm/dist/schema-converter/util/annotateReadablePredicates.d.ts.map
  57. 129
      sdk/js/shex-orm/dist/schema-converter/util/annotateReadablePredicates.js
  58. 3
      sdk/js/shex-orm/dist/schema-converter/util/dedupeObjectTypeMembers.d.ts
  59. 1
      sdk/js/shex-orm/dist/schema-converter/util/dedupeObjectTypeMembers.d.ts.map
  60. 38
      sdk/js/shex-orm/dist/schema-converter/util/dedupeObjectTypeMembers.js
  61. 4
      sdk/js/shex-orm/dist/schema-converter/util/getRdfTypesForTripleConstraint.d.ts
  62. 1
      sdk/js/shex-orm/dist/schema-converter/util/getRdfTypesForTripleConstraint.d.ts.map
  63. 89
      sdk/js/shex-orm/dist/schema-converter/util/getRdfTypesForTripleConstraint.js
  64. 37
      sdk/js/shex-orm/dist/types.d.ts
  65. 1
      sdk/js/shex-orm/dist/types.d.ts.map
  66. 1
      sdk/js/shex-orm/dist/types.js
  67. 2
      sdk/js/shex-orm/dist/util/forAllShapes.d.ts
  68. 1
      sdk/js/shex-orm/dist/util/forAllShapes.d.ts.map
  69. 17
      sdk/js/shex-orm/dist/util/forAllShapes.js
  70. 26
      sdk/js/shex-orm/src/schema-converter/transformers/ShexJSchemaTransformer.ts
  71. 10
      sdk/js/signals/src/connector/ormConnectionHandler.ts
  72. 119
      sdk/rust/src/tests/orm_apply_patches.rs
  73. 58
      sdk/rust/src/tests/orm_create_patches.rs

@ -41,8 +41,10 @@ pub enum OrmPatchType {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OrmPatch {
pub op: OrmPatchOp,
#[serde(skip_serializing_if = "Option::is_none")]
pub valType: Option<OrmPatchType>,
pub path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<Value>, // TODO: Improve type
}

@ -44,7 +44,6 @@ pub fn add_remove_triples(
tracked_predicates: HashMap::new(),
parents: HashMap::new(),
valid: OrmTrackedSubjectValidity::Pending,
prev_valid: OrmTrackedSubjectValidity::Pending,
subject_iri: subject_iri.to_string(),
shape: shape.clone(),
}))
@ -208,47 +207,8 @@ pub fn add_remove_triples(
} else {
panic!("tracked_predicate.current_literals must not be None.");
}
} else if tracked_predicate
.schema
.dataTypes
.iter()
.any(|dt| dt.valType == OrmSchemaValType::shape)
{
// Remove parent from child and child from tracked children.
// If predicate is of type shape, register (parent -> child) links so that
// nested subjects can later be (lazily) fetched / validated.
let shapes_to_process: Vec<_> = tracked_predicate
.schema
.dataTypes
.iter()
.filter_map(|dt| {
if dt.valType == OrmSchemaValType::shape {
dt.shape.clone()
} else {
None
}
})
.collect();
if let BasicType::Str(obj_iri) = &val_removed {
// Remove link to children
tracked_predicate
.tracked_children
.retain(|ts| *obj_iri != ts.read().unwrap().subject_iri);
for shape_iri in shapes_to_process {
// Get or create object's tracked subject struct.
let child_shape = schema.get(&shape_iri).unwrap();
// Remove self from parent
get_or_create_tracked_subject(&obj_iri, child_shape, tracked_subjects)
.write()
.unwrap()
.parents
.remove(subject_iri);
}
}
}
// Parent-child link removal is handled during cleanup since we need to keep them for creating patches.
}
Ok(())
}

@ -123,8 +123,8 @@ impl Verifier {
);
}
let subs = self.orm_subscriptions.get(&scope).unwrap();
for sub in subs.iter() {
let subs = self.orm_subscriptions.get_mut(&scope).unwrap();
for sub in subs.iter_mut() {
log_debug!(
"Applying changes to subscription with nuri {} and shape {}",
sub.nuri.repo(),
@ -161,48 +161,65 @@ impl Verifier {
for (shape_iri, subject_changes) in &orm_changes {
for (subject_iri, change) in subject_changes {
log_debug!(
"Patch creating for subject change {}. #changed preds: {}",
"Patch creating for subject change x shape {} x {}. #changed preds: {}",
subject_iri,
shape_iri,
change.predicates.len()
);
// Get the tracked subject for this (subject, shape) pair
let tracked_subject = sub
let Some(tracked_subject) = sub
.tracked_subjects
.get(subject_iri)
.and_then(|shapes| shapes.get(shape_iri))
.map(|ts| ts.read().unwrap())
.unwrap();
else {
// We might not be tracking this subject x shape combination. Then, there is nothing to do.
continue;
};
log_debug!(
" - Validity check: prev_valid={:?}, valid={:?}",
change.prev_valid,
tracked_subject.valid
);
// Now we have the tracked predicate (containing the shape) and the change.
// Check validity changes
if tracked_subject.prev_valid == OrmTrackedSubjectValidity::Invalid
if change.prev_valid == OrmTrackedSubjectValidity::Invalid
&& tracked_subject.valid == OrmTrackedSubjectValidity::Invalid
{
// Is the subject invalid and was it before? There is nothing we need to inform about.
continue;
} else if tracked_subject.prev_valid == OrmTrackedSubjectValidity::Valid
&& tracked_subject.valid == OrmTrackedSubjectValidity::Invalid
|| tracked_subject.valid == OrmTrackedSubjectValidity::Untracked
} else if change.prev_valid == OrmTrackedSubjectValidity::Valid
&& tracked_subject.valid != OrmTrackedSubjectValidity::Valid
{
// Has the subject become invalid or untracked?
// We add a patch, deleting the object at its root.
let mut path: Vec<String>;
if tracked_subject.parents.is_empty() {
// If this is a root object, we need to add the object's id itself.
path = vec![tracked_subject.subject_iri.clone()];
} else {
path = vec![];
// Check if any parent is also being deleted - if so, skip this deletion patch
// because the parent deletion will implicitly delete the children
let has_parent_being_deleted =
tracked_subject.parents.values().any(|parent_arc| {
let parent_ts = parent_arc.read().unwrap();
parent_ts.valid == OrmTrackedSubjectValidity::ToDelete
});
if !has_parent_being_deleted {
// We add a patch, deleting the object at its root.
// Start with an empty path - the subject IRI will be added in build_path_to_root_and_create_patches
let mut path = vec![];
build_path_to_root_and_create_patches(
&tracked_subject,
&sub.tracked_subjects,
&sub.shape_type.shape,
&mut path,
(OrmPatchOp::remove, Some(OrmPatchType::object), None, None),
&mut patches,
&mut objects_to_create,
&change.prev_valid,
&orm_changes,
&tracked_subject.subject_iri,
);
}
build_path_to_root_and_create_patches(
&tracked_subject,
&sub.tracked_subjects,
&sub.shape_type.shape,
&mut path,
(OrmPatchOp::remove, Some(OrmPatchType::object), None, None),
&mut patches,
&mut objects_to_create,
);
} else {
// The subject is valid or has become valid.
// Process each predicate change
@ -234,6 +251,9 @@ impl Verifier {
diff_op,
&mut patches,
&mut objects_to_create,
&change.prev_valid,
&orm_changes,
&tracked_subject.subject_iri,
);
}
}
@ -246,7 +266,7 @@ impl Verifier {
// Sort by path length (shorter first) to ensure parent objects are created before children
let mut sorted_objects: Vec<_> = objects_to_create.iter().collect();
sorted_objects.sort_by_key(|(path_segments, _)| path_segments.len());
let mut object_create_patches = vec![];
for (path_segments, maybe_iri) in sorted_objects {
let escaped_path: Vec<String> = path_segments
.iter()
@ -254,8 +274,8 @@ impl Verifier {
.collect();
let json_pointer = format!("/{}", escaped_path.join("/"));
// Always create the object itself
patches.push(OrmPatch {
// Always create the object itself.
object_create_patches.push(OrmPatch {
op: OrmPatchOp::add,
valType: Some(OrmPatchType::object),
path: json_pointer.clone(),
@ -264,7 +284,7 @@ impl Verifier {
// If this object has an IRI (it's a real subject), add the id field
if let Some(iri) = maybe_iri {
patches.push(OrmPatch {
object_create_patches.push(OrmPatch {
op: OrmPatchOp::add,
valType: None,
path: format!("{}/@id", json_pointer),
@ -277,8 +297,13 @@ impl Verifier {
let _ = sub
.sender
.clone()
.send(AppResponse::V0(AppResponseV0::OrmUpdate(patches.to_vec())))
.send(AppResponse::V0(AppResponseV0::OrmUpdate(
[object_create_patches, patches].concat(),
)))
.await;
// Cleanup (remove tracked subjects to be deleted).
Verifier::cleanup_tracked_subjects(sub);
}
}
}
@ -286,40 +311,32 @@ impl Verifier {
/// Queue patches for a newly valid tracked subject.
/// This handles creating object patches and id field patches for subjects that have become valid.
fn queue_patches_for_newly_valid_subject(
tracked_subject: &OrmTrackedSubject,
fn queue_objects_to_create(
current_ts: &OrmTrackedSubject,
tracked_subjects: &HashMap<String, HashMap<String, Arc<RwLock<OrmTrackedSubject>>>>,
root_shape: &String,
path: &[String],
patches: &mut Vec<OrmPatch>,
objects_to_create: &mut HashSet<(Vec<String>, Option<SubjectIri>)>,
orm_changes: &OrmChanges,
child_iri: &String,
) {
// Check if we're at a root subject or need to traverse to parents
if tracked_subject.parents.is_empty() || tracked_subject.shape.iri == *root_shape {
// Register object for creation.
// Path to object consists of this subject's iri and the path except for the last element.
let mut path_to_subject = vec![tracked_subject.subject_iri.clone()];
if path.len() > 1 {
path_to_subject.extend_from_slice(&path[..path.len() - 1]);
}
// log_debug!("Queuing object creation for path: {:?}", path_to_subject);
// Always create the object itself with its IRI
objects_to_create.insert((
path_to_subject.clone(),
Some(tracked_subject.subject_iri.clone()),
));
if current_ts.parents.is_empty() || current_ts.shape.iri == *root_shape {
// We are at the root. Insert without the last element (which is the property name).
objects_to_create.insert((path[..path.len() - 1].to_vec(), Some(child_iri.clone())));
} else {
// Not at root: traverse to parents and create object patches along the way
for (_parent_iri, parent_tracked_subject) in tracked_subject.parents.iter() {
for (_parent_iri, parent_tracked_subject) in current_ts.parents.iter() {
let parent_ts = parent_tracked_subject.read().unwrap();
if let Some(new_path) = build_path_segment_for_parent(tracked_subject, &parent_ts, path)
{
if let Some(new_path) = build_path_segment_for_parent(current_ts, &parent_ts, path) {
// Check if the parent's predicate is multi-valued and if no siblings were previously valid
let should_create_parent_predicate_object =
check_should_create_parent_predicate_object(tracked_subject, &parent_ts);
check_should_create_parent_predicate_object(
current_ts,
&parent_ts,
orm_changes,
);
if should_create_parent_predicate_object {
// Need to create an intermediate object for the multi-valued predicate
@ -331,18 +348,18 @@ fn queue_patches_for_newly_valid_subject(
}
// Recurse to the parent first
queue_patches_for_newly_valid_subject(
queue_objects_to_create(
&parent_ts,
tracked_subjects,
root_shape,
&new_path,
patches,
objects_to_create,
orm_changes,
child_iri,
);
// Register this object for creation with its IRI
objects_to_create
.insert((new_path.clone(), Some(tracked_subject.subject_iri.clone())));
objects_to_create.insert((new_path.clone(), Some(current_ts.subject_iri.clone())));
}
}
}
@ -353,6 +370,7 @@ fn queue_patches_for_newly_valid_subject(
fn check_should_create_parent_predicate_object(
tracked_subject: &OrmTrackedSubject,
parent_ts: &OrmTrackedSubject,
orm_changes: &OrmChanges,
) -> bool {
// Find the predicate schema linking parent to this subject
for pred_arc in &parent_ts.shape.predicates {
@ -369,11 +387,22 @@ fn check_should_create_parent_predicate_object(
let is_multi = pred_arc.maxCardinality > 1 || pred_arc.maxCardinality == -1;
if is_multi {
// Check if any siblings were previously valid
// Check if any siblings were previously valid.
// If not, the intermediate object does not exist yet.
let any_sibling_was_valid = tp.tracked_children.iter().any(|child| {
let child_read = child.read().unwrap();
child_read.subject_iri != tracked_subject.subject_iri
&& child_read.prev_valid == OrmTrackedSubjectValidity::Valid
if child_read.subject_iri == tracked_subject.subject_iri {
return false;
}
// Look up the prev_valid from orm_changes
let prev_valid = orm_changes
.get(&child_read.shape.iri)
.and_then(|subjects| subjects.get(&child_read.subject_iri))
.map(|change| &change.prev_valid)
.unwrap_or(&OrmTrackedSubjectValidity::Valid);
*prev_valid == OrmTrackedSubjectValidity::Valid
});
return !any_sibling_was_valid;
@ -442,14 +471,21 @@ fn build_path_to_root_and_create_patches(
),
patches: &mut Vec<OrmPatch>,
objects_to_create: &mut HashSet<(Vec<String>, Option<SubjectIri>)>,
prev_valid: &OrmTrackedSubjectValidity,
orm_changes: &OrmChanges,
child_iri: &String,
) {
log_debug!(
" - build path, ts: {}, path {:?}",
" - build path, ts: {}, path {:?}, #parents: {}, shape: {}",
tracked_subject.subject_iri,
path
path,
tracked_subject.parents.len(),
tracked_subject.shape.iri
);
// If the tracked subject is not valid, we don't create patches for it
if tracked_subject.valid != OrmTrackedSubjectValidity::Valid {
// EXCEPT when we're removing the object itself (indicated by op == remove and valType == object)
let is_delete_op = diff_op.0 == OrmPatchOp::remove && diff_op.1 == Some(OrmPatchType::object);
if tracked_subject.valid != OrmTrackedSubjectValidity::Valid && !is_delete_op {
return;
}
@ -457,12 +493,19 @@ fn build_path_to_root_and_create_patches(
if tracked_subject.parents.is_empty() || tracked_subject.shape.iri == *root_shape {
// Build the final JSON Pointer path
let escaped_path: Vec<String> = path.iter().map(|seg| escape_json_pointer(seg)).collect();
// Always add the root subject to the path.
let json_pointer = format!(
"/{}/{}",
escape_json_pointer(&tracked_subject.subject_iri),
escaped_path.join("/")
);
// Create the JSON pointer path
let json_pointer = if escaped_path.is_empty() {
// For root object operations (no path elements), just use the subject IRI
format!("/{}", escape_json_pointer(&tracked_subject.subject_iri))
} else {
// For nested operations, include both subject and path
format!(
"/{}/{}",
escape_json_pointer(&tracked_subject.subject_iri),
escaped_path.join("/")
)
};
// Create the patch for the actual value change
patches.push(OrmPatch {
@ -473,16 +516,17 @@ fn build_path_to_root_and_create_patches(
});
// If the subject is newly valid, now we have the full path to queue its creation.
if tracked_subject.prev_valid != OrmTrackedSubjectValidity::Valid {
if *prev_valid != OrmTrackedSubjectValidity::Valid {
let mut final_path = vec![tracked_subject.subject_iri.clone()];
final_path.extend_from_slice(path);
queue_patches_for_newly_valid_subject(
queue_objects_to_create(
tracked_subject,
tracked_subjects,
root_shape,
&final_path,
patches,
objects_to_create,
orm_changes,
child_iri,
);
}
@ -505,6 +549,15 @@ fn build_path_to_root_and_create_patches(
diff_op.clone(),
patches,
objects_to_create,
prev_valid,
orm_changes,
child_iri,
);
} else {
log_debug!(
" - build_path_segment_for_parent returned None for parent: {}, child: {}",
parent_ts.subject_iri,
tracked_subject.subject_iri
);
}
}

@ -24,10 +24,7 @@ use crate::types::GraphQuadsPatch;
use crate::verifier::*;
impl Verifier {
/// After creating new objects (without an id) in JS-land,
/// we send the generated id for those back.
/// If something went wrong (revert_inserts / revert_removes not empty),
/// we send a JSON patch back to revert the made changes.
///
pub(crate) async fn orm_update_self(
&mut self,
scope: &NuriV0,
@ -48,10 +45,10 @@ impl Verifier {
inserts: revert_removes,
removes: revert_inserts,
};
log_info!("[orm_frontend_update] Reverting");
// TODO: Call with correct params.
// self.orm_backend_update(session_id, scope, "", revert_changes)
log_info!("[orm_update_self] Reverting triples, calling orm_backend_update. TODO");
// TODO
// self.orm_backend_update(session_id, scope, "", revert_changes);
log_info!("[orm_update_self] Triples reverted.");
}
Ok(())
@ -151,20 +148,20 @@ fn create_sparql_update_query_for_diff(
delete_patches.len()
);
let add_object_patches: Vec<_> = diff
.iter()
.filter(|patch| {
patch.op == OrmPatchOp::add
&& match &patch.valType {
Some(vt) => *vt == OrmPatchType::object,
_ => false,
}
})
.collect();
log_info!(
"[create_sparql_update_query_for_diff] Found {} add object patches",
add_object_patches.len()
);
// let add_object_patches: Vec<_> = diff
// .iter()
// .filter(|patch| {
// patch.op == OrmPatchOp::add
// && match &patch.valType {
// Some(vt) => *vt == OrmPatchType::object,
// _ => false,
// }
// })
// .collect();
// log_info!(
// "[create_sparql_update_query_for_diff] Found {} add object patches",
// add_object_patches.len()
// );
let add_primitive_patches: Vec<_> = diff
.iter()
@ -235,17 +232,6 @@ fn create_sparql_update_query_for_diff(
);
}
// Process add object patches (might need blank nodes)
//
for (idx, _add_obj_patch) in add_object_patches.iter().enumerate() {
log_info!("[create_sparql_update_query_for_diff] Processing add object patch {}/{} (NOT YET IMPLEMENTED)", idx + 1, add_object_patches.len());
// Creating objects without an id field is only supported in one circumstance:
// An object is added to a property which has a max cardinality of one, e.g. `painting.artist`.
// In that case, we create a blank node.
// TODO: We need to set up a list of created blank nodes and where they belong to.
// POTENTIAL PANIC SOURCE: This is not implemented yet
}
// Process primitive add patches
//
for (idx, add_patch) in add_primitive_patches.iter().enumerate() {
@ -259,7 +245,6 @@ fn create_sparql_update_query_for_diff(
let mut var_counter: i32 = 0;
// Create WHERE statements from path.
// POTENTIAL PANIC SOURCE: create_where_statements_for_patch can panic in several places
let (where_statements, target, pred_schema) =
create_where_statements_for_patch(&add_patch, &mut var_counter, &orm_subscription);
let (subject_var, target_predicate, target_object) = target;
@ -434,7 +419,6 @@ fn create_where_statements_for_patch(
path.len()
);
// POTENTIAL PANIC SOURCE: find_pred_schema_by_name can panic
log_info!(
"[create_where_statements_for_patch] Looking up predicate schema for name={}",
pred_name
@ -505,7 +489,6 @@ fn create_where_statements_for_patch(
);
}
// POTENTIAL PANIC SOURCE: get_first_child_schema can panic
log_info!(
"[create_where_statements_for_patch] Getting child schema for object_iri={}",
object_iri
@ -528,7 +511,6 @@ fn create_where_statements_for_patch(
// As long as there is only one allowed shape or the first one is valid, this is fine.
log_info!("[create_where_statements_for_patch] Predicate is single-valued, getting child schema");
// POTENTIAL PANIC SOURCE: get_first_child_schema can panic
current_subj_schema = get_first_child_schema(None, &pred_schema, &orm_subscription);
log_info!("[create_where_statements_for_patch] Child schema found");
}

@ -162,7 +162,7 @@ impl Verifier {
// Mark as currently validating
currently_validating.insert(validation_key.clone());
// Get triples of subject (added & removed).
// Get triple changes for subject (added & removed).
let triples_added_for_subj = added_triples_by_subject
.get(*subject_iri)
.map(|v| v.as_slice())
@ -177,31 +177,36 @@ impl Verifier {
.entry(shape.iri.clone())
.or_insert_with(HashMap::new)
.entry((*subject_iri).clone())
.or_insert_with(|| OrmTrackedSubjectChange {
subject_iri: (*subject_iri).clone(),
predicates: HashMap::new(),
data_applied: false,
});
.or_insert_with(|| {
// Create a new change record.
// This includes the previous validity and triple changes.
let orm_subscription = self
.orm_subscriptions
.get_mut(nuri)
.unwrap()
.iter_mut()
.find(|sub| {
sub.shape_type.shape == *root_shape_iri
&& sub.session_id == session_id
})
.unwrap();
// Apply all triples for that subject to the tracked (shape, subject) pair.
// Record the changes.
{
let orm_subscription = self
.orm_subscriptions
.get_mut(nuri)
.unwrap()
.iter_mut()
.find(|sub| {
sub.shape_type.shape == *root_shape_iri && sub.session_id == session_id
})
.unwrap();
log_debug!("[process_changes_for_shape_and_session] Creating change object for {}, {}", subject_iri, shape.iri);
let prev_valid = match orm_subscription
.tracked_subjects
.get(*subject_iri)
.and_then(|shapes| shapes.get(&shape.iri))
{
Some(tracked_subject) => tracked_subject.read().unwrap().valid.clone(),
None => OrmTrackedSubjectValidity::Pending,
};
// Update tracked subjects and modify change objects.
if !change.data_applied {
log_debug!(
"Adding triples to change tracker for subject {}",
subject_iri
);
let mut change = OrmTrackedSubjectChange {
subject_iri: (*subject_iri).clone(),
predicates: HashMap::new(),
is_validated: false,
prev_valid,
};
if let Err(e) = add_remove_triples(
shape.clone(),
@ -209,46 +214,31 @@ impl Verifier {
triples_added_for_subj,
triples_removed_for_subj,
orm_subscription,
change,
&mut change,
) {
log_err!("apply_changes_from_triples add/remove error: {:?}", e);
panic!();
}
change.data_applied = true;
}
// Check if this is the first evaluation round - In that case, set old validity to new one.
// if the object was already validated, don't do so again.
{
let tracked_subject = &mut orm_subscription
.tracked_subjects
.get(*subject_iri)
.unwrap()
.get(&shape.iri)
.unwrap()
.write()
.unwrap();
// First run
if !change.data_applied
&& tracked_subject.valid != OrmTrackedSubjectValidity::Pending
{
tracked_subject.prev_valid = tracked_subject.valid.clone();
}
if change.data_applied {
log_debug!("not applying triples again for subject {subject_iri}");
change
});
// Has this subject already been validated?
if change.data_applied
&& tracked_subject.valid != OrmTrackedSubjectValidity::Pending
{
log_debug!("Not evaluating subject again {subject_iri}");
// If validation took place already, there's nothing more to do...
if change.is_validated {
continue;
}
continue;
}
}
}
// Run validation and record objects that need to be re-evaluated.
{
let orm_subscription = self
.orm_subscriptions
.get_mut(nuri)
.unwrap()
.iter_mut()
.find(|sub| {
sub.shape_type.shape == *root_shape_iri && sub.session_id == session_id
})
.unwrap();
// Validate the subject.
// need_eval contains elements in reverse priority (last element to be validated first)
@ -415,4 +405,88 @@ impl Verifier {
Some(subscription) => Ok((subscription.sender.clone(), subscription)),
}
}
pub fn cleanup_tracked_subjects(orm_subscription: &mut OrmSubscription) {
let tracked_subjects = &mut orm_subscription.tracked_subjects;
// First pass: Clean up relationships for subjects being deleted
for (subject_iri, subjects_for_shape) in tracked_subjects.iter() {
for (_shape_iri, tracked_subject_lock) in subjects_for_shape.iter() {
let tracked_subject = tracked_subject_lock.read().unwrap();
// Only process subjects that are marked for deletion
if tracked_subject.valid != OrmTrackedSubjectValidity::ToDelete {
continue;
}
let has_parents = !tracked_subject.parents.is_empty();
// Set all children to `untracked` that don't have other parents
for tracked_predicate in tracked_subject.tracked_predicates.values() {
let tracked_pred_read = tracked_predicate.read().unwrap();
for child in &tracked_pred_read.tracked_children {
let mut tracked_child = child.write().unwrap();
if tracked_child.parents.is_empty()
|| (tracked_child.parents.len() == 1
&& tracked_child
.parents
.contains_key(&tracked_subject.subject_iri))
{
if tracked_child.valid != OrmTrackedSubjectValidity::ToDelete {
tracked_child.valid = OrmTrackedSubjectValidity::Untracked;
}
}
}
}
// Remove this subject from its children's parent lists
// (Only if this is not a root subject - root subjects keep child relationships)
if has_parents {
for tracked_pred in tracked_subject.tracked_predicates.values() {
let tracked_pred_read = tracked_pred.read().unwrap();
for child in &tracked_pred_read.tracked_children {
child.write().unwrap().parents.remove(subject_iri);
}
}
}
// Also remove this subject from its parents' children lists
for (_parent_iri, parent_tracked_subject) in &tracked_subject.parents {
let mut parent_ts = parent_tracked_subject.write().unwrap();
for tracked_pred in parent_ts.tracked_predicates.values_mut() {
let mut tracked_pred_mut = tracked_pred.write().unwrap();
tracked_pred_mut
.tracked_children
.retain(|child| child.read().unwrap().subject_iri != *subject_iri);
}
}
}
}
// Second pass: Collect subjects to remove (we can't remove while iterating)
let mut subjects_to_remove: Vec<(String, String)> = vec![];
for (subject_iri, subjects_for_shape) in tracked_subjects.iter() {
for (shape_iri, tracked_subject) in subjects_for_shape.iter() {
let tracked_subject = tracked_subject.read().unwrap();
// Only cleanup subjects that are marked for deletion
if tracked_subject.valid == OrmTrackedSubjectValidity::ToDelete {
subjects_to_remove.push((subject_iri.clone(), shape_iri.clone()));
}
}
}
// Third pass: Remove the subjects marked for deletion
for (subject_iri, shape_iri) in subjects_to_remove {
if let Some(shapes_map) = tracked_subjects.get_mut(&subject_iri) {
shapes_map.remove(&shape_iri);
// If this was the last shape for this subject, remove the subject entry entirely
if shapes_map.is_empty() {
tracked_subjects.remove(&subject_iri);
}
}
}
}
}

@ -19,7 +19,7 @@ impl Verifier {
/// Might return nested objects that need to be validated.
/// Assumes all triples to be of same subject.
pub fn update_subject_validity(
s_change: &OrmTrackedSubjectChange,
s_change: &mut OrmTrackedSubjectChange,
shape: &OrmSchemaShape,
orm_subscription: &mut OrmSubscription,
) -> Vec<(SubjectIri, ShapeIri, NeedsFetchBool)> {
@ -32,7 +32,7 @@ impl Verifier {
return vec![];
};
let mut tracked_subject = tracked_subject.write().unwrap();
let previous_validity = tracked_subject.prev_valid.clone();
let previous_validity = s_change.prev_valid.clone();
// Keep track of objects that need to be validated against a shape to fetch and validate.
let mut need_evaluation: Vec<(String, String, bool)> = vec![];
@ -348,42 +348,14 @@ impl Verifier {
tracked_subject.valid = new_validity.clone();
if new_validity == OrmTrackedSubjectValidity::Invalid {
// For invalid subjects, we need to to cleanup.
let has_parents = !tracked_subject.parents.is_empty();
if has_parents {
// This object is not a root object. Tracked child objects can be dropped.
// We therefore delete the child -> parent links.
// Untracked objects (with no parents) will be deleted in the subsequent child validation.
for tracked_predicate in tracked_subject.tracked_predicates.values() {
for child in &tracked_predicate.write().unwrap().tracked_children {
child
.write()
.unwrap()
.parents
.remove(&tracked_subject.subject_iri);
}
}
} else {
// This is a root objects, we will set the children to untracked
// but don't delete the child > parent relationship.
}
// First, if we have a definite decision, we set is_validated to true.
if new_validity != OrmTrackedSubjectValidity::Pending {
s_change.is_validated = true;
}
// Set all children to `untracked` that don't have other parents.
for tracked_predicate in tracked_subject.tracked_predicates.values() {
for child in &tracked_predicate.write().unwrap().tracked_children {
let mut tracked_child = child.write().unwrap();
if tracked_child.parents.is_empty()
|| (tracked_child.parents.len() == 1
&& tracked_child
.parents
.contains_key(&tracked_subject.subject_iri))
{
tracked_child.valid = OrmTrackedSubjectValidity::Untracked;
}
}
}
if new_validity == OrmTrackedSubjectValidity::Invalid {
// For invalid subjects, we schedule cleanup.
tracked_subject.valid = OrmTrackedSubjectValidity::ToDelete;
// Add all children to need_evaluation for their cleanup.
for tracked_predicate in tracked_subject.tracked_predicates.values() {
@ -396,9 +368,6 @@ impl Verifier {
));
}
}
// Remove all tracked_predicates.
tracked_subject.tracked_predicates.clear();
} else if new_validity == OrmTrackedSubjectValidity::Valid
&& previous_validity != OrmTrackedSubjectValidity::Valid
{

@ -25,8 +25,6 @@ pub struct OrmTrackedSubject {
pub parents: HashMap<String, Arc<RwLock<OrmTrackedSubject>>>,
/// Validity. When untracked, triple updates are not processed for this tracked subject.
pub valid: OrmTrackedSubjectValidity,
/// Previous validity. Used for validation and creating JSON Patch diffs from changes.
pub prev_valid: OrmTrackedSubjectValidity,
/// Subject IRI
pub subject_iri: String,
/// The shape for which the predicates are tracked.
@ -39,6 +37,7 @@ pub enum OrmTrackedSubjectValidity {
Invalid,
Pending,
Untracked,
ToDelete,
}
#[derive(Clone, Debug)]
@ -60,10 +59,10 @@ pub struct OrmTrackedSubjectChange {
pub subject_iri: String,
/// Predicates that were changed.
pub predicates: HashMap<String, OrmTrackedPredicateChanges>,
/// If the new triples have been added to the tracked predicates
/// (values_added / values_removed) already. This is to prevent
/// double-application.
pub data_applied: bool,
/// If the validation has taken place
pub is_validated: bool,
/// The validity before the new validation.
pub prev_valid: OrmTrackedSubjectValidity,
}
#[derive(Debug)]
pub struct OrmTrackedPredicateChanges {

@ -46,7 +46,7 @@ const title = "Multi-framework app";
<ReactRoot client:only="react" />
</Highlight>
<Highlight svelte>
<!-- <Highlight svelte>
<SvelteRoot client:only />
</Highlight>
</Highlight> -->
</Layout>

@ -1,10 +1,11 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { useShape } from "@ng-org/signals/react";
import flattenObject from "../utils/flattenObject";
import { TestObjectShapeType } from "../../shapes/orm/testShape.shapeTypes";
import { BasicShapeType } from "../../shapes/orm/basic.shapeTypes";
import type { ShapeType } from "@ng-org/shex-orm";
import type { Basic } from "../../shapes/orm/basic.typings";
import { deepSignal, watch } from "@ng-org/alien-deepsignals";
const sparqlExampleData = `
PREFIX ex: <http://example.org/>
@ -72,10 +73,10 @@ INSERT DATA {
export function HelloWorldReact() {
const state = useShape(BasicShapeType);
const objects = [...(state || [])];
// @ts-expect-error
window.reactState = state;
console.log("react state", state);
if (!state) return <div>Loading...</div>;
// Create a table from the state object: One column for keys, one for values, one with an input to change the value.
@ -97,7 +98,7 @@ export function HelloWorldReact() {
</button>
<div>
{state.values()?.map((ormObj) => (
{objects.map((ormObj) => (
<table border={1} cellPadding={5} key={ormObj["@id"]}>
<thead>
<tr>
@ -155,7 +156,7 @@ export function HelloWorldReact() {
value={value}
onChange={(e) => {
setNestedValue(
state,
ormObj,
key,
e.target.value
);
@ -168,7 +169,7 @@ export function HelloWorldReact() {
value={value}
onChange={(e) => {
setNestedValue(
state,
ormObj,
key,
Number(
e.target
@ -184,7 +185,7 @@ export function HelloWorldReact() {
checked={value}
onChange={(e) => {
setNestedValue(
state,
ormObj,
key,
e.target.checked
);
@ -196,11 +197,11 @@ export function HelloWorldReact() {
onClick={() => {
const currentArray =
getNestedValue(
state,
ormObj,
key
);
setNestedValue(
state,
ormObj,
key,
[
...currentArray,
@ -216,7 +217,7 @@ export function HelloWorldReact() {
onClick={() => {
const currentArray =
getNestedValue(
state,
ormObj,
key
);
if (
@ -224,7 +225,7 @@ export function HelloWorldReact() {
0
) {
setNestedValue(
state,
ormObj,
key,
currentArray.slice(
0,
@ -243,7 +244,7 @@ export function HelloWorldReact() {
onClick={() => {
const currentSet =
getNestedValue(
state,
ormObj,
key
);
currentSet.add(
@ -257,7 +258,7 @@ export function HelloWorldReact() {
onClick={() => {
const currentSet =
getNestedValue(
state,
ormObj,
key
);
const lastItem =

@ -2,8 +2,9 @@
import { TestObjectShapeType } from "../../shapes/orm/testShape.shapeTypes";
import { useShape } from "@ng-org/signals/svelte";
import flattenObject from "../utils/flattenObject";
import { BasicShapeType } from "../../shapes/orm/basic.shapeTypes";
const shapeObject = useShape(TestObjectShapeType);
const shapeObjects = useShape(BasicShapeType);
function getNestedValue(obj: any, path: string) {
return path
@ -20,16 +21,16 @@
cur[keys[keys.length - 1]] = value;
}
const flattenedObjects = $derived(
$shapeObject
? $shapeObject.values().map((o) => flattenObject(o)[0] || ({} as any))
$shapeObjects
? $shapeObjects.values().map((o) => flattenObject(o)[0] || ({} as any))
: []
);
$effect(() => {
(window as any).svelteState = $shapeObject;
(window as any).svelteState = $shapeObjects;
});
</script>
{#if $shapeObject}
{#if $shapeObjects}
<div>
<p>Rendered in Svelte</p>
@ -61,28 +62,32 @@
type="text"
{value}
oninput={(e: any) =>
setNestedValue($shapeObject, key, e.target.value)}
setNestedValue($shapeObjects, key, e.target.value)}
/>
{:else if typeof value === "number"}
<input
type="number"
{value}
oninput={(e: any) =>
setNestedValue($shapeObject, key, Number(e.target.value))}
setNestedValue(
$shapeObjects,
key,
Number(e.target.value)
)}
/>
{:else if typeof value === "boolean"}
<input
type="checkbox"
checked={value}
onchange={(e: any) =>
setNestedValue($shapeObject, key, e.target.checked)}
setNestedValue($shapeObjects, key, e.target.checked)}
/>
{:else if Array.isArray(value)}
<div style="display:flex; gap:.5rem;">
<button
onclick={() => {
const cur = getNestedValue($shapeObject, key) || [];
setNestedValue($shapeObject, key, [
const cur = getNestedValue($shapeObjects, key) || [];
setNestedValue($shapeObjects, key, [
...cur,
cur.length + 1,
]);
@ -90,9 +95,9 @@
>
<button
onclick={() => {
const cur = getNestedValue($shapeObject, key) || [];
const cur = getNestedValue($shapeObjects, key) || [];
if (cur.length)
setNestedValue($shapeObject, key, cur.slice(0, -1));
setNestedValue($shapeObjects, key, cur.slice(0, -1));
}}>Remove</button
>
</div>
@ -100,13 +105,19 @@
<div style="display:flex; gap:.5rem;">
<button
onclick={() => {
const cur: Set<any> = getNestedValue($shapeObject, key);
const cur: Set<any> = getNestedValue(
$shapeObjects,
key
);
cur.add(`item${cur.size + 1}`);
}}>Add</button
>
<button
onclick={() => {
const cur: Set<any> = getNestedValue($shapeObject, key);
const cur: Set<any> = getNestedValue(
$shapeObjects,
key
);
const last = Array.from(cur).pop();
if (last !== undefined) cur.delete(last);
}}>Remove</button

@ -3,14 +3,15 @@ import { computed } from "vue";
import { useShape } from "@ng-org/signals/vue";
import flattenObject from "../utils/flattenObject";
import { TestObjectShapeType } from "../../shapes/orm/testShape.shapeTypes";
import { BasicShapeType } from "../../shapes/orm/basic.shapeTypes";
// Acquire deep signal object (proxy) for a shape; scope second arg left empty string for parity
const shapeObjects = useShape(TestObjectShapeType);
const shapeObjects = useShape(BasicShapeType);
// Expose for devtools exploration
// @ts-ignore
window.vueState = shapeObjects;
console.log("vue loaded")
</script>
<template>

@ -6,8 +6,8 @@ import type { Schema } from "@ng-org/shex-orm";
* =============================================================================
*/
export const basicSchema: Schema = {
"http://example.org/Basic": {
iri: "http://example.org/Basic",
"http://example.org/BasicShape": {
iri: "http://example.org/BasicShape",
predicates: [
{
dataTypes: [

@ -5,5 +5,5 @@ import type { Basic } from "./basic.typings";
// ShapeTypes for basic
export const BasicShapeType: ShapeType<Basic> = {
schema: basicSchema,
shape: "http://example.org/Basic",
shape: "http://example.org/BasicShape",
};

@ -14,7 +14,7 @@ export interface Basic {
/**
* Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type
*/
"@type": string;
"@type": "http://example.org/Basic";
/**
* Original IRI: http://example.org/basicString
*/

@ -6,8 +6,8 @@ import type { Schema } from "@ng-org/shex-orm";
* =============================================================================
*/
export const catShapeSchema: Schema = {
"http://example.org/Cat": {
iri: "http://example.org/Cat",
"http://example.org/CatShape": {
iri: "http://example.org/CatShape",
predicates: [
{
dataTypes: [
@ -58,7 +58,7 @@ export const catShapeSchema: Schema = {
dataTypes: [
{
valType: "shape",
shape: "http://example.org/Cat||http://example.org/address",
shape: "http://example.org/CatShape||http://example.org/address",
},
],
maxCardinality: 1,
@ -68,8 +68,8 @@ export const catShapeSchema: Schema = {
},
],
},
"http://example.org/Cat||http://example.org/address": {
iri: "http://example.org/Cat||http://example.org/address",
"http://example.org/CatShape||http://example.org/address": {
iri: "http://example.org/CatShape||http://example.org/address",
predicates: [
{
dataTypes: [

@ -5,5 +5,5 @@ import type { Cat } from "./catShape.typings";
// ShapeTypes for catShape
export const CatShapeType: ShapeType<Cat> = {
schema: catShapeSchema,
shape: "http://example.org/Cat",
shape: "http://example.org/CatShape",
};

@ -14,7 +14,7 @@ export interface Cat {
/**
* Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type
*/
"@type": string;
"@type": "http://example.org/Cat";
/**
* Original IRI: http://example.org/name
*/

@ -6,8 +6,8 @@ import type { Schema } from "@ng-org/shex-orm";
* =============================================================================
*/
export const personShapeSchema: Schema = {
"http://example.org/Person": {
iri: "http://example.org/Person",
"http://example.org/PersonShape": {
iri: "http://example.org/PersonShape",
predicates: [
{
dataTypes: [
@ -36,7 +36,7 @@ export const personShapeSchema: Schema = {
dataTypes: [
{
valType: "shape",
shape: "http://example.org/Person||http://example.org/address",
shape: "http://example.org/PersonShape||http://example.org/address",
},
],
maxCardinality: 1,
@ -68,8 +68,8 @@ export const personShapeSchema: Schema = {
},
],
},
"http://example.org/Person||http://example.org/address": {
iri: "http://example.org/Person||http://example.org/address",
"http://example.org/PersonShape||http://example.org/address": {
iri: "http://example.org/PersonShape||http://example.org/address",
predicates: [
{
dataTypes: [

@ -5,5 +5,5 @@ import type { Person } from "./personShape.typings";
// ShapeTypes for personShape
export const PersonShapeType: ShapeType<Person> = {
schema: personShapeSchema,
shape: "http://example.org/Person",
shape: "http://example.org/PersonShape",
};

@ -14,7 +14,7 @@ export interface Person {
/**
* Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type
*/
"@type": string;
"@type": "http://example.org/Person";
/**
* Original IRI: http://example.org/name
*/

@ -6,8 +6,8 @@ import type { Schema } from "@ng-org/shex-orm";
* =============================================================================
*/
export const testShapeSchema: Schema = {
"http://example.org/TestObject": {
iri: "http://example.org/TestObject",
"http://example.org/TestObjectShape": {
iri: "http://example.org/TestObjectShape",
predicates: [
{
dataTypes: [
@ -71,7 +71,7 @@ export const testShapeSchema: Schema = {
{
valType: "shape",
shape:
"http://example.org/TestObject||http://example.org/objectValue",
"http://example.org/TestObjectShape||http://example.org/objectValue",
},
],
maxCardinality: 1,
@ -84,7 +84,7 @@ export const testShapeSchema: Schema = {
{
valType: "shape",
shape:
"http://example.org/TestObject||http://example.org/anotherObject",
"http://example.org/TestObjectShape||http://example.org/anotherObject",
},
],
maxCardinality: -1,
@ -120,8 +120,8 @@ export const testShapeSchema: Schema = {
},
],
},
"http://example.org/TestObject||http://example.org/objectValue": {
iri: "http://example.org/TestObject||http://example.org/objectValue",
"http://example.org/TestObjectShape||http://example.org/objectValue": {
iri: "http://example.org/TestObjectShape||http://example.org/objectValue",
predicates: [
{
dataTypes: [
@ -158,8 +158,8 @@ export const testShapeSchema: Schema = {
},
],
},
"http://example.org/TestObject||http://example.org/anotherObject": {
iri: "http://example.org/TestObject||http://example.org/anotherObject",
"http://example.org/TestObjectShape||http://example.org/anotherObject": {
iri: "http://example.org/TestObjectShape||http://example.org/anotherObject",
predicates: [
{
dataTypes: [

@ -5,5 +5,5 @@ import type { TestObject } from "./testShape.typings";
// ShapeTypes for testShape
export const TestObjectShapeType: ShapeType<TestObject> = {
schema: testShapeSchema,
shape: "http://example.org/TestObject",
shape: "http://example.org/TestObjectShape",
};

@ -14,7 +14,7 @@ export interface TestObject {
/**
* Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type
*/
"@type": string;
"@type": "http://example.org/TestObject";
/**
* Original IRI: http://example.org/stringValue
*/

@ -1,7 +1,7 @@
PREFIX ex: <http://example.org/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
ex:Basic {
ex:BasicShape {
a [ ex:Basic ] ;
ex:basicString xsd:string ;
}

@ -1,7 +1,7 @@
PREFIX ex: <http://example.org/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
ex:Cat {
ex:CatShape {
a [ ex:Cat ] ;
ex:name xsd:string ;
ex:age xsd:integer ;

@ -1,7 +1,7 @@
PREFIX ex: <http://example.org/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
ex:Person {
ex:PersonShape {
a [ ex:Person ] ;
ex:name xsd:string ;
ex:address {

@ -1,7 +1,7 @@
PREFIX ex: <http://example.org/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
ex:TestObject EXTRA a {
ex:TestObjectShape EXTRA a {
a [ ex:TestObject ] ;
ex:stringValue xsd:string ;
ex:numValue xsd:integer ;

@ -1,542 +0,0 @@
export {};
/**
* Structure for expressing a Shape Expression schema.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapes-schema">ShEx Schema definition</a>
*/
export interface Schema {
/**
* Mandatory type "Schema".
*/
type: "Schema";
/**
* JSON-LD <a href="https://www.w3.org/TR/json-ld11/#the-context">@context</a> for ShEx.
*/
"@context"?: "http://www.w3.org/ns/shex.jsonld" | undefined;
/**
* List of semantic actions to be executed when evaluating conformance.
*/
startActs?: SemAct[] | undefined;
/**
* Identifies default starting shape expression.
*/
start?: shapeExprOrRef | undefined;
/**
* List of ShEx schemas to <a href="http://shex.io/shex-semantics/#import">import</a> when processing this schema.
*/
imports?: IRIREF[] | undefined;
/**
* The list of {@link ShapeDecl}s defined in this schema. Each MUST include and {@link ShapeOr#id}.
*/
shapes?: ShapeDecl[] | undefined;
}
export interface semactsAndAnnotations {
/**
* List of semantic actions to be executed when evaluating conformance.
*/
semActs?: SemAct[] | undefined;
/**
* List of {@link SemAct#predicate}/{@link SemAct#object} annotations.
*/
annotations?: Annotation[] | undefined;
}
/**
* A declaration for a shapeExpr with added inheritance constraints.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapedecl">ShEx ShapeDecl definition</a>
*/
export interface ShapeDecl {
/**
* Mandatory type "ShapeDecl".
*/
type: "ShapeDecl";
/**
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
id: shapeDeclLabel;
/**
* Whether this ShapeDecl participates in <a href="http://shex.io/shex-semantics/#dfn-inheritanceSubstitution">inheritance substitution</a>.
*/
abstract?: BOOL | undefined;
/**
* The list of {@link shapeExprOrRef}s that a neighborhood MUST conform to in order to conform to this ShapeDecl.
*/
restricts?: shapeExprOrRef[] | undefined;
/**
* The {@link shapeExpr} to which this neighborhood MUST also conform.
*/
shapeExpr: shapeExpr;
}
/**
* Union of shape expression types.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeexpr">ShEx shapeExpr definition</a>
*/
export type shapeExpr = ShapeOr | ShapeAnd | ShapeNot | NodeConstraint | Shape | ShapeExternal;
/**
* Union of shapeExpr and shapeDeclRef.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeexpr">ShEx shapeExpr definition</a>
*/
export type shapeExprOrRef = shapeExpr | shapeDeclRef;
/**
* A non-exclusive choice of shape expressions; considered conformant if any of {@link #shapeExprs} conforms.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeor">ShEx shapeExpr definition</a>
*/
export interface ShapeOr {
/**
* Mandatory type "ShapeOr".
*/
type: "ShapeOr";
/**
* List of two or more {@link shapeExprOrRef}s in this disjunction.
*/
shapeExprs: shapeExprOrRef[];
}
/**
* A conjunction of shape expressions; considered conformant if each conjunct conforms.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeor">ShEx shapeExpr definition</a>
*/
export interface ShapeAnd {
/**
* Mandatory type "ShapeAnd".
*/
type: "ShapeAnd";
/**
* List of two or more {@link shapeExprOrRef}s in this conjunction.
*/
shapeExprs: shapeExprOrRef[];
}
/**
* A negated shape expressions; considered conformant if {@link #shapeExpr} is not conformant.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapenot">ShEx shapeExpr definition</a>
*/
export interface ShapeNot {
/**
* Mandatory type "ShapeNot".
*/
type: "ShapeNot";
/**
* The {@link shapeExprOrRef} that must be non-conformant for this shape expression to be conformant.
*/
shapeExpr: shapeExprOrRef;
}
/**
* A shape expression not defined in this schema or in any imported schema. The definition of this shape expression is NOT defined by ShEx.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeexternal">ShEx shapeExpr definition</a>
*/
export interface ShapeExternal {
/**
* Mandatory type "ShapeExternal".
*/
type: "ShapeExternal";
}
/**
* A reference a shape expression.
* The reference is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
* This is modified to also include the possibility of ShapeDecl
*/
export type shapeDeclRef = shapeDeclLabel | ShapeDecl;
/**
* An identifier for a shape expression.
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
export type shapeDeclLabel = IRIREF | BNODE;
export type nodeKind = "iri" | "bnode" | "nonliteral" | "literal";
/**
* A collection of constraints on <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-node">RDF Term</a>s expected for conformance.
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
export interface NodeConstraint extends xsFacets, semactsAndAnnotations {
/**
* Mandatory type "NodeConstraint".
*/
type: "NodeConstraint";
/**
* Type of <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-node">RDF Term</a> expected for a conformant RDF node.
* @see <a href="http://shex.io/shex-semantics/#nodeKind">ShEx nodeKind definition</a>
*/
nodeKind?: nodeKind | undefined;
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri">RDF Literal datatype IRITerm</a> expected for a conformant RDF node.
* @see <a href="http://shex.io/shex-semantics/#datatype">ShEx datatype definition</a>
*/
datatype?: IRIREF | undefined;
/**
* The set of permissible values.
* @see <a href="http://shex.io/shex-semantics/#values">ShEx values definition</a>
*/
values?: valueSetValue[] | undefined;
}
/**
* The set of XML Schema Facets supported in ShEx; defers to {@link stringFacets} and {@link numericFacets}.
* @see <a href="http://shex.io/shex-semantics/#xs-string">ShEx String Facet Constraints</a> and <a href="http://shex.io/shex-semantics/#xs-numeric">ShEx Numeric Facet Constraints</a>.
*/
export interface xsFacets extends stringFacets, numericFacets {
}
/**
* The set of <a href="https://www.w3.org/TR/xmlschema-2/#facets">XML Schema Facets</a> applying to <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form">lexical forms of RDF terms</a>.
* @see <a href="http://shex.io/shex-semantics/#xs-string">ShEx String Facet Constraints</a>.
*/
export interface stringFacets {
/**
* Expected length of the lexical form of an RDF Term.
*/
length?: INTEGER | undefined;
/**
* Expected minimum length of the lexical form of an RDF Term.
*/
minlength?: INTEGER | undefined;
/**
* Expected maximum length of the lexical form of an RDF Term.
*/
maxlength?: INTEGER | undefined;
/**
* Regular expression which the lexical forn of an RDF Term must match.
*/
pattern?: STRING | undefined;
/**
* Optional flags for the regular expression in {@link pattern}.
*/
flags?: STRING | undefined;
}
/**
* The set of <a href="https://www.w3.org/TR/xmlschema-2/#facets">XML Schema Facets</a> applying to <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-value-space">numeric values of RDF terms</a>.
* @see <a href="http://shex.io/shex-semantics/#xs-numeric">ShEx Numeric Facet Constraints</a>.
*/
export interface numericFacets {
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value <= {@link mininclusive}.
*/
mininclusive?: numericLiteral | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value < {@link minexclusive}.
*/
minexclusive?: numericLiteral | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value > {@link maxinclusive}.
*/
maxinclusive?: numericLiteral | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value >= {@link maxexclusive}.
*/
maxexclusive?: numericLiteral | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value whose canonical form has {@link totaldigits} digits.
* @see <a href="http://shex.io/shex-semantics/#nodeSatisfies-totaldigits">ShEx totalDigits definition</a>
*/
totaldigits?: INTEGER | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value whose canonical form has {@link fractiondigits} digits.
* @see <a href="http://shex.io/shex-semantics/#nodeSatisfies-fractiondigits">ShEx fractionDigits definition</a>
*/
fractiondigits?: INTEGER | undefined;
}
/**
* Union of numeric types in ShEx used in {@link numericFacets}s.
*/
export type numericLiteral = INTEGER | DECIMAL | DOUBLE;
/**
* Union of numeric types that may appear in a value set.
* @see {@link NodeConstraint#values}.
*/
export type valueSetValue = objectValue | IriStem | IriStemRange | LiteralStem | LiteralStemRange | Language | LanguageStem | LanguageStemRange;
/**
* JSON-LD representation of a URL or a Literal.
*/
export type objectValue = IRIREF | ObjectLiteral;
/**
* A <a href="https://www.w3.org/TR/json-ld11/#value-objects">JSON-LD Value Object</a> used to express an <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a>.
*/
export interface ObjectLiteral {
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form">lexical form</a> of an RDF Literal.
*/
value: STRING;
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag">language tag</a> of an RDF Literal.
*/
language?: STRING | undefined;
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-datatype">datatype</a> of an RDF Literal.
*/
type?: STRING | undefined;
}
/**
* Matchs an <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-iri">RDF IRI</a> starting with the character sequence in {@link stem}.
*/
export interface IriStem {
/**
* Mandatory type "IriStem".
*/
type: "IriStem";
/**
* substring of IRI to be matched.
*/
stem: IRIREF;
}
export type iriRangeStem = IRIREF | Wildcard;
export type iriRangeExclusion = IRIREF | IriStem;
/**
* Filters a matching <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-iri">RDF IRI</a>s through a list of exclusions.
* The initial match is made on an IRI stem per {@link IriStem} or a {@link Wildcard} to accept any IRI.
* The {@link exclusion}s are either specific IRIs or {@link IRIStem}s.
*/
export interface IriStemRange {
/**
* Mandatory type "IriStemRange".
*/
type: "IriStemRange";
/**
* substring of IRI to be matched or a {@link Wildcard} matching any IRI.
*/
stem: iriRangeStem;
/**
* IRIs or {@link IRIStem}s to exclude.
*/
exclusions: iriRangeExclusion[];
}
/**
* Matchs an <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> starting with the character sequence in {@link stem}.
*/
export interface LiteralStem {
/**
* Mandatory type "LiteralStem".
*/
type: "LiteralStem";
/**
* substring of Literal to be matched.
*/
stem: STRING;
}
export type literalRangeStem = string | Wildcard;
export type literalRangeExclusion = string | LiteralStem;
/**
* Filters a matching <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a>s through a list of exclusions.
* The initial match is made on an Literal stem per {@link LiteralStem} or a {@link Wildcard} to accept any Literal.
* The {@link exclusion}s are either specific Literals or {@link LiteralStem}s.
*/
export interface LiteralStemRange {
/**
* Mandatory type "LiteralStemRange".
*/
type: "LiteralStemRange";
/**
* substring of Literal to be matched or a {@link Wildcard} matching any Literal.
*/
stem: literalRangeStem;
/**
* Literals or {@link LiteralStem}s to exclude.
*/
exclusions: literalRangeExclusion[];
}
/**
* An <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag">RDF Language Tag</a>.
*/
export interface Language {
/**
* Mandatory type "Language".
*/
type: "Language";
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form">lexical representation</a> of an RDF Language Tag.
*/
languageTag: LANGTAG;
}
/**
* Matchs an <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag">RDF Language Tag</a> starting with the character sequence in {@link stem}.
*/
export interface LanguageStem {
/**
* Mandatory type "LanguageStem".
*/
type: "LanguageStem";
/**
* substring of Language Tag to be matched.
*/
stem: LANGTAG;
}
export type languageRangeStem = string | Wildcard;
export type languageRangeExclusion = string | LanguageStem;
/**
* Filters a matching <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-langugae-tag">RDF Language Tag</a>s through a list of exclusions.
* The initial match is made on an Language Tag stem per {@link Language TagStem} or a {@link Wildcard} to accept any Language Tag.
* The {@link exclusion}s are either specific Language Tags or {@link Language TagStem}s.
*/
export interface LanguageStemRange {
/**
* Mandatory type "LanguageStemRange".
*/
type: "LanguageStemRange";
/**
* substring of Language-Tag to be matched or a {@link Wildcard} matching any Language Tag.
*/
stem: languageRangeStem;
/**
* Language Tags or {@link LanguageStem}s to exclude.
*/
exclusions: languageRangeExclusion[];
}
/**
* An empty object signifying than any item may be matched.
* This is used in {@link IriStemRange}, {@link LiteralStemRange} and {@link LanguageStemRange}.
*/
export interface Wildcard {
/**
* Mandatory type "Wildcard".
*/
type: "Wildcard";
}
/**
* A collection of {@link tripleExpr}s which must be matched by <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-triple">RDF Triple</a>s in conformance data.
*/
export interface Shape extends semactsAndAnnotations {
/**
* Mandatory type "Shape".
*/
type: "Shape";
/**
* Only the predicates mentioned in the {@link expression} may appear in conformant data.
*/
closed?: BOOL | undefined;
/**
* Permit extra triples with these predicates to appear in triples which don't match any {@link TripleConstraint}s mentioned in the {@link expression}.
*/
extra?: IRIREF[] | undefined;
/**
* List of one or more {@link shapeExprOrRef}s that a neighborhood must satisfy in order to conform to this shape.
*/
extends?: shapeExprOrRef[];
/**
* A tree of {@link tripleExpr}s specifying a set triples into or out of conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-node">RDF Nodes</a>.
*/
expression?: tripleExprOrRef | undefined;
}
/**
* Union of triple expression types.
* @see <a href="http://shex.io/shex-semantics/#dfn-tripleexpr">ShEx tripleExpr definition</a>
*/
export type tripleExpr = EachOf | OneOf | TripleConstraint;
/**
* A tripleExpr or a label to one.
* @see <a href="http://shex.io/shex-semantics/#dfn-tripleexpr">ShEx tripleExpr definition</a>
*/
export type tripleExprOrRef = tripleExpr | tripleExprRef;
/**
* Common attributes appearing in every form of {@link tripleExpr}.
*/
export interface tripleExprBase extends semactsAndAnnotations {
/**
* Optional identifier for {@link tripleExpr}s for reference by {@link tripleExprRef}.
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
id?: tripleExprLabel | undefined;
/**
* Minimum number of times matching triples must appear in conformant data.
*/
min?: INTEGER | undefined;
/**
* Maximum number of times matching triples must appear in conformant data.
*/
max?: INTEGER | undefined;
}
/**
* A list of of triple expressions; considered conformant if there is some conforming mapping of the examined triples to the {@link #tripleExprs}.
* @see <a href="http://shex.io/shex-semantics/#dfn-eachof">ShEx EachOf definition</a>
*/
export interface EachOf extends tripleExprBase {
/**
* Mandatory type "EachOf".
*/
type: "EachOf";
expressions: tripleExprOrRef[];
}
/**
* An exclusive choice of triple expressions; considered conformant if exactly one of {@link #shapeExprs} conforms.
* @see <a href="http://shex.io/shex-semantics/#dfn-oneof">ShEx OneOf definition</a>
*/
export interface OneOf extends tripleExprBase {
/**
* Mandatory type "OneOf".
*/
type: "OneOf";
expressions: tripleExprOrRef[];
}
/**
* A template matching a number of triples attached to the node being validated.
*/
export interface TripleConstraint extends tripleExprBase {
/**
* Mandatory type "TripleConstraint".
*/
type: "TripleConstraint";
/**
* If false, the TripleConstraint matches the a triple composed of a focus node, the {@link predicate} and an object matching the (optional) {@link shapeExpr}.
* If true, the TripleConstraint matches the a triple composed of a subject matching the (optional) {@link shapeExpr}, the {@link predicate} and focus node.
*/
inverse?: BOOL | undefined;
/**
* The predicate expected in a matching <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-triple">RDF Triple</a>.
*/
predicate: IRIREF;
/**
* A {@link shapeExpr} matching a conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-triple">RDF Triple</a>s subject or object, depending on the value of {@link inverse}.
*/
valueExpr?: shapeExprOrRef | undefined;
/**
* The property name used for creating the JSON object.
*/
readablePredicate: string;
}
/**
* A reference a triple expression.
* The reference is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
export type tripleExprRef = tripleExprLabel;
/**
* An identifier for a triple expression.
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
export type tripleExprLabel = IRIREF | BNODE;
/**
* An extension point for Shape Expressions allowing external code to be invoked during validation.
*/
export interface SemAct {
/**
* Mandatory type "SemAct".
*/
type: "SemAct";
name: IRIREF;
code?: STRING | undefined;
}
/**
* An assertion about some part of a ShEx schema which has no affect on conformance checking.
* These can be useful for documentation, provenance tracking, form generation, etch.
*/
export interface Annotation {
/**
* Mandatory type "Annotation".
*/
type: "Annotation";
/**
* The <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">RDF Predicate</a> of the annotation.
*/
predicate: IRI;
/**
* A value for the above {@link predicate}.
*/
object: objectValue;
}
export type IRIREF = string;
export type BNODE = string;
export type INTEGER = number;
export type STRING = string;
export type DECIMAL = number;
export type DOUBLE = number;
export type LANGTAG = string;
export type BOOL = boolean;
export type IRI = string;
//# sourceMappingURL=ShexJTypes.d.ts.map

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
export {};

@ -1,8 +0,0 @@
interface BuildOptions {
input: string;
output: string;
baseIRI?: string;
}
export declare function build({ input: inputFile, output: outputFile, baseIRI, }: BuildOptions): Promise<void>;
export {};
//# sourceMappingURL=build.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAiBA,UAAU,YAAY;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,KAAK,CAAC,EACxB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,UAAU,EAClB,OAAyC,GAC5C,EAAE,YAAY,iBAmEd"}

@ -1,62 +0,0 @@
import fs from "fs-extra";
import path from "path";
import parser from "@shexjs/parser";
import { shexJConverter } from "./schema-converter/converter.js";
import { renderFile } from "ejs";
import prettier from "prettier";
import loading from "loading-cli";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { forAllShapes } from "./util/forAllShapes.js";
import annotateReadablePredicates from "./schema-converter/util/annotateReadablePredicates.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const __dirname = dirname(fileURLToPath(import.meta.url));
export async function build({ input: inputFile, output: outputFile, baseIRI = "https://nextgraph.org/shapes#", }) {
const load = loading("Preparing Environment");
load.start();
// Prepare new folder by clearing/and/or creating it
if (fs.existsSync(outputFile)) {
await fs.promises.rm(outputFile, { recursive: true });
}
await fs.promises.mkdir(outputFile);
const fileTemplates = [];
// Pre-annotate schema with readablePredicate to unify naming across outputs
fileTemplates.push("schema", "typings", "shapeTypes");
load.text = "Generating Schema Documents";
await forAllShapes(inputFile, async (fileName, shexC) => {
// Convert to ShexJ
let schema;
try {
// Prase Shex schema to JSON.
// TODO: Do we need the base IRI?
// @ts-ignore ...
schema = parser.construct(baseIRI).parse(shexC);
}
catch (err) {
const errMessage = err instanceof Error
? err.message
: typeof err === "string"
? err
: "Unknown Error";
console.error(`Error processing ${fileName}: ${errMessage}`);
return;
}
// Add readable predicates to schema as the single source of truth.
// @ts-ignore ...
annotateReadablePredicates(schema);
const [typings, compactSchema] = await shexJConverter(schema);
await Promise.all(fileTemplates.map(async (templateName) => {
const finalContent = await renderFile(path.join(__dirname, "schema-converter", "templates", `${templateName}.ejs`), {
typings: typings.typings,
fileName,
schema: JSON.stringify(schema, null, 2),
compactSchema: JSON.stringify(compactSchema, null, 2),
});
await fs.promises.writeFile(path.join(outputFile, `${fileName}.${templateName}.ts`), await prettier.format(finalContent, {
parser: "typescript",
}));
}));
});
load.stop();
}

@ -1,3 +0,0 @@
#!/usr/bin/env node
export {};
//# sourceMappingURL=cli.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}

@ -1,15 +0,0 @@
#!/usr/bin/env node
import { program } from "commander";
import { build } from "./build.js";
program
.name("NG-ORM")
.description("CLI to some JavaScript string utilities")
.version("0.1.0");
program
.command("build")
.description("Build contents of a shex folder into Shape Types")
.option("-i, --input <inputPath>", "Provide the input path", "./.shapes")
.option("-o, --output <outputPath>", "Provide the output path", "./.orm")
.option("-b, --baseIRI <baseIri>", "The base IRI for anonymous shapes", "https://nextgraph.org/shapes#")
.action(build);
program.parse();

@ -1,2 +0,0 @@
export * from "../src/types.ts";
//# sourceMappingURL=index.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}

@ -1 +0,0 @@
export * from "./types.js";

@ -1,12 +0,0 @@
import type { Schema } from "@ldo/traverser-shexj";
import * as dom from "dts-dom";
import type { Schema as ShapeSchema } from "../types.ts";
export interface TypingReturn {
typingsString: string;
typings: {
typingString: string;
dts: dom.TopLevelDeclaration;
}[];
}
export declare function shexJConverter(shexj: Schema): Promise<[TypingReturn, ShapeSchema]>;
//# sourceMappingURL=converter.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../../src/schema-converter/converter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,KAAK,GAAG,MAAM,SAAS,CAAC;AAM/B,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAS,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,YAAY;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,GAAG,EAAE,GAAG,CAAC,mBAAmB,CAAC;KAChC,EAAE,CAAC;CACP;AAED,wBAAsB,cAAc,CAChC,KAAK,EAAE,MAAM,GACd,OAAO,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAkDtC"}

@ -1,69 +0,0 @@
import { jsonld2graphobject } from "jsonld2graphobject";
import * as dom from "dts-dom";
import { ShexJTypingTransformerCompact, additionalCompactEnumAliases, } from "./transformers/ShexJTypingTransformer.js";
import { ShexJSchemaTransformerCompact } from "./transformers/ShexJSchemaTransformer.js";
export async function shexJConverter(shexj) {
// Prepare processed schema (names still rely on context visitor)
const processedShexj = (await jsonld2graphobject({
...shexj,
"@id": "SCHEMA",
"@context": "http://www.w3.org/ns/shex.jsonld",
}, "SCHEMA"));
additionalCompactEnumAliases.clear();
const declarations = await ShexJTypingTransformerCompact.transform(processedShexj, "Schema", null);
const compactSchemaShapesUnflattened = await ShexJSchemaTransformerCompact.transform(processedShexj, "Schema", null);
const compactSchema = flattenSchema(compactSchemaShapesUnflattened);
// Append only enum aliases (no interface Id aliases in compact format now)
const hasName = (d) => typeof d.name === "string";
additionalCompactEnumAliases.forEach((alias) => {
const exists = declarations.some((d) => hasName(d) && d.name === alias);
if (!exists)
declarations.push(dom.create.alias(alias, dom.create.namedTypeReference("IRI")));
});
const typings = declarations.map((declaration) => ({
typingString: dom
.emit(declaration, {
rootFlags: dom.ContextFlags.InAmbientNamespace,
})
.replace(/\r\n/g, "\n"),
dts: declaration,
}));
const header = `export type IRI = string;\n\n`;
const typingsString = header + typings.map((t) => `export ${t.typingString}`).join("");
return [{ typingsString, typings }, compactSchema];
}
/** Shapes may be nested. Put all to their root and give nested ones ids. */
function flattenSchema(shapes) {
let schema = {};
for (const shape of shapes) {
schema[shape.iri] = shape;
// Find nested, unflattened (i.e. anonymous) schemas in predicates' dataTypes.
for (const pred of shape.predicates) {
for (let i = 0; i < pred.dataTypes.length; i++) {
const dt = pred.dataTypes[i];
if (dt.valType === "shape" &&
typeof dt.shape === "object" &&
dt.shape !== null) {
// create a deterministic id for the nested shape; include index if multiple shape entries exist
const shapeCount = pred.dataTypes.filter((d) => d.valType === "shape").length;
const newId = shape.iri +
"||" +
pred.iri +
(shapeCount > 1 ? `||${i}` : "");
// Recurse
const flattened = flattenSchema([
{
...dt.shape,
iri: newId,
},
]);
// Replace the nested schema with its new id.
dt.shape = newId;
schema = { ...schema, ...flattened };
}
}
}
// Flatten / Recurse
}
return schema;
}

@ -1,8 +0,0 @@
import type { Schema } from "@ng-org/shex-orm";
/**
* =============================================================================
* <%- fileName %>Schema: Schema for <%- fileName %>
* =============================================================================
*/
export const <%- fileName %>Schema: Schema = <%- compactSchema %>;

@ -1,14 +0,0 @@
import type { ShapeType } from "@ng-org/shex-orm";
import { <%- fileName %>Schema } from "./<%- fileName %>.schema";
import type {
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%>
<%- typing.dts.name %>,
<% } }); -%>} from "./<%- fileName %>.typings";
// ShapeTypes for <%- fileName %>
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%>
export const <%- typing.dts.name %>ShapeType: ShapeType<<%- typing.dts.name %>> = {
schema: <%- fileName %>Schema,
shape: "<%- typing.dts.shapeId %>",
};
<% } }); -%>

@ -1,14 +0,0 @@
export type IRI = string;
/**
* =============================================================================
* Typescript Typings for <%- fileName %>
* =============================================================================
*/
<% typings.forEach((typing)=> { -%>
/**
* <%- typing.dts.name %> Type
*/
export <%- typing.typingString -%>
<% }); -%>

@ -1,348 +0,0 @@
import type { Predicate, DataType, Shape } from "../../types.ts";
export declare const ShexJSchemaTransformerCompact: import("@ldo/type-traverser").Transformer<{
Schema: {
kind: "interface";
type: import("@ldo/traverser-shexj").Schema;
properties: {
startActs: "SemAct";
start: "shapeExprOrRef";
imports: "IRIREF";
shapes: "ShapeDecl";
};
};
ShapeDecl: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeDecl;
properties: {
id: "shapeDeclLabel";
abstract: "BOOL";
restricts: "shapeExprOrRef";
shapeExpr: "shapeExpr";
};
};
shapeExpr: {
kind: "union";
type: import("@ldo/traverser-shexj").shapeExpr;
typeNames: "ShapeOr" | "ShapeAnd" | "ShapeNot" | "NodeConstraint" | "Shape" | "ShapeExternal";
};
shapeExprOrRef: {
kind: "union";
type: import("@ldo/traverser-shexj").shapeExprOrRef;
typeNames: "shapeExpr" | "shapeDeclRef";
};
ShapeOr: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeOr;
properties: {
shapeExprs: "shapeExprOrRef";
};
};
ShapeAnd: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeAnd;
properties: {
shapeExprs: "shapeExprOrRef";
};
};
ShapeNot: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeNot;
properties: {
shapeExpr: "shapeExprOrRef";
};
};
ShapeExternal: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeExternal;
properties: Record<string, never>;
};
shapeDeclRef: {
kind: "union";
type: import("@ldo/traverser-shexj").shapeDeclRef;
typeNames: "shapeDeclLabel" | "ShapeDecl";
};
shapeDeclLabel: {
kind: "union";
type: import("@ldo/traverser-shexj").shapeDeclLabel;
typeNames: "IRIREF" | "BNODE";
};
NodeConstraint: {
kind: "interface";
type: import("@ldo/traverser-shexj").NodeConstraint;
properties: {
datatype: "IRIREF";
values: "valueSetValue";
length: "INTEGER";
minlength: "INTEGER";
maxlength: "INTEGER";
pattern: "STRING";
flags: "STRING";
mininclusive: "numericLiteral";
minexclusive: "numericLiteral";
totaldigits: "INTEGER";
fractiondigits: "INTEGER";
semActs: "SemAct";
annotations: "Annotation";
};
};
numericLiteral: {
kind: "union";
type: import("@ldo/traverser-shexj").numericLiteral;
typeNames: "INTEGER" | "DECIMAL" | "DOUBLE";
};
valueSetValue: {
kind: "union";
type: import("@ldo/traverser-shexj").valueSetValue;
typeNames: "objectValue" | "IriStem" | "IriStemRange" | "LiteralStem" | "LiteralStemRange" | "Language" | "LanguageStem" | "LanguageStemRange";
};
objectValue: {
kind: "union";
type: import("@ldo/traverser-shexj").objectValue;
typeNames: "IRIREF" | "ObjectLiteral";
};
ObjectLiteral: {
kind: "interface";
type: import("@ldo/traverser-shexj").ObjectLiteral;
properties: {
value: "STRING";
language: "STRING";
type: "STRING";
};
};
IriStem: {
kind: "interface";
type: import("@ldo/traverser-shexj").IriStem;
properties: {
stem: "IRIREF";
};
};
IriStemRange: {
kind: "interface";
type: import("@ldo/traverser-shexj").IriStemRange;
properties: {
stem: "IRIREF";
exclusions: "IriStemRangeExclusions";
};
};
IriStemRangeExclusions: {
kind: "union";
type: import("@ldo/traverser-shexj").IRIREF | import("@ldo/traverser-shexj").IriStem;
typeNames: "IRIREF" | "IriStem";
};
LiteralStem: {
kind: "interface";
type: import("@ldo/traverser-shexj").LiteralStem;
properties: {
stem: "STRING";
};
};
LiteralStemRange: {
kind: "interface";
type: import("@ldo/traverser-shexj").LiteralStemRange;
properties: {
stem: "LiteralStemRangeStem";
exclusions: "LiteralStemRangeExclusions";
};
};
LiteralStemRangeStem: {
kind: "union";
type: import("@ldo/traverser-shexj").STRING | import("@ldo/traverser-shexj").Wildcard;
typeNames: "STRING" | "Wildcard";
};
LiteralStemRangeExclusions: {
kind: "union";
type: import("@ldo/traverser-shexj").STRING | import("@ldo/traverser-shexj").LiteralStem;
typeNames: "STRING" | "LiteralStem";
};
Language: {
kind: "interface";
type: import("@ldo/traverser-shexj").Language;
properties: {
languageTag: "LANGTAG";
};
};
LanguageStem: {
kind: "interface";
type: import("@ldo/traverser-shexj").LanguageStem;
properties: {
stem: "LANGTAG";
};
};
LanguageStemRange: {
kind: "interface";
type: import("@ldo/traverser-shexj").LanguageStemRange;
properties: {
stem: "LanguageStemRangeStem";
exclusions: "LanguageStemRangeExclusions";
};
};
LanguageStemRangeStem: {
kind: "union";
type: import("@ldo/traverser-shexj").LANGTAG | import("@ldo/traverser-shexj").Wildcard;
typeNames: "LANGTAG" | "Wildcard";
};
LanguageStemRangeExclusions: {
kind: "union";
type: import("@ldo/traverser-shexj").LANGTAG | import("@ldo/traverser-shexj").LanguageStem;
typeNames: "LANGTAG" | "LanguageStem";
};
Wildcard: {
kind: "interface";
type: import("@ldo/traverser-shexj").Wildcard;
properties: Record<string, never>;
};
Shape: {
kind: "interface";
type: import("@ldo/traverser-shexj").Shape;
properties: {
closed: "BOOL";
extra: "IRIREF";
extends: "shapeExprOrRef";
expression: "tripleExprOrRef";
semActs: "SemAct";
annotations: "Annotation";
};
};
tripleExpr: {
kind: "union";
type: import("@ldo/traverser-shexj").tripleExpr;
typeNames: "EachOf" | "OneOf" | "TripleConstraint";
};
tripleExprOrRef: {
kind: "union";
type: import("@ldo/traverser-shexj").tripleExprOrRef;
typeNames: "tripleExpr" | "tripleExprRef";
};
EachOf: {
kind: "interface";
type: import("@ldo/traverser-shexj").EachOf;
properties: {
id: "tripleExprLabel";
min: "INTEGER";
max: "INTEGER";
expressions: "tripleExprOrRef";
semActs: "SemAct";
annotations: "Annotation";
};
};
OneOf: {
kind: "interface";
type: import("@ldo/traverser-shexj").OneOf;
properties: {
id: "tripleExprLabel";
min: "INTEGER";
max: "INTEGER";
expressions: "tripleExprOrRef";
semActs: "SemAct";
annotations: "Annotation";
};
};
TripleConstraint: {
kind: "interface";
type: import("@ldo/traverser-shexj").TripleConstraint;
properties: {
id: "tripleExprLabel";
min: "INTEGER";
max: "INTEGER";
inverse: "BOOL";
predicate: "IRIREF";
valueExpr: "shapeExprOrRef";
semActs: "SemAct";
annotations: "Annotation";
};
};
tripleExprRef: {
kind: "union";
type: import("@ldo/traverser-shexj").tripleExprRef;
typeNames: "tripleExprLabel";
};
tripleExprLabel: {
kind: "union";
type: import("@ldo/traverser-shexj").tripleExprLabel;
typeNames: "IRIREF" | "BNODE";
};
SemAct: {
kind: "interface";
type: import("@ldo/traverser-shexj").SemAct;
properties: {
name: "IRIREF";
code: "STRING";
};
};
Annotation: {
kind: "interface";
type: import("@ldo/traverser-shexj").Annotation;
properties: {
predicate: "IRI";
object: "objectValue";
};
};
IRIREF: {
kind: "primitive";
type: import("@ldo/traverser-shexj").IRIREF;
};
BNODE: {
kind: "primitive";
type: import("@ldo/traverser-shexj").BNODE;
};
INTEGER: {
kind: "primitive";
type: import("@ldo/traverser-shexj").INTEGER;
};
STRING: {
kind: "primitive";
type: import("@ldo/traverser-shexj").STRING;
};
DECIMAL: {
kind: "primitive";
type: import("@ldo/traverser-shexj").DECIMAL;
};
DOUBLE: {
kind: "primitive";
type: import("@ldo/traverser-shexj").DOUBLE;
};
LANGTAG: {
kind: "primitive";
type: import("@ldo/traverser-shexj").LANGTAG;
};
BOOL: {
kind: "primitive";
type: import("@ldo/traverser-shexj").BOOL;
};
IRI: {
kind: "primitive";
type: import("@ldo/traverser-shexj").IRI;
};
}, {
Schema: {
return: Shape[];
};
ShapeDecl: {
return: Shape;
};
Shape: {
return: Shape;
};
EachOf: {
return: Shape;
};
TripleConstraint: {
return: Predicate;
};
NodeConstraint: {
return: DataType;
};
ShapeOr: {
return: DataType[];
};
ShapeAnd: {
return: never;
};
ShapeNot: {
return: never;
};
ShapeExternal: {
return: never;
};
}, null>;
//# sourceMappingURL=ShexJSchemaTransformer.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"ShexJSchemaTransformer.d.ts","sourceRoot":"","sources":["../../../src/schema-converter/transformers/ShexJSchemaTransformer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AA0DjE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAE1B;QAAE,MAAM,EAAE,KAAK,EAAE,CAAA;KAAE;eAChB;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE;WACrB;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE;YAChB;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE;sBACP;QAAE,MAAM,EAAE,SAAS,CAAA;KAAE;oBACvB;QAAE,MAAM,EAAE,QAAQ,CAAA;KAAE;aAC3B;QAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;KAAE;cACrB;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE;cACjB;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE;mBACZ;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE;QAqLtC,CAAC"}

@ -1,208 +0,0 @@
import ShexJTraverser from "@ldo/traverser-shexj";
const rdfDataTypeToBasic = (dataType) => {
switch (dataType) {
case "http://www.w3.org/2001/XMLSchema#string":
case "http://www.w3.org/2001/XMLSchema#ENTITIES":
case "http://www.w3.org/2001/XMLSchema#ENTITY":
case "http://www.w3.org/2001/XMLSchema#ID":
case "http://www.w3.org/2001/XMLSchema#IDREF":
case "http://www.w3.org/2001/XMLSchema#IDREFS":
case "http://www.w3.org/2001/XMLSchema#language":
case "http://www.w3.org/2001/XMLSchema#Name":
case "http://www.w3.org/2001/XMLSchema#NCName":
case "http://www.w3.org/2001/XMLSchema#NMTOKEN":
case "http://www.w3.org/2001/XMLSchema#NMTOKENS":
case "http://www.w3.org/2001/XMLSchema#normalizedString":
case "http://www.w3.org/2001/XMLSchema#QName":
case "http://www.w3.org/2001/XMLSchema#token":
return "string";
case "http://www.w3.org/2001/XMLSchema#date":
case "http://www.w3.org/2001/XMLSchema#dateTime":
case "http://www.w3.org/2001/XMLSchema#duration":
case "http://www.w3.org/2001/XMLSchema#gDay":
case "http://www.w3.org/2001/XMLSchema#gMonth":
case "http://www.w3.org/2001/XMLSchema#gMonthDay":
case "http://www.w3.org/2001/XMLSchema#gYear":
case "http://www.w3.org/2001/XMLSchema#gYearMonth":
case "http://www.w3.org/2001/XMLSchema#time":
return "string";
case "http://www.w3.org/2001/XMLSchema#byte":
case "http://www.w3.org/2001/XMLSchema#decimal":
case "http://www.w3.org/2001/XMLSchema#double":
case "http://www.w3.org/2001/XMLSchema#float":
case "http://www.w3.org/2001/XMLSchema#int":
case "http://www.w3.org/2001/XMLSchema#integer":
case "http://www.w3.org/2001/XMLSchema#long":
case "http://www.w3.org/2001/XMLSchema#negativeInteger":
case "http://www.w3.org/2001/XMLSchema#nonNegativeInteger":
case "http://www.w3.org/2001/XMLSchema#nonPositiveInteger":
case "http://www.w3.org/2001/XMLSchema#positiveInteger":
case "http://www.w3.org/2001/XMLSchema#short":
case "http://www.w3.org/2001/XMLSchema#unsignedLong":
case "http://www.w3.org/2001/XMLSchema#unsignedInt":
case "http://www.w3.org/2001/XMLSchema#unsignedShort":
case "http://www.w3.org/2001/XMLSchema#unsignedByte":
return "number";
case "http://www.w3.org/2001/XMLSchema#boolean":
return "boolean";
case "http://www.w3.org/2001/XMLSchema#hexBinary":
return "string";
case "http://www.w3.org/2001/XMLSchema#anyURI":
return "iri";
default:
return "string";
}
};
export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer({
Schema: {
transformer: async (_schema, getTransformedChildren) => {
const transformedChildren = await getTransformedChildren();
return transformedChildren.shapes || [];
},
},
ShapeDecl: {
transformer: async (shapeDecl, getTransformedChildren) => {
const schema = await getTransformedChildren();
const shape = schema.shapeExpr;
return { ...shape, iri: shapeDecl.id };
},
},
Shape: {
transformer: async (_shape, getTransformedChildren) => {
// TODO: We don't handles those
_shape.closed;
const transformedChildren = await getTransformedChildren();
const compactShape = transformedChildren.expression;
for (const extra of _shape.extra || []) {
const extraPredicate = compactShape.predicates.find((p) => p.iri === extra);
if (extraPredicate)
extraPredicate.extra = true;
}
return compactShape;
},
},
// EachOf contains the `expressions` array of properties (TripleConstraint)
EachOf: {
transformer: async (eachOf, getTransformedChildren) => {
const transformedChildren = await getTransformedChildren();
return {
iri: "",
predicates: transformedChildren.expressions.map(
// We disregard cases where properties are referenced (strings)
// or where they consist of Unions or Intersections (not supported).
(expr) => expr),
};
},
},
TripleConstraint: {
transformer: async (tripleConstraint, getTransformedChildren, _setReturnPointer) => {
const transformedChildren = await getTransformedChildren();
const commonProperties = {
maxCardinality: tripleConstraint.max ?? 1,
minCardinality: tripleConstraint.min ?? 1,
iri: tripleConstraint.predicate,
// @ts-expect-error The ldo library does not have our modded readablePredicate property.
readablePredicate: tripleConstraint.readablePredicate,
};
// Make property based on object type which is either a parsed schema, literal or type.
if (typeof transformedChildren.valueExpr === "string") {
// Reference to nested object
return {
dataTypes: [
{
valType: "shape",
shape: transformedChildren.valueExpr,
},
],
...commonProperties,
};
}
else if (transformedChildren.valueExpr &&
transformedChildren.valueExpr.predicates) {
// Nested object
return {
dataTypes: [
{
valType: "shape",
shape: transformedChildren.valueExpr,
},
],
...commonProperties,
};
}
else if (Array.isArray(transformedChildren.valueExpr)) {
return {
dataTypes: transformedChildren.valueExpr, // DataType[]
...commonProperties,
};
}
else {
// type or literal
const nodeConstraint = transformedChildren.valueExpr;
return {
dataTypes: [
{
valType: nodeConstraint.valType,
literals: nodeConstraint.literals,
},
],
...commonProperties,
};
}
},
},
NodeConstraint: {
transformer: async (nodeConstraint) => {
if (nodeConstraint.datatype) {
return {
valType: rdfDataTypeToBasic(nodeConstraint.datatype),
};
}
if (nodeConstraint.nodeKind) {
// Something reference-like.
return { valType: "iri" };
}
if (nodeConstraint.values) {
return {
valType: "literal",
literals: nodeConstraint.values.map(
// TODO: We do not convert them to number or boolean or lang tag.
// And we don't have an annotation of the literal's type.
// @ts-expect-error
(valueRecord) => valueRecord.value || valueRecord.id),
};
}
// Maybe we should throw instead...
throw {
error: new Error("Could not parse Node Constraint"),
nodeConstraint,
};
},
},
// Transformer from ShapeOr
ShapeOr: {
transformer: async (shapeOr, getTransformedChildren) => {
const { shapeExprs } = await getTransformedChildren();
// Either a shape IRI, a nested shape or a node CompactSchemaValue (node constraint).
return (Array.isArray(shapeExprs) ? shapeExprs : [shapeExprs]);
},
},
// Transformer from ShapeAnd
ShapeAnd: {
transformer: async () => {
throw new Error("ShapeAnd not supported (compact)");
},
},
// Transformer from ShapeNot - not supported.
ShapeNot: {
transformer: async () => {
throw new Error("ShapeNot not supported (compact)");
},
},
// Transformer from ShapeExternal - not supported.
ShapeExternal: {
transformer: async () => {
throw new Error("ShapeExternal not supported (compact)");
},
},
});

@ -1,366 +0,0 @@
import type { Annotation } from "shexj";
import * as dom from "dts-dom";
import type { InterfaceDeclaration } from "dts-dom";
export interface ShapeInterfaceDeclaration extends InterfaceDeclaration {
shapeId?: string;
}
export declare const additionalCompactEnumAliases: Set<string>;
export interface CompactTransformerContext {
getNameFromIri: (iri: string, rdfType?: string) => string;
}
export declare function toCamelCase(text: string): string;
/**
* Name functions
*/
export declare function iriToName(iri: string): string;
export declare function nameFromAnnotationOrId(obj: {
id?: string;
annotations?: Annotation[];
}): string | undefined;
export declare const ShexJTypingTransformerCompact: import("@ldo/type-traverser").Transformer<{
Schema: {
kind: "interface";
type: import("@ldo/traverser-shexj").Schema;
properties: {
startActs: "SemAct";
start: "shapeExprOrRef";
imports: "IRIREF";
shapes: "ShapeDecl";
};
};
ShapeDecl: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeDecl;
properties: {
id: "shapeDeclLabel";
abstract: "BOOL";
restricts: "shapeExprOrRef";
shapeExpr: "shapeExpr";
};
};
shapeExpr: {
kind: "union";
type: import("@ldo/traverser-shexj").shapeExpr;
typeNames: "ShapeOr" | "ShapeAnd" | "ShapeNot" | "NodeConstraint" | "Shape" | "ShapeExternal";
};
shapeExprOrRef: {
kind: "union";
type: import("@ldo/traverser-shexj").shapeExprOrRef;
typeNames: "shapeExpr" | "shapeDeclRef";
};
ShapeOr: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeOr;
properties: {
shapeExprs: "shapeExprOrRef";
};
};
ShapeAnd: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeAnd;
properties: {
shapeExprs: "shapeExprOrRef";
};
};
ShapeNot: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeNot;
properties: {
shapeExpr: "shapeExprOrRef";
};
};
ShapeExternal: {
kind: "interface";
type: import("@ldo/traverser-shexj").ShapeExternal;
properties: Record<string, never>;
};
shapeDeclRef: {
kind: "union";
type: import("@ldo/traverser-shexj").shapeDeclRef;
typeNames: "shapeDeclLabel" | "ShapeDecl";
};
shapeDeclLabel: {
kind: "union";
type: import("@ldo/traverser-shexj").shapeDeclLabel;
typeNames: "IRIREF" | "BNODE";
};
NodeConstraint: {
kind: "interface";
type: import("@ldo/traverser-shexj").NodeConstraint;
properties: {
datatype: "IRIREF";
values: "valueSetValue";
length: "INTEGER";
minlength: "INTEGER";
maxlength: "INTEGER";
pattern: "STRING";
flags: "STRING";
mininclusive: "numericLiteral";
minexclusive: "numericLiteral";
totaldigits: "INTEGER";
fractiondigits: "INTEGER";
semActs: "SemAct";
annotations: "Annotation";
};
};
numericLiteral: {
kind: "union";
type: import("@ldo/traverser-shexj").numericLiteral;
typeNames: "INTEGER" | "DECIMAL" | "DOUBLE";
};
valueSetValue: {
kind: "union";
type: import("@ldo/traverser-shexj").valueSetValue;
typeNames: "objectValue" | "IriStem" | "IriStemRange" | "LiteralStem" | "LiteralStemRange" | "Language" | "LanguageStem" | "LanguageStemRange";
};
objectValue: {
kind: "union";
type: import("@ldo/traverser-shexj").objectValue;
typeNames: "IRIREF" | "ObjectLiteral";
};
ObjectLiteral: {
kind: "interface";
type: import("@ldo/traverser-shexj").ObjectLiteral;
properties: {
value: "STRING";
language: "STRING";
type: "STRING";
};
};
IriStem: {
kind: "interface";
type: import("@ldo/traverser-shexj").IriStem;
properties: {
stem: "IRIREF";
};
};
IriStemRange: {
kind: "interface";
type: import("@ldo/traverser-shexj").IriStemRange;
properties: {
stem: "IRIREF";
exclusions: "IriStemRangeExclusions";
};
};
IriStemRangeExclusions: {
kind: "union";
type: import("@ldo/traverser-shexj").IRIREF | import("@ldo/traverser-shexj").IriStem;
typeNames: "IRIREF" | "IriStem";
};
LiteralStem: {
kind: "interface";
type: import("@ldo/traverser-shexj").LiteralStem;
properties: {
stem: "STRING";
};
};
LiteralStemRange: {
kind: "interface";
type: import("@ldo/traverser-shexj").LiteralStemRange;
properties: {
stem: "LiteralStemRangeStem";
exclusions: "LiteralStemRangeExclusions";
};
};
LiteralStemRangeStem: {
kind: "union";
type: import("@ldo/traverser-shexj").STRING | import("@ldo/traverser-shexj").Wildcard;
typeNames: "STRING" | "Wildcard";
};
LiteralStemRangeExclusions: {
kind: "union";
type: import("@ldo/traverser-shexj").STRING | import("@ldo/traverser-shexj").LiteralStem;
typeNames: "STRING" | "LiteralStem";
};
Language: {
kind: "interface";
type: import("@ldo/traverser-shexj").Language;
properties: {
languageTag: "LANGTAG";
};
};
LanguageStem: {
kind: "interface";
type: import("@ldo/traverser-shexj").LanguageStem;
properties: {
stem: "LANGTAG";
};
};
LanguageStemRange: {
kind: "interface";
type: import("@ldo/traverser-shexj").LanguageStemRange;
properties: {
stem: "LanguageStemRangeStem";
exclusions: "LanguageStemRangeExclusions";
};
};
LanguageStemRangeStem: {
kind: "union";
type: import("@ldo/traverser-shexj").LANGTAG | import("@ldo/traverser-shexj").Wildcard;
typeNames: "LANGTAG" | "Wildcard";
};
LanguageStemRangeExclusions: {
kind: "union";
type: import("@ldo/traverser-shexj").LANGTAG | import("@ldo/traverser-shexj").LanguageStem;
typeNames: "LANGTAG" | "LanguageStem";
};
Wildcard: {
kind: "interface";
type: import("@ldo/traverser-shexj").Wildcard;
properties: Record<string, never>;
};
Shape: {
kind: "interface";
type: import("@ldo/traverser-shexj").Shape;
properties: {
closed: "BOOL";
extra: "IRIREF";
extends: "shapeExprOrRef";
expression: "tripleExprOrRef";
semActs: "SemAct";
annotations: "Annotation";
};
};
tripleExpr: {
kind: "union";
type: import("@ldo/traverser-shexj").tripleExpr;
typeNames: "EachOf" | "OneOf" | "TripleConstraint";
};
tripleExprOrRef: {
kind: "union";
type: import("@ldo/traverser-shexj").tripleExprOrRef;
typeNames: "tripleExpr" | "tripleExprRef";
};
EachOf: {
kind: "interface";
type: import("@ldo/traverser-shexj").EachOf;
properties: {
id: "tripleExprLabel";
min: "INTEGER";
max: "INTEGER";
expressions: "tripleExprOrRef";
semActs: "SemAct";
annotations: "Annotation";
};
};
OneOf: {
kind: "interface";
type: import("@ldo/traverser-shexj").OneOf;
properties: {
id: "tripleExprLabel";
min: "INTEGER";
max: "INTEGER";
expressions: "tripleExprOrRef";
semActs: "SemAct";
annotations: "Annotation";
};
};
TripleConstraint: {
kind: "interface";
type: import("@ldo/traverser-shexj").TripleConstraint;
properties: {
id: "tripleExprLabel";
min: "INTEGER";
max: "INTEGER";
inverse: "BOOL";
predicate: "IRIREF";
valueExpr: "shapeExprOrRef";
semActs: "SemAct";
annotations: "Annotation";
};
};
tripleExprRef: {
kind: "union";
type: import("@ldo/traverser-shexj").tripleExprRef;
typeNames: "tripleExprLabel";
};
tripleExprLabel: {
kind: "union";
type: import("@ldo/traverser-shexj").tripleExprLabel;
typeNames: "IRIREF" | "BNODE";
};
SemAct: {
kind: "interface";
type: import("@ldo/traverser-shexj").SemAct;
properties: {
name: "IRIREF";
code: "STRING";
};
};
Annotation: {
kind: "interface";
type: import("@ldo/traverser-shexj").Annotation;
properties: {
predicate: "IRI";
object: "objectValue";
};
};
IRIREF: {
kind: "primitive";
type: import("@ldo/traverser-shexj").IRIREF;
};
BNODE: {
kind: "primitive";
type: import("@ldo/traverser-shexj").BNODE;
};
INTEGER: {
kind: "primitive";
type: import("@ldo/traverser-shexj").INTEGER;
};
STRING: {
kind: "primitive";
type: import("@ldo/traverser-shexj").STRING;
};
DECIMAL: {
kind: "primitive";
type: import("@ldo/traverser-shexj").DECIMAL;
};
DOUBLE: {
kind: "primitive";
type: import("@ldo/traverser-shexj").DOUBLE;
};
LANGTAG: {
kind: "primitive";
type: import("@ldo/traverser-shexj").LANGTAG;
};
BOOL: {
kind: "primitive";
type: import("@ldo/traverser-shexj").BOOL;
};
IRI: {
kind: "primitive";
type: import("@ldo/traverser-shexj").IRI;
};
}, {
Schema: {
return: dom.TopLevelDeclaration[];
};
ShapeDecl: {
return: dom.InterfaceDeclaration;
};
Shape: {
return: dom.InterfaceDeclaration;
};
EachOf: {
return: dom.ObjectType | dom.InterfaceDeclaration;
};
TripleConstraint: {
return: dom.PropertyDeclaration;
};
NodeConstraint: {
return: dom.Type;
};
ShapeOr: {
return: dom.UnionType;
};
ShapeAnd: {
return: dom.IntersectionType;
};
ShapeNot: {
return: never;
};
ShapeExternal: {
return: never;
};
}, null>;
//# sourceMappingURL=ShexJTypingTransformer.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"ShexJTypingTransformer.d.ts","sourceRoot":"","sources":["../../../src/schema-converter/transformers/ShexJTypingTransformer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,KAAK,GAAG,MAAM,SAAS,CAAC;AAC/B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,yBAA0B,SAAQ,oBAAoB;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,eAAO,MAAM,4BAA4B,aAAoB,CAAC;AAE9D,MAAM,WAAW,yBAAyB;IACtC,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CAC7D;AAeD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,UAOvC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAc7C;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC9B,GAAG,MAAM,GAAG,SAAS,CAgBrB;AAyMD,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAE1B;QAAE,MAAM,EAAE,GAAG,CAAC,mBAAmB,EAAE,CAAA;KAAE;eAClC;QAAE,MAAM,EAAE,GAAG,CAAC,oBAAoB,CAAA;KAAE;WACxC;QAAE,MAAM,EAAE,GAAG,CAAC,oBAAoB,CAAA;KAAE;YACnC;QAAE,MAAM,EAAE,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,oBAAoB,CAAA;KAAE;sBAC3C;QAAE,MAAM,EAAE,GAAG,CAAC,mBAAmB,CAAA;KAAE;oBACrC;QAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAA;KAAE;aAC3B;QAAE,MAAM,EAAE,GAAG,CAAC,SAAS,CAAA;KAAE;cACxB;QAAE,MAAM,EAAE,GAAG,CAAC,gBAAgB,CAAA;KAAE;cAChC;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE;mBACZ;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE;QAmatC,CAAC"}

@ -1,550 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ShexJTraverser from "@ldo/traverser-shexj";
import * as dom from "dts-dom";
// Collected enum alias names (e.g., AuthenticatedAgentId) to emit at end
export const additionalCompactEnumAliases = new Set();
function commentFromAnnotations(annotations) {
const commentAnnotationObject = annotations?.find((annotation) => annotation.predicate ===
"http://www.w3.org/2000/01/rdf-schema#comment")?.object;
if (typeof commentAnnotationObject === "string")
return commentAnnotationObject;
return commentAnnotationObject?.value;
}
export function toCamelCase(text) {
return text
.replace(/([-_ ]){1,}/g, " ")
.split(/[-_ ]/)
.reduce((cur, acc) => {
return cur + acc[0].toUpperCase() + acc.substring(1);
});
}
/**
* Name functions
*/
export function iriToName(iri) {
try {
const url = new URL(iri);
let name;
if (url.hash) {
name = url.hash.slice(1);
}
else {
const splitPathname = url.pathname.split("/");
name = splitPathname[splitPathname.length - 1];
}
return name.replace(/(?<!^)Shape$/, "");
}
catch (err) {
return iri;
}
}
export function nameFromAnnotationOrId(obj) {
const labelAnnotationObject = obj.annotations?.find((annotation) => annotation.predicate ===
"http://www.w3.org/2000/01/rdf-schema#label")?.object;
if (labelAnnotationObject && typeof labelAnnotationObject === "string") {
return toCamelCase(iriToName(labelAnnotationObject));
}
else if (labelAnnotationObject &&
typeof labelAnnotationObject !== "string") {
return toCamelCase(labelAnnotationObject.value);
}
else if (obj.id) {
return toCamelCase(iriToName(obj.id));
}
}
// Helper: classify a dom.Type into categories we care about.
function isObjectLike(t) {
return (t.kind === "object" ||
t.kind === "interface");
}
function isPrimitiveLike(t) {
const kind = t?.kind;
if (kind === "name")
return true; // named references and intrinsic tokens
if (kind === "union") {
return t.members.every(isPrimitiveLike);
}
if (kind === "type-parameter")
return true;
// Fallback: treat scalar intrinsic tokens as primitive
const intrinsicKinds = new Set([
"string",
"number",
"boolean",
"undefined",
]);
return intrinsicKinds.has(kind || "");
}
// Small helpers for unions and alias naming
function isUnionType(t) {
return t?.kind === "union";
}
function unionOf(types) {
const flat = [];
const collect = (tt) => {
if (isUnionType(tt))
tt.members.forEach(collect);
else
flat.push(tt);
};
types.forEach(collect);
const seen = new Set();
const unique = [];
flat.forEach((m) => {
const key = m.name ||
m.value ||
m.kind + JSON.stringify(m);
if (!seen.has(key)) {
seen.add(key);
unique.push(m);
}
});
if (unique.length === 0)
return dom.type.any;
if (unique.length === 1)
return unique[0];
return dom.create.union(unique);
}
function setOf(inner) {
return {
kind: "name",
name: "Set",
typeArguments: [inner],
};
}
function recordOf(key, value) {
return {
kind: "name",
name: "Record",
typeArguments: [key, value],
};
}
// Note: aliasing helpers previously used in earlier versions were removed.
// Property name collision resolution using predicate IRI mapping
const predicateIriByProp = new WeakMap();
// Note: collisions are handled by annotateReadablePredicates pre-pass.
// Merge duplicate properties without introducing LdSet. If a property appears multiple
// times (e.g., via EXTENDS or grouped expressions) we:
// - union the types (flattening existing unions)
// - if one side is Set<T> and the other is plain U, produce Set<T|U>
// - if both are Set<A>, Set<B> -> Set<A|B>
// - preserve optional flag if any occurrence optional
function dedupeCompactProperties(props) {
const isSetRef = (t) => t.kind === "name" && t.name === "Set";
const getSetInner = (t) => isSetRef(t) ? t.typeArguments[0] : t;
// Group by composite key (name + predicate IRI)
const groups = new Map();
for (const p of props) {
const pred = predicateIriByProp.get(p) || "";
const key = `${p.name}\u0000${pred}`;
if (!groups.has(key))
groups.set(key, []);
groups.get(key).push(p);
}
const merged = [];
for (const [, group] of groups) {
if (group.length === 1) {
merged.push(group[0]);
continue;
}
let acc = group[0];
for (let i = 1; i < group.length; i++) {
const next = group[i];
const accSet = isSetRef(acc.type);
const nextSet = isSetRef(next.type);
let mergedType;
if (accSet && nextSet) {
mergedType = setOf(unionOf([getSetInner(acc.type), getSetInner(next.type)]));
}
else if (accSet && !nextSet) {
mergedType = setOf(unionOf([getSetInner(acc.type), next.type]));
}
else if (!accSet && nextSet) {
mergedType = setOf(unionOf([acc.type, getSetInner(next.type)]));
}
else {
mergedType = unionOf([acc.type, next.type]);
}
const optional = acc.flags === dom.DeclarationFlags.Optional ||
next.flags === dom.DeclarationFlags.Optional
? dom.DeclarationFlags.Optional
: dom.DeclarationFlags.None;
const mergedProp = dom.create.property(acc.name, mergedType, optional);
mergedProp.jsDocComment =
acc.jsDocComment && next.jsDocComment
? `${acc.jsDocComment} | ${next.jsDocComment}`
: acc.jsDocComment || next.jsDocComment;
const pred = predicateIriByProp.get(acc) || predicateIriByProp.get(next);
if (pred)
predicateIriByProp.set(mergedProp, pred);
acc = mergedProp;
}
merged.push(acc);
}
return merged;
}
// Helpers to add id: IRI to anonymous object(-union) types
function ensureIdOnMembers(members) {
if (!members)
return;
const props = (members.filter?.((m) => m?.kind === "property") ||
[]);
if (!props.some((m) => m.name === "id")) {
members.unshift(dom.create.property("id", dom.create.namedTypeReference("IRI"), dom.DeclarationFlags.None));
}
}
function withIdOnAnonymousObject(t) {
if (t?.kind === "object") {
const mems = t.members;
ensureIdOnMembers(mems);
return t;
}
return t;
}
function withIdInUnionObjectMembers(t) {
if (!isUnionType(t))
return t;
const members = t.members.map((m) => m?.kind === "object" ? withIdOnAnonymousObject(m) : m);
return dom.create.union(members);
}
// Create property and attach predicate IRI and annotations consistently
function createProperty(name, type, flags, predicateIri, annotations) {
const prop = dom.create.property(name, type, flags);
if (predicateIri)
predicateIriByProp.set(prop, predicateIri);
const cmt = commentFromAnnotations(annotations) || "";
prop.jsDocComment = cmt
? `${cmt}\n\nOriginal IRI: ${predicateIri ?? ""}`.trim()
: `Original IRI: ${predicateIri ?? ""}`;
return prop;
}
export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer({
// Transformer from Schema to interfaces
Schema: {
transformer: async (_schema, getTransformedChildren) => {
const transformedChildren = await getTransformedChildren();
const interfaces = [];
transformedChildren.shapes?.forEach((shape) => {
if (typeof shape !== "string" &&
shape.kind === "interface") {
interfaces.push(shape);
}
});
return interfaces;
},
},
// Transformer from ShapeDecl to interface
ShapeDecl: {
transformer: async (shapeDecl, getTransformedChildren) => {
const shapeName = nameFromAnnotationOrId(shapeDecl) || "Shape";
const { shapeExpr } = await getTransformedChildren();
if (shapeExpr.kind === "interface") {
const shapeInterface = shapeExpr;
shapeInterface.name = shapeName;
// Preserve shape id for downstream shapeTypes generation
// (mirrors standard transformer behavior)
shapeInterface.shapeId = shapeDecl.id;
if (!shapeInterface.members.find((m) => m.kind === "property" && m.name === "id")) {
shapeInterface.members.unshift(dom.create.property("id", dom.create.namedTypeReference("IRI"),
// Root interfaces should have mandatory id
dom.DeclarationFlags.None));
}
return shapeInterface;
}
throw new Error("Unsupported direct shape expression on ShapeDecl for compact format.");
},
},
// Transformer from Shape to interface
Shape: {
transformer: async (_shape, getTransformedChildren, setReturnPointer) => {
const newInterface = dom.create.interface("");
setReturnPointer(newInterface);
const transformedChildren = await getTransformedChildren();
if (typeof transformedChildren.expression !== "string" &&
transformedChildren.expression &&
(transformedChildren.expression.kind ===
"object" ||
transformedChildren.expression
.kind === "interface")) {
newInterface.members.push(...transformedChildren.expression
.members);
}
else if (transformedChildren.expression
?.kind === "property") {
newInterface.members.push(transformedChildren.expression);
}
if (transformedChildren.extends) {
transformedChildren.extends.forEach((ext) => {
const extInt = ext;
if (extInt.kind === "interface") {
const merged = [
...extInt.members.filter((m) => !(m.kind === "property" && m.name === "id")),
...newInterface.members,
].filter((m) => m.kind === "property");
newInterface.members = dedupeCompactProperties(merged);
}
});
}
// Final pass: ensure only a single id property
const idSeen = new Set();
newInterface.members = newInterface.members.filter((m, idx) => {
if (m.kind !== "property" || m.name !== "id")
return true;
if (idSeen.size === 0) {
idSeen.add(idx);
// normalize id type to IRI
m.type = dom.create.namedTypeReference("IRI");
return true;
}
return false;
});
return newInterface;
},
},
// Transformer from EachOf to object type. EachOf contains the `expressions` array of properties (TripleConstraint)
EachOf: {
transformer: async (eachOf, getTransformedChildren, setReturnPointer) => {
const transformedChildren = await getTransformedChildren();
const name = nameFromAnnotationOrId(eachOf);
const objectType = name
? dom.create.interface(name)
: dom.create.objectType([]);
setReturnPointer(objectType);
const inputProps = [];
transformedChildren.expressions.forEach((expr) => {
if (!expr || typeof expr === "string")
return;
const kind = expr.kind;
if (kind === "property") {
inputProps.push(expr);
}
else if (kind === "object" || kind === "interface") {
const mlist = expr.members;
mlist.forEach((m) => {
if (m.kind === "property") {
inputProps.push(m);
}
});
}
});
const deduped = dedupeCompactProperties(inputProps);
objectType.members.push(...deduped);
return objectType;
},
},
// Transformer from triple constraints to type properties.
TripleConstraint: {
transformer: async (tripleConstraint, getTransformedChildren, _setReturnPointer, node) => {
const transformedChildren = await getTransformedChildren();
const baseName = tripleConstraint
.readablePredicate;
const max = tripleConstraint.max;
const isPlural = max === -1 || (max !== undefined && max !== 1);
const isOptional = tripleConstraint.min === 0;
let valueType = dom.type.any;
if (transformedChildren.valueExpr)
valueType = transformedChildren.valueExpr;
// Generic: If valueExpr is a NodeConstraint with concrete `values`,
// build a union of named alias references derived from those values.
// Works for any predicate (not only rdf:type).
const originalValueExpr = tripleConstraint?.valueExpr;
if (originalValueExpr &&
typeof originalValueExpr === "object" &&
originalValueExpr.type === "NodeConstraint" &&
Array.isArray(originalValueExpr.values) &&
originalValueExpr.values.length > 0) {
const aliasRefs = [];
for (const v of originalValueExpr.values) {
// valueSetValue can be string IRIREF or ObjectLiteral or other stems; handle IRIREF and ObjectLiteral
if (typeof v === "string") {
// For concrete IRIREF values, use a string literal of the IRI
aliasRefs.push(dom.type.stringLiteral(v));
}
else if (v && typeof v === "object") {
// ObjectLiteral has `value`; use that literal as alias base
const literalVal = v.value;
if (literalVal) {
// For explicit literal values, use a string literal type
aliasRefs.push(dom.type.stringLiteral(literalVal));
}
// For other union members (IriStem, ranges, Language, etc.), skip here; fall back covered below if none collected
}
}
if (aliasRefs.length > 0) {
const union = unionOf(aliasRefs);
const final = isPlural ? setOf(union) : union;
return createProperty(baseName, final, isOptional
? dom.DeclarationFlags.Optional
: dom.DeclarationFlags.None, tripleConstraint.predicate, tripleConstraint.annotations);
}
}
if (valueType.kind === "interface" &&
!valueType.name) {
valueType = dom.create.objectType(valueType
.members);
}
// Normalize NodeConstraint returned object forms for IRIs into IRI
// Heuristic: existing transformer (compact) returns string/number/boolean OR object/interface.
// We treat any simple string/number/boolean/name as primitive.
// Determine category
const objLike = isObjectLike(valueType);
const isUnion = valueType?.kind === "union";
const unionMembers = isUnion
? valueType.members
: [];
const unionAllObjLike = isUnion &&
unionMembers.length > 0 &&
unionMembers.every(isObjectLike);
const primLike = isPrimitiveLike(valueType);
if (!primLike &&
!objLike &&
valueType.kind === "union") {
const u = valueType;
const hasObj = u.members.some(isObjectLike);
const hasPrim = u.members.some(isPrimitiveLike);
if (isPlural && hasObj && hasPrim) {
throw new Error(`Mixed plural union (object + primitive) not supported for predicate ${tripleConstraint.predicate}`);
}
}
let finalType;
if (isPlural) {
if (objLike || unionAllObjLike) {
if (valueType.kind ===
"interface" &&
valueType.name) {
const ifaceName = valueType.name;
// Dictionary of full object instances keyed by IRI
finalType = recordOf(dom.create.namedTypeReference("IRI"), dom.create.namedTypeReference(ifaceName));
}
else {
// Anonymous object or union of anonymous/interface objects
let valueForRecord = valueType;
if (unionAllObjLike) {
// Ensure each union member has id?: IRI if anonymous object
valueForRecord =
withIdInUnionObjectMembers(valueType);
}
else {
valueForRecord = withIdOnAnonymousObject(valueType);
}
finalType = recordOf(dom.create.namedTypeReference("IRI"), valueForRecord);
}
}
else {
finalType = setOf(valueType);
}
}
else {
// Singular
// If anonymous object or union of object-like types, ensure id: IRI is present (mandatory)
if (objLike) {
if (valueType.kind === "object") {
valueType = withIdOnAnonymousObject(valueType);
}
}
else if (isUnion && unionAllObjLike) {
valueType = withIdInUnionObjectMembers(valueType);
}
// Singular: always the interface/object type itself (never Id union)
if (valueType.kind ===
"interface" &&
valueType.name) {
finalType = dom.create.namedTypeReference(valueType.name);
}
else {
finalType = valueType;
}
}
return createProperty(baseName, finalType, isOptional
? dom.DeclarationFlags.Optional
: dom.DeclarationFlags.None, tripleConstraint.predicate, tripleConstraint.annotations);
},
},
// Transformer from node constraint to type
NodeConstraint: {
transformer: async (nodeConstraint) => {
if (nodeConstraint.datatype) {
switch (nodeConstraint.datatype) {
case "http://www.w3.org/2001/XMLSchema#boolean":
return dom.type.boolean;
case "http://www.w3.org/2001/XMLSchema#byte":
case "http://www.w3.org/2001/XMLSchema#decimal":
case "http://www.w3.org/2001/XMLSchema#double":
case "http://www.w3.org/2001/XMLSchema#float":
case "http://www.w3.org/2001/XMLSchema#int":
case "http://www.w3.org/2001/XMLSchema#integer":
case "http://www.w3.org/2001/XMLSchema#long":
case "http://www.w3.org/2001/XMLSchema#negativeInteger":
case "http://www.w3.org/2001/XMLSchema#nonNegativeInteger":
case "http://www.w3.org/2001/XMLSchema#nonPositiveInteger":
case "http://www.w3.org/2001/XMLSchema#positiveInteger":
case "http://www.w3.org/2001/XMLSchema#short":
case "http://www.w3.org/2001/XMLSchema#unsignedLong":
case "http://www.w3.org/2001/XMLSchema#unsignedInt":
case "http://www.w3.org/2001/XMLSchema#unsignedShort":
case "http://www.w3.org/2001/XMLSchema#unsignedByte":
return dom.type.number;
default:
return dom.type.string; // treat most as string
}
}
if (nodeConstraint.nodeKind) {
switch (nodeConstraint.nodeKind) {
case "iri":
return dom.create.namedTypeReference("IRI");
case "bnode":
return dom.type.string; // opaque id as string
case "nonliteral":
return dom.create.namedTypeReference("IRI");
case "literal":
default:
return dom.type.string;
}
}
if (nodeConstraint.values) {
const u = dom.create.union([]);
nodeConstraint.values.forEach((v) => {
if (typeof v === "string")
u.members.push(dom.type.stringLiteral(v));
});
if (!u.members.length)
return dom.type.string;
if (u.members.length === 1)
return u.members[0];
return u;
}
return dom.type.any;
},
},
// Transformer from ShapeOr to union type
ShapeOr: {
transformer: async (_shapeOr, getTransformedChildren) => {
const tc = await getTransformedChildren();
return dom.create.union(tc.shapeExprs);
},
},
// Transformer from ShapeAnd to intersection type
ShapeAnd: {
transformer: async (_shapeAnd, getTransformedChildren) => {
const tc = await getTransformedChildren();
const valid = [];
tc.shapeExprs.forEach((t) => {
if (typeof t === "object")
valid.push(t);
});
return dom.create.intersection(valid);
},
},
// Transformer from ShapeNot to type - not supported.
ShapeNot: {
transformer: async () => {
throw new Error("ShapeNot not supported (compact)");
},
},
// Transformer from ShapeExternal to type - not supported.
ShapeExternal: {
transformer: async () => {
throw new Error("ShapeExternal not supported (compact)");
},
},
});

@ -1,5 +0,0 @@
import type { InterfaceDeclaration } from "dts-dom";
export interface ShapeInterfaceDeclaration extends InterfaceDeclaration {
shapeId?: string;
}
//# sourceMappingURL=ShapeInterfaceDeclaration.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"ShapeInterfaceDeclaration.d.ts","sourceRoot":"","sources":["../../../src/schema-converter/util/ShapeInterfaceDeclaration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,yBAA0B,SAAQ,oBAAoB;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}

@ -1,8 +0,0 @@
import type { Schema } from "shexj";
/**
* Annotate EachOf-level TripleConstraints with a collision-free readablePredicate.
* Rule: for any group that shares the same local token, rename all members using
* prefix-first `${prefix}_${local}` from right to left; fallback to composite.
*/
export default function annotateReadablePredicates(schema: Schema): void;
//# sourceMappingURL=annotateReadablePredicates.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"annotateReadablePredicates.d.ts","sourceRoot":"","sources":["../../../src/schema-converter/util/annotateReadablePredicates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA8C,MAAM,OAAO,CAAC;AAUhF;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAoIvE"}

@ -1,129 +0,0 @@
// Split IRI by colon, slash and hash; drop empties
const splitIriTokens = (iri) => iri.split(/[:/#]+/).filter(Boolean);
// Keep dots and dashes (so 0.1 stays as 0.1) but sanitize everything else
const sanitize = (s) => s.replace(/[^\w.\-]/g, "_");
/**
* Annotate EachOf-level TripleConstraints with a collision-free readablePredicate.
* Rule: for any group that shares the same local token, rename all members using
* prefix-first `${prefix}_${local}` from right to left; fallback to composite.
*/
export default function annotateReadablePredicates(schema) {
const shapes = schema.shapes ?? [];
const annotateEachOf = (eachOf) => {
if (!eachOf ||
eachOf.type !== "EachOf" ||
!Array.isArray(eachOf.expressions))
return;
const tcs = eachOf.expressions.filter((e) => typeof e === "object" &&
e !== null &&
e.type === "TripleConstraint");
if (tcs.length > 0) {
// Group by local token (last segment of IRI) and set a base readablePredicate for all
const groups = new Map();
for (const tc of tcs) {
const tokens = splitIriTokens(tc.predicate);
const local = tokens.length
? tokens[tokens.length - 1]
: tc.predicate;
// default base name for non-colliders
tc.readablePredicate = local;
const arr = groups.get(local) ?? [];
arr.push(tc);
groups.set(local, arr);
}
// Resolve each group (rename all in collisions)
for (const [, arr] of groups) {
if (arr.length <= 1)
continue;
const used = new Set();
const local = splitIriTokens(arr[0].predicate).slice(-1)[0] ?? "";
for (const tc of arr) {
const tokens = splitIriTokens(tc.predicate);
let localIdx = tokens.lastIndexOf(local);
if (localIdx === -1)
localIdx = Math.max(tokens.length - 1, 0);
let prefixIdx = localIdx - 1;
let assigned = false;
while (prefixIdx >= 0) {
const cand = `${sanitize(tokens[prefixIdx])}_${sanitize(tokens[localIdx])}`;
if (!used.has(cand)) {
tc.readablePredicate = cand;
used.add(cand);
assigned = true;
break;
}
prefixIdx -= 1;
}
if (!assigned) {
const iriNoProto = tc.predicate.replace(/^[a-z]+:\/\//i, "");
const composite = sanitize(iriNoProto
.split(/[:/#]+/)
.slice(0, -1)
.join("_") || "iri");
let cand = `${composite}_${sanitize(tokens[localIdx] || local)}`;
let n = 1;
while (used.has(cand))
cand = `${cand}_${n++}`;
tc.readablePredicate = cand;
used.add(cand);
}
}
}
// Recurse into nested valueExpr shapes of each TC
for (const tc of tcs) {
const ve = tc.valueExpr;
if (ve && typeof ve === "object") {
const t = ve.type;
if (t === "Shape" && ve.expression)
annotateEachOf(ve.expression);
else if (t === "EachOf")
annotateEachOf(ve);
else if (t === "ShapeOr" &&
Array.isArray(ve.shapeExprs)) {
for (const sub of ve.shapeExprs)
annotateFromExpr(sub);
}
else if (t === "ShapeAnd" &&
Array.isArray(ve.shapeExprs)) {
for (const sub of ve.shapeExprs)
annotateFromExpr(sub);
}
}
}
}
// Also recurse into any inline sub-EachOf/Shape expressions found directly in expressions
for (const ex of eachOf.expressions) {
if (ex && typeof ex === "object")
annotateFromExpr(ex);
}
};
const annotateFromExpr = (expr) => {
if (!expr || typeof expr !== "object")
return;
const t = expr.type;
if (t === "Shape" && expr.expression)
annotateEachOf(expr.expression);
else if (t === "EachOf")
annotateEachOf(expr);
else if (t === "ShapeOr" && Array.isArray(expr.shapeExprs)) {
for (const sub of expr.shapeExprs)
annotateFromExpr(sub);
}
else if (t === "ShapeAnd" &&
Array.isArray(expr.shapeExprs)) {
for (const sub of expr.shapeExprs)
annotateFromExpr(sub);
}
else if (t === "TripleConstraint") {
const ve = expr.valueExpr;
if (ve && typeof ve === "object")
annotateFromExpr(ve);
}
};
for (const s of shapes) {
const sd = s;
const shape = (sd.shapeExpr || sd);
if (shape?.expression)
annotateFromExpr(shape);
}
}

@ -1,3 +0,0 @@
import type { ObjectTypeMember } from "dts-dom";
export declare function dedupeObjectTypeMembers(memberList: ObjectTypeMember[]): ObjectTypeMember[];
//# sourceMappingURL=dedupeObjectTypeMembers.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"dedupeObjectTypeMembers.d.ts","sourceRoot":"","sources":["../../../src/schema-converter/util/dedupeObjectTypeMembers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGhD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,gBAAgB,EAAE,GAC7B,gBAAgB,EAAE,CAoCpB"}

@ -1,38 +0,0 @@
import * as dom from "dts-dom";
export function dedupeObjectTypeMembers(memberList) {
const properties = {};
memberList.forEach((expression) => {
const propertyDeclaration = expression;
// Combine properties if they're duplicates
if (properties[propertyDeclaration.name]) {
const oldPropertyDeclaration = properties[propertyDeclaration.name];
const oldPropertyType = isLdSetType(oldPropertyDeclaration.type)
? oldPropertyDeclaration.type.typeArguments[0]
: oldPropertyDeclaration.type;
const propertyType = isLdSetType(propertyDeclaration.type)
? propertyDeclaration.type.typeArguments[0]
: propertyDeclaration.type;
const isOptional = propertyDeclaration.flags === dom.DeclarationFlags.Optional ||
oldPropertyDeclaration.flags === dom.DeclarationFlags.Optional;
properties[propertyDeclaration.name] = dom.create.property(propertyDeclaration.name, {
kind: "name",
name: "LdSet",
typeArguments: [dom.create.union([oldPropertyType, propertyType])],
}, isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None);
// Set JS Comment
properties[propertyDeclaration.name].jsDocComment =
oldPropertyDeclaration.jsDocComment && propertyDeclaration.jsDocComment
? `${oldPropertyDeclaration.jsDocComment} | ${propertyDeclaration.jsDocComment}`
: oldPropertyDeclaration.jsDocComment ||
propertyDeclaration.jsDocComment;
}
else {
properties[propertyDeclaration.name] = propertyDeclaration;
}
});
return Object.values(properties);
}
function isLdSetType(potentialLdSet) {
return (potentialLdSet.kind === "name" &&
potentialLdSet.name === "LdSet");
}

@ -1,4 +0,0 @@
import type { ShexJTraverserTypes } from "@ldo/traverser-shexj";
import type { InterfaceInstanceNode } from "@ldo/type-traverser";
export declare function getRdfTypesForTripleConstraint(tripleConstraintNode: InterfaceInstanceNode<ShexJTraverserTypes, "TripleConstraint", ShexJTraverserTypes["TripleConstraint"]>): string[] | undefined[];
//# sourceMappingURL=getRdfTypesForTripleConstraint.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"getRdfTypesForTripleConstraint.d.ts","sourceRoot":"","sources":["../../../src/schema-converter/util/getRdfTypesForTripleConstraint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,mBAAmB,EAEtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAwGjE,wBAAgB,8BAA8B,CAC1C,oBAAoB,EAAE,qBAAqB,CACvC,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,CAAC,kBAAkB,CAAC,CAC1C,GACF,MAAM,EAAE,GAAG,SAAS,EAAE,CA4BxB"}

@ -1,89 +0,0 @@
function addRdfTypeFromTripleExpr(tripleExpr, rdfTypeSet) {
if (typeof tripleExpr === "object" &&
tripleExpr.type === "TripleConstraint" &&
tripleExpr.predicate ===
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type" &&
typeof tripleExpr.valueExpr === "object" &&
tripleExpr.valueExpr.type === "NodeConstraint" &&
tripleExpr.valueExpr.values) {
tripleExpr.valueExpr.values.forEach((val) => {
if (typeof val === "string")
rdfTypeSet.add(val);
// TODO handle other edge cases like IRIStem
});
}
}
function recursivelyGatherTypesFromShapeNodes(shapeNode, rdfTypeSet) {
const tripleExpr = shapeNode.instance.expression;
if (tripleExpr)
addRdfTypeFromTripleExpr(tripleExpr, rdfTypeSet);
shapeNode.parent("shapeExpr").forEach((parentShapeExpr) => {
parentShapeExpr
.parent("ShapeDecl", "shapeExpr")
.forEach((parentShapeDecl) => {
parentShapeDecl
.parent("shapeDeclRef")
.forEach((parentShapeDeclOrRef) => {
parentShapeDeclOrRef
.parent("shapeExprOrRef")
.forEach((parentShapeExprOrRef) => {
parentShapeExprOrRef
.parent("Shape", "extends")
.forEach((parentShape) => {
recursivelyGatherTypesFromShapeNodes(parentShape, rdfTypeSet);
const childExpressionNode = parentShape.child("expression");
if (!childExpressionNode)
return;
const childEachOf = childExpressionNode
.child()
.child();
if (childEachOf.typeName === "EachOf") {
recursivelyGatherTypesFromEachOfNodes(childEachOf, rdfTypeSet);
}
});
});
});
});
});
}
function recursivelyGatherTypesFromEachOfNodes(eachOfNode, rdfTypeSet) {
const tripleExprs = eachOfNode.instance.expressions;
tripleExprs.forEach((tripleExpr) => {
addRdfTypeFromTripleExpr(tripleExpr, rdfTypeSet);
});
eachOfNode.parent("tripleExpr").forEach((tripleExprNode) => {
const tripleExprOrRefNodes = tripleExprNode.parent("tripleExprOrRef");
tripleExprOrRefNodes.forEach((tripleExprOrRdfNode) => {
const parentEachOfs = tripleExprOrRdfNode.parent("EachOf", "expressions");
parentEachOfs.forEach((parentEachOf) => {
recursivelyGatherTypesFromEachOfNodes(parentEachOf, rdfTypeSet);
});
// Deal with shape extends
const parentShapes = tripleExprOrRdfNode.parent("Shape", "expression");
parentShapes.forEach((parentShape) => recursivelyGatherTypesFromShapeNodes(parentShape, rdfTypeSet));
});
});
}
export function getRdfTypesForTripleConstraint(tripleConstraintNode) {
// Check that there's a triple constraint that is a type at the
// same level if there is, use that as an rdfType
const rdfTypeSet = new Set();
tripleConstraintNode.parent("tripleExpr").forEach((tripleExprParents) => {
tripleExprParents
.parent("tripleExprOrRef")
.forEach((tripleExprOrRefParent) => {
tripleExprOrRefParent
.parent("EachOf", "expressions")
.forEach((eachOfParent) => {
recursivelyGatherTypesFromEachOfNodes(eachOfParent, rdfTypeSet);
});
tripleExprOrRefParent
.parent("Shape", "expression")
.forEach((shapeParent) => {
recursivelyGatherTypesFromShapeNodes(shapeParent, rdfTypeSet);
});
});
});
const rdfTypes = rdfTypeSet.size > 0 ? Array.from(rdfTypeSet) : [undefined];
return rdfTypes;
}

@ -1,37 +0,0 @@
export interface ShapeType<T extends BaseType> {
schema: Schema;
shape: string;
}
export interface BaseType extends Record<string, any> {
id: string;
}
export type Schema = {
[id: string]: Shape;
};
export interface Shape {
iri: string;
predicates: Predicate[];
}
export type DataType = {
/** The required literal value(s), if type is `literal`. Others are allowed, if `extra` is true. */
literals?: number[] | string[] | boolean;
/** If `valType` is `"shape"`, the nested shape or its reference. Use reference for serialization. */
shape?: string | Shape;
/** The type of object value for a triple constraint. */
valType: "number" | "string" | "boolean" | "iri" | "literal" | "shape";
};
export interface Predicate {
/** Allowed type of object. If more than one is present, either of them is allowed. */
dataTypes: DataType[];
/** The RDF predicate URI. */
iri: string;
/** The alias of the `predicateUri` when serialized to a JSON object. */
readablePredicate: string;
/** Maximum allowed number of values. `-1` means infinite. */
maxCardinality: number;
/** Minimum required number of values */
minCardinality: number;
/** If other (additional) values are permitted. Useful for literals. */
extra?: boolean;
}
//# sourceMappingURL=types.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,QAAQ;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAS,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACjD,EAAE,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,MAAM,GAAG;IACjB,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,CAAC;CACvB,CAAC;AAEF,MAAM,WAAW,KAAK;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,SAAS,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,QAAQ,GAAG;IACnB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IACzC,qGAAqG;IACrG,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,wDAAwD;IACxD,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,CAAC;CAC1E,CAAC;AAEF,MAAM,WAAW,SAAS;IACtB,sFAAsF;IACtF,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB"}

@ -1 +0,0 @@
export {};

@ -1,2 +0,0 @@
export declare function forAllShapes(shapePath: string, callback: (filename: string, shape: string) => Promise<void>): Promise<void>;
//# sourceMappingURL=forAllShapes.d.ts.map

@ -1 +0,0 @@
{"version":3,"file":"forAllShapes.d.ts","sourceRoot":"","sources":["../../src/util/forAllShapes.ts"],"names":[],"mappings":"AAGA,wBAAsB,YAAY,CAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAC7D,OAAO,CAAC,IAAI,CAAC,CAuBf"}

@ -1,17 +0,0 @@
import fs from "fs";
import path from "node:path";
export async function forAllShapes(shapePath, callback) {
const shapeDir = await fs.promises.readdir(shapePath, {
withFileTypes: true,
});
// Filter out non-shex documents
const shexFiles = shapeDir.filter((file) => file.isFile() && file.name.endsWith(".shex"));
const shexPromise = Promise.all(shexFiles.map(async (file) => {
const fileName = path.parse(file.name).name;
// Get the content of each document
const shexC = await fs.promises.readFile(path.join(shapePath, file.name), "utf8");
await callback(fileName, shexC);
}));
// Note: SHACL conversion omitted here.
await Promise.all([shexPromise]);
}

@ -204,8 +204,30 @@ export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer<
literals: nodeConstraint.values.map(
// TODO: We do not convert them to number or boolean or lang tag.
// And we don't have an annotation of the literal's type.
// @ts-expect-error
(valueRecord) => valueRecord.value || valueRecord.id
(valueRecord) => {
// If valueRecord is a string (IRIREF), return it directly
if (typeof valueRecord === "string") {
return valueRecord;
}
// Handle ObjectLiteral (has .value property)
if ("value" in valueRecord) {
return valueRecord.value;
}
// Handle other types with .id property (if any)
if ("id" in valueRecord) {
return (valueRecord as any).id;
}
// Handle Language type (has .languageTag)
if ("languageTag" in valueRecord) {
return valueRecord.languageTag;
}
// Handle stem-based types (IriStem, LiteralStem, LanguageStem)
if ("stem" in valueRecord) {
return valueRecord.stem as string;
}
// Fallback - should not happen in well-formed ShEx
return undefined;
}
),
};
}

@ -1,5 +1,5 @@
import type { Diff as Patches, Scope } from "../types.ts";
import { applyDiff } from "./applyDiff.ts";
import { applyDiff, applyDiffToDeepSignal, Patch } from "./applyDiff.ts";
import { ngSession } from "./initNg.ts";
@ -145,6 +145,10 @@ export class OrmConnection<T extends BaseType> {
private onBackendMessage = ({ V0: data }: any) => {
if (data.OrmInitial) {
this.handleInitialResponse(data.OrmInitial);
} else if (data.OrmUpdate) {
this.onBackendUpdate(data.OrmUpdate);
} else {
console.warn("Received unknown ORM message from backend", data);
}
};
@ -169,8 +173,8 @@ export class OrmConnection<T extends BaseType> {
this.ready = true;
};
private onBackendUpdate = (...params: any) => {
// Apply diff
private onBackendUpdate = (patches: Patch[]) => {
applyDiffToDeepSignal(this.signalObject, patches);
};
/** Function to create random subject IRIs for newly created nested objects. */

@ -8,8 +8,8 @@
// according to those terms.
use crate::local_broker::{doc_sparql_construct, orm_start, orm_update};
use crate::tests::create_doc_with_data;
use crate::tests::create_or_open_wallet::create_or_open_wallet;
use crate::tests::{assert_json_eq, create_doc_with_data};
use async_std::stream::StreamExt;
use ng_net::app_protocol::{AppResponse, AppResponseV0, NuriV0};
use ng_net::orm::{
@ -55,6 +55,9 @@ async fn test_orm_apply_patches() {
// Test 9: Nested object creation
test_patch_create_nested_object(session_id).await;
// Test 10: Object deleted after invalidating patch.
test_patch_invalidating_object(session_id).await
}
/// Test adding a single literal value via ORM patch
@ -1264,3 +1267,117 @@ INSERT DATA {
log_info!("✓ Test passed: Nested object creation");
}
/// Test replacing object's type invalidating it.
async fn test_patch_invalidating_object(session_id: u64) {
log_info!("\n\n=== TEST: Remove Single Literal ===\n");
let doc_nuri = create_doc_with_data(
session_id,
r#"
PREFIX ex: <http://example.org/>
INSERT DATA {
<urn:test:person2> a ex:Person ;
ex:name "Bob" .
}
"#
.to_string(),
)
.await;
let mut schema = HashMap::new();
schema.insert(
"http://example.org/Person".to_string(),
Arc::new(OrmSchemaShape {
iri: "http://example.org/Person".to_string(),
predicates: vec![
Arc::new(OrmSchemaPredicate {
iri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
extra: Some(false),
maxCardinality: 1,
minCardinality: 1,
readablePredicate: "type".to_string(),
dataTypes: vec![OrmSchemaDataType {
valType: OrmSchemaValType::literal,
literals: Some(vec![BasicType::Str(
"http://example.org/Person".to_string(),
)]),
shape: None,
}],
}),
Arc::new(OrmSchemaPredicate {
iri: "http://example.org/name".to_string(),
extra: Some(false),
readablePredicate: "name".to_string(),
minCardinality: 0,
maxCardinality: 1,
dataTypes: vec![OrmSchemaDataType {
valType: OrmSchemaValType::string,
literals: None,
shape: None,
}],
}),
],
}),
);
let shape_type = OrmShapeType {
shape: "http://example.org/Person".to_string(),
schema,
};
let nuri = NuriV0::new_from(&doc_nuri).expect("parse nuri");
let (mut receiver, _cancel_fn) = orm_start(nuri.clone(), shape_type.clone(), session_id)
.await
.expect("orm_start failed");
// Get initial state (person without name)
while let Some(app_response) = receiver.next().await {
if let AppResponse::V0(AppResponseV0::OrmInitial(initial)) = app_response {
break;
}
}
// Apply ORM patch: Change type to something invalid by schema.
let patch = vec![OrmPatch {
op: OrmPatchOp::add,
path: "/urn:test:person2/type".to_string(),
valType: None,
value: Some(json!("InvalidType")),
}];
orm_update(nuri.clone(), shape_type.shape.clone(), patch, session_id)
.await
.expect("orm_update failed");
// Expect delete patch for root object
while let Some(app_response) = receiver.next().await {
let patches = match app_response {
AppResponse::V0(v) => match v {
AppResponseV0::OrmUpdate(json) => Some(json),
_ => None,
},
}
.unwrap();
log_info!("Patches arrived:\n");
for patch in patches.iter() {
log_info!("{:?}", patch);
}
let mut expected = json!([
{
"op": "remove",
"valType": "object",
"path": "/urn:test:person2",
},
]);
let mut actual = json!(patches);
assert_json_eq(&mut expected, &mut actual);
break;
}
log_info!("✓ Test passed: Received object remove patch after patch makes object invalid.");
}

@ -27,35 +27,13 @@ async fn test_orm_patch_creation() {
// Setup wallet and document
let (_wallet, session_id) = create_or_open_wallet().await;
// Tests below all in this test, to prevent waiting times through wallet creation.
// // ===
// test_patch_add_array(session_id).await;
// test_patch_remove_array(session_id).await;
// // ===
// test_patch_add_nested_1(session_id).await;
// ===
test_patch_nested_house_inhabitants(session_id).await;
// // ===
// test_orm_literal(session_id).await;
// // ===
// test_orm_multi_type(session_id).await;
// // ===
// test_orm_nested_1(session_id).await;
// // // ===
// // test_orm_nested_2(session_id).await;
test_patch_add_array(session_id).await;
// // // ===
// // test_orm_nested_3(session_id).await;
test_patch_remove_array(session_id).await;
// // ===
// test_orm_nested_4(session_id).await;
// test_patch_add_nested_1(session_id).await; // TODO: Edge case not yet fully implemented
}
async fn test_patch_add_array(session_id: u64) {
@ -204,19 +182,11 @@ INSERT DATA {
"op": "add",
"valType": "object",
"path": "/urn:test:numArrayObj4",
"value": Value::Null
},
{
"op": "add",
"value": "urn:test:numArrayObj4",
"path": "/urn:test:numArrayObj4/@id",
"valType": Value::Null,
},
{
"op": "add",
"value": "http://example.org/TestObject",
"path": "/urn:test:numArrayObj4/type",
"valType": Value::Null,
},
{
"op": "add",
@ -224,6 +194,11 @@ INSERT DATA {
"value": [0.0],
"path": "/urn:test:numArrayObj4/numArray",
},
{
"op": "add",
"value": "http://example.org/TestObject",
"path": "/urn:test:numArrayObj4/type",
},
]);
let mut actual = json!(patches);
@ -556,42 +531,29 @@ INSERT DATA {
{
"op": "remove",
"path": "/urn:test:oj1/multiNest/urn:test:multiNested2/string2",
// "valType": None,
// "value": None,
},
{
"op": "add",
// "valType": None,
"value": "replacing object shape view",
"path": "/urn:test:oj1/multiNest/urn:test:multiNested2/string1",
},
{
"op": "add",
"valType": "object",
// "value": None,
"path": "/urn:test:oj1/multiNest/urn:test:multiNested4",
},
{
"op": "add",
// "valType": None,
"value": "urn:test:multiNested4",
"path": "/urn:test:oj1/multiNest/urn:test:multiNested4/@id",
},
{
"op": "add",
// "valType": None,
"value": "multi 4 added",
"path": "/urn:test:oj1/multiNest/urn:test:multiNested4/string2",
},
{
"op": "remove",
// "valType": None,
// "value": None,
"path": "/urn:test:oj1/singleNest/str",
},
{
"op": "add",
// "valType": None,
"value": "Different nested val",
"path": "/urn:test:oj1/singleNest/str",
},
@ -1013,10 +975,6 @@ INSERT DATA {
"path": "/urn:test:house1/inhabitants/urn:test:person1/cat",
},
// Bob's cat name changes
{
"op": "remove",
"path": "/urn:test:house1/inhabitants/urn:test:person2/cat/name",
},
{
"op": "add",
"value": "Mr. Mittens",

Loading…
Cancel
Save