SeaORM Entities
Table models with typed columns, relation enums, and junction tables for many-to-many relationships. Plus from_model() and to_active_model() conversions.
You’ve defined your data model. Now you need a database entity. And a conversion layer. And CRUD methods. And an API module. And HTTP handlers. And IPC commands. And a TypeScript client. And an admin UI registry.
For every single entity.
That’s hundreds of lines of code that look almost identical across your codebase — the kind of boilerplate where the 95th copy-paste is where bugs hide.
Ontogen is a build-time code generator that runs in your build.rs. You write one annotated struct per entity, and a layered pipeline of independent generators produces everything downstream.
Schema/*.rs ──► parse_schema() ──► SchemaOutput │ ┌────────────┼────────────┐ ▼ ▼ ▼ gen_seaorm gen_markdown_io gen_dtos independent │ generators ▼ SeaOrmOutput gen_store() ──► StoreOutput │ ▼ gen_api() ──► ApiOutput │ ▼ gen_servers() ──► ServersOutput (also emits TypeScript clients and admin registry)Each generator is standalone. You can run the full pipeline, drive it through the Pipeline builder, or pick individual stages. Upstream outputs enrich downstream generators but are never required.
#[derive(OntologyEntity)]#[ontology(entity, table = "tasks", directory = "tasks", prefix = "task")]pub struct Task { #[ontology(id)] pub id: String,
pub name: String, pub description: Option<String>,
#[ontology(enum_field)] pub status: Option<TaskStatus>,
#[ontology(relation(belongs_to, target = "Agent"))] pub assignee_id: Option<String>,
#[ontology(relation(many_to_many, target = "Requirement"))] pub fulfills: Vec<String>,}From that single struct definition, one cargo build generates:
SeaORM Entities
Table models with typed columns, relation enums, and junction tables for many-to-many relationships. Plus from_model() and to_active_model() conversions.
CRUD Store
list(), get(), create(), update(), delete() methods with relation population, junction sync, and lifecycle hook call sites. Update structs with apply() methods.
API Layer
Forwarding functions that bridge your store to your transport layer. Generated modules merge seamlessly with hand-written custom endpoints.
Server Transports
Axum HTTP route handlers, Tauri IPC commands, and MCP tool definitions — all generated from the same API surface with consistent naming.
TypeScript Clients
Typed client functions for HTTP and Tauri IPC, split transport layers that pick the right backend at runtime, and admin UI registry with per-field metadata.
Markdown I/O
YAML-frontmatter parsers, Markdown writers, and filesystem operations for content-as-code workflows. Read and write entities as Markdown files.
Independent generators, not a monolith. Each generator is a standalone function. gen_seaorm doesn’t know about gen_api. You can use just the persistence layer and skip everything else, or run the full pipeline. This isn’t a framework that owns your architecture — it’s a toolbox.
Generated and custom code coexist. Generated code lands in generated/ subdirectories. Your hand-written modules live alongside them. The API layer scans both and merges them into a unified intermediate representation. Downstream generators see no difference between generated and custom endpoints.
Typed IRs between stages. Each generator returns a plain Rust struct — SchemaOutput, SeaOrmOutput, StoreOutput, ApiOutput, ServersOutput. No magic, no framework types. Just data that the next stage can use to make smarter decisions.
Lifecycle hooks you own. Hook files are scaffolded once per entity and never overwritten. You fill in before_create, after_update, before_delete — the generated store calls them at the right time. Your business logic stays in files you control.