# Entity Types This document details the four entity type implementations: Container, Document, Category, and Index. ## Entity Type Overview ```mermaid graph TB subgraph Entity Types Container[Container - Container] Document[Document - Properties] Category[Category - Auto Categories] Index[Index - Text Search] end subgraph Relationships Container -->|contains| Container Container -->|contains| Document Container -->|contains| Category Container -->|contains| Index Category -->|generates| Container Index -->|returns| IndexResult IndexResult -->|contains| Document end ``` ## Container Entity A Container is a container for child entities, similar to a filesystem folder. ### Implementation ```vala namespace Implexus.Entities { public class Container : AbstractEntity { public Container(Engine engine, Path path) { base(engine, path); } public override EntityType entity_type { get { return EntityType.CONTAINER; } } // Child creation - only Container can create children public override Entity? create_container(string name) throws EngineError { validate_can_create_child(name); var child_path = _path.child(name); var container = new Container(_engine, child_path); save_entity(container); register_child(name); _engine.entity_created(container); return container; } public override Entity? create_document(string name, string type_label) throws EngineError { validate_can_create_child(name); var child_path = _path.child(name); var document = new Document(_engine, child_path, type_label); save_entity(document); register_child(name); register_type(type_label, child_path); _engine.entity_created(document); return document; } public override Entity? create_category( string name, string type_label, string expression ) throws EngineError { validate_can_create_child(name); var child_path = _path.child(name); var category = new Category(_engine, child_path, type_label, expression); save_entity(category); register_child(name); _engine.entity_created(category); return category; } public override Entity? create_index( string name, string type_label, string expression ) throws EngineError { validate_can_create_child(name); var child_path = _path.child(name); var index = new Index(_engine, child_path, type_label, expression); save_entity(index); register_child(name); _engine.entity_created(index); return index; } public override void delete() throws EngineError { // Delete all children first foreach (var child_name in child_names) { var child = get_child(child_name); if (child != null) { ((!) child).delete(); } } base.delete(); } private void validate_can_create_child(string name) throws EngineError { if (name == null || name == "") { throw new EngineError.INVALID_PATH("Child name cannot be empty"); } var child_path = _path.child(name); if (_engine.entity_exists(child_path)) { throw new EngineError.ENTITY_ALREADY_EXISTS( "Entity already exists: %s", child_path.to_string() ); } } private void save_entity(Entity entity) throws EngineError { var serializer = new EntitySerializer(); var data = serializer.serialize(entity); _engine.configuration.storage.save_entity(entity.path, data); } private void register_child(string name) { _engine.configuration.storage.register_child(_path, name); } private void register_type(string type_label, Path path) { _engine.configuration.storage.register_type(path, type_label); } } } // namespace Implexus.Entities ``` ## Document Entity A Document is a typed object with properties. The type_label is application-defined and used for querying. ### Implementation ```vala namespace Implexus.Entities { public class Document : AbstractEntity { private string _type_label; private Invercargill.DataStructures.PropertyDictionary _properties; public Document(Engine engine, Path path, string type_label) { base(engine, path); _type_label = type_label; _properties = new Invercargill.DataStructures.PropertyDictionary(); } public override EntityType entity_type { get { return EntityType.DOCUMENT; } } public override string type_label { owned get { return _type_label; } } public override Invercargill.Properties properties { owned get { return _properties; } } public override Invercargill.Element? get_property(string name) { return _properties.get(name); } public override void set_property(string name, Invercargill.Element value) { _properties.set(name, value); save(); _engine.entity_modified(this); } public override void remove_property(string name) { _properties.remove(name); save(); _engine.entity_modified(this); } public override Invercargill.ReadOnlySet child_names { owned get { // Documents don't have children return new Invercargill.DataStructures.HashSet().as_read_only(); } } public override Entity? get_child(string name) { return null; // Documents don't have children } private void save() throws EngineError { var serializer = new EntitySerializer(); var data = serializer.serialize(this); _engine.configuration.storage.save_entity(_path, data); } } } // namespace Implexus.Entities ``` ### Properties Interface Documents implement `Invercargill.Properties` through `PropertyDictionary`: ```vala // Using Invercargill.DataStructures.PropertyDictionary var doc = container.create_document("user1", "User"); doc.set_property("name", new Invercargill.ValueElement("John")); doc.set_property("age", new Invercargill.ValueElement(30)); doc.set_property("active", new Invercargill.ValueElement(true)); // Reading properties var name = doc.get_property("name").to_value(); var age = doc.get_property("age").to_value(); ``` ## Category Entity A Category automatically generates Container entities based on expression evaluation over documents of a specific type. ### How It Works 1. Configure a Category with a type_label and expression 2. When a child is requested by name, the expression is evaluated on all documents of that type 3. A Container is returned containing documents where the expression result matches the requested name ### Implementation ```vala namespace Implexus.Entities { public class Category : AbstractEntity { private string _type_label; private string _expression; private Invercargill.Expressions.Expression? _compiled_expression; public Category(Engine engine, Path path, string type_label, string expression) { base(engine, path); _type_label = type_label; _expression = expression; _compiled_expression = null; } public override EntityType entity_type { get { return EntityType.CATEGORY; } } public override string configured_type_label { owned get { return _type_label; } } public override string configured_expression { owned get { return _expression; } } // Compile expression lazily private Invercargill.Expressions.Expression get_compiled_expression() throws EngineError { if (_compiled_expression == null) { var parser = new Invercargill.Expressions.ExpressionParser(); try { _compiled_expression = parser.parse(_expression); } catch (Error e) { throw new EngineError.EXPRESSION_ERROR( "Failed to parse expression: %s", e.message ); } } return (!) _compiled_expression; } // Child names are the unique values of the expression over all documents public override Invercargill.ReadOnlySet child_names { owned get { var names = new Invercargill.DataStructures.HashSet(); try { var expr = get_compiled_expression(); var evaluator = new Invercargill.Expressions.ExpressionEvaluator(); foreach (var doc in _engine.query_by_type(_type_label)) { var result = evaluator.evaluate(expr, doc.properties); if (result != null && !result.is_null()) { names.add(result.to_string()); } } } catch (Error e) { warning("Error evaluating category expression: %s", e.message); } return names.as_read_only(); } } // Get child returns a Container containing matching documents public override Entity? get_child(string name) { var matching_docs = new Invercargill.DataStructures.Vector(); try { var expr = get_compiled_expression(); var evaluator = new Invercargill.Expressions.ExpressionEvaluator(); foreach (var doc in _engine.query_by_type(_type_label)) { var result = evaluator.evaluate(expr, doc.properties); if (result != null && result.to_string() == name) { matching_docs.add(doc); } } } catch (Error e) { warning("Error evaluating category expression: %s", e.message); return null; } if (matching_docs.count == 0) { return null; } // Return a virtual container containing the matching documents return new CategoryContainer(_engine, _path.child(name), matching_docs); } // Categorys cannot create children public override Entity? create_container(string name) throws EngineError { throw new EngineError.INVALID_OPERATION("Cannot create children in a category"); } public override Invercargill.Enumerable get_children() { return child_names.select(name => get_child(name)) .where(entity => entity != null) .select(entity => (!) entity); } } } // namespace Implexus.Entities ``` ### CategoryContainer A virtual container that contains documents matched by the category: ```vala namespace Implexus.Entities { internal class CategoryContainer : AbstractEntity { private Invercargill.DataStructures.Vector _documents; public CategoryContainer( Engine engine, Path path, Invercargill.DataStructures.Vector documents ) { base(engine, path); _documents = documents; } public override EntityType entity_type { get { return EntityType.CONTAINER; } } public override Invercargill.ReadOnlySet child_names { owned get { var names = new Invercargill.DataStructures.HashSet(); foreach (var doc in _documents) { names.add(doc.name); } return names.as_read_only(); } } public override Entity? get_child(string name) { foreach (var doc in _documents) { if (doc.name == name) { return doc; } } return null; } public override Invercargill.Enumerable get_children() { return _documents.as_enumerable(); } // Read-only - cannot create children public override Entity? create_container(string name) throws EngineError { throw new EngineError.INVALID_OPERATION("Cannot create children in a category container"); } public override void delete() throws EngineError { throw new EngineError.INVALID_OPERATION("Cannot delete a category container"); } } } // namespace Implexus.Entities ``` ### Category Usage Example ```vala // Create documents with a "status" property var tasks = engine.get_root().create_container("tasks"); var task1 = tasks.create_document("task1", "Task"); task1.set_property("status", new ValueElement("pending")); task1.set_property("title", new ValueElement("First task")); var task2 = tasks.create_document("task2", "Task"); task2.set_property("status", new ValueElement("done")); task2.set_property("title", new ValueElement("Second task")); var task3 = tasks.create_document("task3", "Task"); task3.set_property("status", new ValueElement("pending")); task3.set_property("title", new ValueElement("Third task")); // Create a category that groups by status var by_status = tasks.create_category("by_status", "Task", "status"); // Navigate the category var pending = by_status.get_child("pending"); // Returns Container with task1, task3 var done = by_status.get_child("done"); // Returns Container with task2 // List all status values foreach (var status in by_status.child_names) { print("Status: %s\n", status); } ``` ## Index Entity An Index provides text search over documents. Requesting a child returns a Container containing documents matching the search term. ### Implementation ```vala namespace Implexus.Entities { public class Index : AbstractEntity { private string _type_label; private string _expression; private Invercargill.Expressions.Expression? _compiled_expression; public Index(Engine engine, Path path, string type_label, string expression) { base(engine, path); _type_label = type_label; _expression = expression; _compiled_expression = null; } public override EntityType entity_type { get { return EntityType.INDEX; } } public override string configured_type_label { owned get { return _type_label; } } public override string configured_expression { owned get { return _expression; } } // Index children are opaque - cannot list them public override Invercargill.ReadOnlySet child_names { owned get { // Indexes have opaque children - return empty set return new Invercargill.DataStructures.HashSet().as_read_only(); } } // Get child performs text search and returns a Container with results public override Entity? get_child(string search_term) { var matching_docs = new Invercargill.DataStructures.Vector(); try { var expr = get_compiled_expression(); var evaluator = new Invercargill.Expressions.ExpressionEvaluator(); var search_lower = search_term.down(); foreach (var doc in _engine.query_by_type(_type_label)) { var result = evaluator.evaluate(expr, doc.properties); if (result != null) { var text = result.to_string().down(); if (text.contains(search_lower)) { matching_docs.add(doc); } } } } catch (Error e) { warning("Error evaluating index expression: %s", e.message); return null; } if (matching_docs.count == 0) { return null; } // Return an IndexResult - a Container containing matching documents return new IndexResult(_engine, _path.child(search_term), search_term, matching_docs); } // Indexes cannot create children public override Entity? create_container(string name) throws EngineError { throw new EngineError.INVALID_OPERATION("Cannot create children in an index"); } private Invercargill.Expressions.Expression get_compiled_expression() throws EngineError { if (_compiled_expression == null) { var parser = new Invercargill.Expressions.ExpressionParser(); try { _compiled_expression = parser.parse(_expression); } catch (Error e) { throw new EngineError.EXPRESSION_ERROR( "Failed to parse expression: %s", e.message ); } } return (!) _compiled_expression; } } } // namespace Implexus.Entities ``` ### IndexResult The Container returned by an Index query: ```vala namespace Implexus.Entities { public class IndexResult : AbstractEntity { private string _search_term; private Invercargill.DataStructures.Vector _documents; public IndexResult( Engine engine, Path path, string search_term, Invercargill.DataStructures.Vector documents ) { base(engine, path); _search_term = search_term; _documents = documents; } public string search_term { get { return _search_term; } } public override EntityType entity_type { get { return EntityType.CONTAINER; } } public override Invercargill.ReadOnlySet child_names { owned get { var names = new Invercargill.DataStructures.HashSet(); foreach (var doc in _documents) { names.add(doc.name); } return names.as_read_only(); } } public override Entity? get_child(string name) { foreach (var doc in _documents) { if (doc.name == name) { return doc; } } return null; } public override Invercargill.Enumerable get_children() { return _documents.as_enumerable(); } // Read-only public override Entity? create_container(string name) throws EngineError { throw new EngineError.INVALID_OPERATION("Cannot create children in an index result"); } public override void delete() throws EngineError { throw new EngineError.INVALID_OPERATION("Cannot delete an index result"); } } } // namespace Implexus.Entities ``` ### Index Usage Example ```vala // Create documents with searchable content var articles = engine.get_root().create_container("articles"); var article1 = articles.create_document("article1", "Article"); article1.set_property("title", new ValueElement("Introduction to Vala")); article1.set_property("content", new ValueElement("Vala is a programming language...")); var article2 = articles.create_document("article2", "Article"); article2.set_property("title", new ValueElement("Advanced Vala Techniques")); article2.set_property("content", new ValueElement("This article covers advanced...")); // Create an index over the content property var search = articles.create_index("search", "Article", "content"); // Search for documents var results = search.get_child("Vala"); // Returns IndexResult with article1, article2 var intro_results = search.get_child("Introduction"); // Returns IndexResult with article1 // Navigate results foreach (var doc in results.get_children()) { print("Found: %s\n", doc.name); } ``` ## Entity Type Comparison | Feature | Container | Document | Category | Index | |---------|----------|----------|-----------|-------| | Can contain children | Yes | No | Virtual | Virtual | | Has properties | No | Yes | No | No | | Has type_label | No | Yes | No | No | | Child names enumerable | Yes | - | Yes | No | | Expression-based | No | No | Yes | Yes | | Children are persistent | Yes | - | No | No | | Can create children | Yes | No | No | No |