# Class Hierarchy This document describes the class hierarchy and relationships between types in Implexus. ## Async I/O Model All I/O operations in Implexus are asynchronous. The class hierarchy reflects this: - **Engine implementations** provide `*_async` methods for all I/O operations - **Entity implementations** provide `*_async` methods for navigation, CRUD, and property access - **Identity properties** (path, name, entity_type, engine) remain synchronous - **No wrapper classes** - AsyncEngine and AsyncEntity have been removed; async is built into the base interfaces ## Inheritance Diagram ```mermaid graph TB subgraph Interfaces EntityI[Entity Interface] EngineI[Engine Interface] StorageI[Storage Interface] DbmI[Dbm Interface] TransactionI[Transaction Interface] end subgraph Abstract Classes AbstractEntity[AbstractEntity] end subgraph Entity Implementations Container[Container] Document[Document] Category[Category] Index[Index] Catalogue[Catalogue] IndexResult[IndexResult] end subgraph Engine Implementations EmbeddedEngine[EmbeddedEngine] RemoteEngine[RemoteEngine] end subgraph Storage Implementations DefaultStorage[DefaultStorage] AsyncDbmQueue[AsyncDbmQueue] FilesystemDbm[FilesystemDbm] GdbmDbm[GdbmDbm] LmdbDbm[LmdbDbm] EmbeddedTransaction[EmbeddedTransaction] end EntityI --> AbstractEntity AbstractEntity --> Container AbstractEntity --> Document AbstractEntity --> Category AbstractEntity --> Index AbstractEntity --> Catalogue Index --> IndexResult EngineI --> EmbeddedEngine EngineI --> RemoteEngine StorageI --> DefaultStorage DbmI --> FilesystemDbm DbmI --> GdbmDbm DbmI --> LmdbDbm AsyncDbmQueue --> DbmI TransactionI --> EmbeddedTransaction ``` ## AbstractEntity Class Base implementation providing common functionality for all entity types. All I/O operations are async. ```vala namespace Implexus.Entities { public abstract class AbstractEntity : Object, Entity { // Protected fields protected weak Engine _engine; protected EntityPath _path; // === Identity (Synchronous - No I/O) === public unowned Engine engine { get { return _engine; } } public EntityPath path { owned get { return _path; } } public string name { owned get { return _path.name; } } public abstract EntityType entity_type { get; } public abstract string type_label { owned get; } public abstract string configured_expression { owned get; } public abstract string configured_type_label { owned get; } // === Parent/Child Navigation (Async) === public async Entity? get_parent_async() throws EntityError { if (_path.is_root) return null; return yield _engine.get_entity_or_null_async(_path.parent); } public virtual async Invercargill.ReadOnlySet get_child_names_async() throws EntityError { return engine.configuration.storage.get_child_names(_path); } public virtual async Entity? get_child_async(string name) throws EntityError { var child_path = _path.child(name); return yield _engine.get_entity_or_null_async(child_path); } public virtual async Entity[] get_children_async() throws EntityError { var names = yield get_child_names_async(); var children = new Entity[0]; foreach (var name in names) { var child = yield get_child_async(name); if (child != null) { children += (!) child; } } return children; } // === Creation methods - override in Container (Async) === public virtual async Entity? create_container_async(string name) throws EntityError { throw new EntityError.INVALID_OPERATION( "Cannot create container on %s", entity_type.to_string() ); } public virtual async Entity? create_document_async(string name, string type_label) throws EntityError { throw new EntityError.INVALID_OPERATION( "Cannot create document on %s", entity_type.to_string() ); } public virtual async Entity? create_category_async(string name, string type_label, string expression) throws EntityError { throw new EntityError.INVALID_OPERATION( "Cannot create category on %s", entity_type.to_string() ); } public virtual async Entity? create_index_async(string name, string type_label, string expression) throws EntityError { throw new EntityError.INVALID_OPERATION( "Cannot create index on %s", entity_type.to_string() ); } public virtual async Entity? create_catalogue_async(string name, string type_label, string expression) throws EntityError { throw new EntityError.INVALID_OPERATION( "Cannot create catalogue on %s", entity_type.to_string() ); } // === Document operations - override in Document (Async) === public virtual async Invercargill.Properties get_properties_async() throws EntityError { throw new EntityError.INVALID_OPERATION("Not a document"); } public virtual async Invercargill.Element? get_entity_property_async(string name) throws EntityError { throw new EntityError.INVALID_OPERATION("Not a document"); } public virtual async void set_entity_property_async(string name, Invercargill.Element value) throws EntityError { throw new EntityError.INVALID_OPERATION("Not a document"); } public virtual async void remove_property_async(string name) throws EntityError { throw new EntityError.INVALID_OPERATION("Not a document"); } // === Lifecycle (Async) === public virtual async void delete_async() throws EntityError { engine.configuration.storage.delete_entity(_path); var parent = yield get_parent_async(); if (parent != null) { engine.configuration.storage.unregister_child(parent.path, name); } engine.entity_deleted(_path); } public virtual bool exists { get { return engine.configuration.storage.has_entity(_path); } } // === Set Operations (Async) === public async EntitySet as_set_async() { return new EntitySet(this); } // Note: Invercargill.Element methods are inherited and implemented here } } // namespace Implexus.Entities ``` ## Engine Implementations ### EmbeddedEngine Direct in-process engine implementation with async I/O. ```vala namespace Implexus.Engine { public class EmbeddedEngine : Object, Engine { private Storage _storage; private Entity? _root; private AsyncDbmQueue _queue; private Transaction? _current_transaction; public EmbeddedEngine(Storage storage) { _storage = storage; _queue = new AsyncDbmQueue(storage.dbm); _queue.start(); } // === Root Access (Async) === public async Entity get_root_async() throws EngineError { if (_root != null) return (!) _root; _root = new Container(this, new EntityPath.root()); return _root; } // === Path-Based Access (Async) === public async Entity? get_entity_async(EntityPath path) throws EngineError { var entity = yield get_entity_or_null_async(path); if (entity == null) { throw new EngineError.ENTITY_NOT_FOUND("Entity not found: %s", path.to_string()); } return entity; } public async Entity? get_entity_or_null_async(EntityPath path) throws EngineError { if (!_storage.has_entity(path)) return null; return yield load_entity_async(path); } public async bool entity_exists_async(EntityPath path) throws EngineError { return _storage.has_entity(path); } // === Query Operations (Async) === public async Entity[] query_by_type_async(string type_label) throws EngineError { var paths = _storage.get_paths_by_type(type_label); var entities = new Entity[0]; foreach (var path in paths) { var entity = yield get_entity_or_null_async(path); if (entity != null) { entities += (!) entity; } } return entities; } public async Entity[] query_by_expression_async(string type_label, string expression) throws EngineError { var evaluator = new Invercargill.Expressions.ExpressionEvaluator(); var all = yield query_by_type_async(type_label); var results = new Entity[0]; foreach (var entity in all) { try { var props = yield entity.get_properties_async(); var result = evaluator.evaluate(expression, props); if (result != null && !(result as Invercargill.NullElement).is_null()) { results += entity; } } catch { // Skip entities that fail evaluation } } return results; } // === Transactions (Async) === public async Transaction begin_transaction_async() throws EngineError { if (_current_transaction != null) { throw new EngineError.TRANSACTION_ERROR("Transaction already active"); } _storage.begin_transaction(); _current_transaction = new EmbeddedTransaction(() => { _storage.commit_transaction(); _current_transaction = null; }, () => { _storage.rollback_transaction(); _current_transaction = null; }); return _current_transaction; } public async void commit_async() throws EngineError { if (_current_transaction == null) { throw new EngineError.TRANSACTION_ERROR("No transaction active"); } yield _current_transaction.commit_async(); } public async void rollback_async() { if (_current_transaction != null) { yield _current_transaction.rollback_async(); } } public bool in_transaction { get { return _current_transaction != null; } } public StorageConfiguration configuration { owned get { return new StorageConfiguration(_storage); } } private async Entity load_entity_async(EntityPath path) throws EngineError { var data = _storage.load_entity(path); if (data == null) return null; var deserializer = new EntityDeserializer(); return deserializer.deserialize((!) data, this, path); } } } // namespace Implexus.Engine ``` ### RemoteEngine Client for connecting to implexusd daemon. All operations are async over the network. ```vala namespace Implexus.Engine { public class RemoteEngine : Object, Engine { private SocketConnection _connection; private MessageReader _reader; private MessageWriter _writer; public RemoteEngine.connect(string host, uint16 port) throws EngineError { try { var client = new SocketClient(); _connection = client.connect_to_host(host, port, null); var stream = _connection.get_input_stream(); var output = _connection.get_output_stream(); _reader = new MessageReader(stream); _writer = new MessageWriter(output); } catch (Error e) { throw new EngineError.CONNECTION_ERROR("Failed to connect: %s", e.message); } } public async Entity get_root_async() throws EngineError { var entity = yield get_entity_or_null_async(new EntityPath.root()); if (entity == null) { return new RemoteContainer(this, new EntityPath.root()); } return entity; } public async Entity? get_entity_async(EntityPath path) throws EngineError { var response = yield send_request_async(new GetEntityRequest(path)); if (response is EntityNotFoundResponse) { throw new EngineError.ENTITY_NOT_FOUND("Entity not found: %s", path.to_string()); } return ((EntityResponse) response).entity; } public async Entity? get_entity_or_null_async(EntityPath path) throws EngineError { try { return yield get_entity_async(path); } catch { return null; } } public async bool entity_exists_async(EntityPath path) throws EngineError { var response = yield send_request_async(new EntityExistsRequest(path)); return ((BooleanResponse) response).value; } // ... other async methods send protocol messages private async Response send_request_async(Request request) throws EngineError { _writer.write(request); var response = yield _reader.read_response_async(); if (response is ErrorResponse) { throw new EngineError.PROTOCOL_ERROR(((ErrorResponse) response).message); } return response; } } } // namespace Implexus.Engine ``` ## Storage Implementations ### DefaultStorage ```vala namespace Implexus.Storage { public class DefaultStorage : Object, Storage { private Dbm _dbm; private AsyncDbmQueue _queue; private Invercargill.DataStructures.Dictionary> _children; private Invercargill.DataStructures.Category> _type_index; public DefaultStorage(Dbm dbm) { _dbm = dbm; _queue = new AsyncDbmQueue(dbm); _children = new Invercargill.DataStructures.Dictionary>(); _type_index = new Invercargill.DataStructures.Category>(); } public bool has_entity(EntityPath path) { return _dbm.has_key(entity_key(path)); } public uint8[]? load_entity(EntityPath path) { var data = _dbm.get(entity_key(path)); return data?.to_bytes(); } public void save_entity(EntityPath path, uint8[] data) throws StorageError { _dbm.set(entity_key(path), new Invercargill.BinaryData.from_bytes(data)); } public void delete_entity(EntityPath path) throws StorageError { _dbm.delete(entity_key(path)); } public Invercargill.ReadOnlySet get_child_names(EntityPath parent_path) { var key = children_key(parent_path); if (!_children.has(key)) { return new Invercargill.DataStructures.HashSet().as_read_only(); } return _children.get(key).as_read_only(); } public void register_child(EntityPath parent, string child_name) { var key = children_key(parent); if (!_children.has(key)) { _children.set(key, new Invercargill.DataStructures.HashSet()); } _children.get(key).add(child_name); } public void unregister_child(EntityPath parent, string child_name) { var key = children_key(parent); if (_children.has(key)) { _children.get(key).remove(child_name); } } public Invercargill.Enumerable get_paths_by_type(string type_label) { if (!_type_index.has(type_label)) { return new Invercargill.DataStructures.Vector().as_enumerable(); } return _type_index.get(type_label) .select(path_str => new EntityPath(path_str)); } public void register_type(EntityPath path, string type_label) { if (!_type_index.has(type_label)) { _type_index.set(type_label, new Invercargill.DataStructures.Vector()); } _type_index.get(type_label).add(path.to_string()); } public void unregister_type(EntityPath path, string type_label) { if (_type_index.has(type_label)) { _type_index.get(type_label).remove_all(path.to_string()); } } private string entity_key(EntityPath path) { return @"entity:$(path.to_string())"; } private string children_key(EntityPath path) { return @"children:$(path.to_string())"; } // Transaction methods delegate to Dbm public void begin_transaction() throws StorageError { _dbm.begin_transaction(); } public void commit_transaction() throws StorageError { _dbm.commit_transaction(); } public void rollback_transaction() { _dbm.rollback_transaction(); } public bool in_transaction { get { return _dbm.in_transaction; } } public void open() throws StorageError { _dbm.open(); _queue.start(); load_indices(); } public void close() { _queue.shutdown(); _dbm.close(); } public void compact() throws StorageError { _dbm.compact(); } private void load_indices() { // Rebuild child and type indices from entity data foreach (var entry in _dbm.keys) { if (entry.has_prefix("entity:")) { // Parse entity and update indices } } } } } // namespace Implexus.Storage ``` ### FilesystemDbm Simple file-based DBM implementation (single-threaded). ```vala namespace Implexus.Storage { public class FilesystemDbm : Object, Dbm { private string _path; private Invercargill.DataStructures.Dictionary _data; private bool _open; private int _transaction_depth; /** * FilesystemDbm does not support concurrent reads. * All operations go through the AsyncDbmQueue worker thread. */ public bool supports_concurrent_reads { get { return false; } } public FilesystemDbm(string path) { _path = path; _data = new Invercargill.DataStructures.Dictionary(); _open = false; _transaction_depth = 0; } public bool has_key(string key) { ensure_open(); return _data.has(key); } public Invercargill.BinaryData? @get(string key) { ensure_open(); if (!_data.has(key)) return null; return _data.get(key); } public void @set(string key, Invercargill.BinaryData value) throws StorageError { ensure_open(); _data.set(key, value); } public void delete(string key) throws StorageError { ensure_open(); _data.remove(key); } public Invercargill.Enumerable keys { owned get { ensure_open(); return _data.keys; } } public void begin_transaction() throws StorageError { ensure_open(); _transaction_depth++; } public void commit_transaction() throws StorageError { ensure_open(); if (_transaction_depth > 0) { _transaction_depth--; if (_transaction_depth == 0) { sync(); } } } public void rollback_transaction() { if (_transaction_depth > 0) { _transaction_depth--; // Reload from disk load_from_disk(); } } public bool in_transaction { get { return _transaction_depth > 0; } } public void open() throws StorageError { if (_open) return; var file = File.new_for_path(_path); if (file.query_exists()) { load_from_disk(); } else { file.get_parent().make_directory_with_parents(); } _open = true; } public void close() { if (!_open) return; sync(); _open = false; } public void sync() throws StorageError { // Write all data to disk var file = File.new_for_path(_path); try { var stream = file.replace(null, false, FileCreateFlags.NONE); var writer = new DataOutputStream(stream); // Write header writer.write_string("IMPXDBM1\n"); // Write entry count writer.put_int64(_data.count); // Write entries foreach (var entry in _data.entries) { writer.put_int64(entry.key.length); writer.write_string(entry.key); writer.put_int64(entry.value.length); writer.write(entry.data); } } catch (Error e) { throw new StorageError.WRITE_ERROR("Failed to sync: %s", e.message); } } public void compact() throws StorageError { sync(); } private void ensure_open() { if (!_open) { critical("DBM not open"); } } private void load_from_disk() throws StorageError { // Read all data from disk var file = File.new_for_path(_path); try { var stream = new DataInputStream(file.read()); // Read header var header = stream.read_line(); if (header != "IMPXDBM1") { throw new StorageError.CORRUPTION_ERROR("Invalid DBM file format"); } // Read entries int64 count = stream.read_int64(); for (int64 i = 0; i < count; i++) { int64 key_len = stream.read_int64(); uint8[] key_bytes = new uint8[key_len]; stream.read(key_bytes); string key = (string) key_bytes; int64 value_len = stream.read_int64(); uint8[] value = new uint8[value_len]; stream.read(value); _data.set(key, new Invercargill.BinaryData.from_bytes(value)); } } catch (Error e) { throw new StorageError.READ_ERROR("Failed to load: %s", e.message); } } } } // namespace Implexus.Storage ``` ### GdbmDbm GDBM-based implementation (single-threaded). ```vala namespace Implexus.Storage { public class GdbmDbm : Object, Dbm { private string _path; private void* _dbf; // GDBM_FILE handle /** * GDBM does not support concurrent reads. * All operations go through the AsyncDbmQueue worker thread. */ public bool supports_concurrent_reads { get { return false; } } // ... GDBM-specific implementation } } // namespace Implexus.Storage ``` ### LmdbDbm LMDB-based implementation with concurrent read support. ```vala namespace Implexus.Storage { public class LmdbDbm : Object, Dbm { private string _path; private void* _env; // MDB_env* private void* _txn; // Current transaction MDB_txn* /** * LMDB supports concurrent reads via MVCC. * Read operations can spawn their own threads, * while writes go through the AsyncDbmQueue. */ public bool supports_concurrent_reads { get { return true; } } // ... LMDB-specific implementation } } // namespace Implexus.Storage ``` ## Class Summary | Class | Extends | Implements | Purpose | |-------|---------|------------|---------| | `AbstractEntity` | `Object` | `Entity` | Base entity implementation with async methods | | `Container` | `AbstractEntity` | - | Container for child entities | | `Document` | `AbstractEntity` | - | Properties-based document | | `Category` | `AbstractEntity` | - | Expression-based auto-categories | | `Index` | `AbstractEntity` | - | Text search with dynamic results | | `Catalogue` | `AbstractEntity` | - | Key-based document grouping | | `IndexResult` | `AbstractEntity` | - | Container returned by index query | | `EmbeddedEngine` | `Object` | `Engine` | In-process engine with async I/O | | `RemoteEngine` | `Object` | `Engine` | Client for daemon with async network I/O | | `DefaultStorage` | `Object` | `Storage` | Entity persistence | | `AsyncDbmQueue` | `Object` | - | Queue for async DBM operations | | `FilesystemDbm` | `Object` | `Dbm` | File-based key-value store (single-threaded) | | `GdbmDbm` | `Object` | `Dbm` | GDBM-based store (single-threaded) | | `LmdbDbm` | `Object` | `Dbm` | LMDB-based store (concurrent reads) | | `EmbeddedTransaction` | `Object` | `Transaction` | Transaction implementation | ## Removed Classes The following classes have been removed as part of the async refactor: | Class | Reason for Removal | |-------|-------------------| | `AsyncEngine` | Async is now built into the base `Engine` interface | | `AsyncEntity` | Async is now built into the base `Entity` interface | | `with_write_transaction()` helper | Vala doesn't support async delegates; use manual begin/commit/rollback | ## Async Method Summary ### Engine Interface | Method | Description | |--------|-------------| | `get_root_async()` | Get root entity | | `get_entity_async(path)` | Get entity by path | | `get_entity_or_null_async(path)` | Get entity or null | | `entity_exists_async(path)` | Check entity existence | | `query_by_type_async(type_label)` | Query by type | | `query_by_expression_async(type_label, expr)` | Query with filter | | `begin_transaction_async()` | Start transaction | | `commit_async()` | Commit transaction | | `rollback_async()` | Rollback transaction | ### Entity Interface | Method | Description | |--------|-------------| | `get_parent_async()` | Get parent entity | | `get_child_names_async()` | Get child names | | `get_child_async(name)` | Get child by name | | `get_children_async()` | Get all children | | `create_container_async(name)` | Create container child | | `create_document_async(name, type)` | Create document child | | `create_category_async(name, type, expr)` | Create category child | | `create_index_async(name, type, expr)` | Create index child | | `create_catalogue_async(name, type, expr)` | Create catalogue child | | `get_properties_async()` | Get document properties | | `get_entity_property_async(name)` | Get property value | | `set_entity_property_async(name, value)` | Set property value | | `remove_property_async(name)` | Remove property | | `delete_async()` | Delete entity | | `as_set_async()` | Create EntitySet |