Skip to content

Public API

Every generator in Ontogen is a standalone function. You call them from build.rs, pass configuration, and get back a typed result. No framework context, no trait implementations.

The functions are listed here in pipeline order — each one’s output can feed the next.

pub fn parse_schema(config: &SchemaConfig) -> Result<SchemaOutput, CodegenError>

Reads .rs files from your schema directory, finds structs annotated with #[derive(OntologyEntity)] and #[ontology(entity, ...)], and parses them into EntityDef metadata using syn.

This is always the starting point. Every other generator needs the entity list that comes out of this.

Parameters:

ParameterTypeDescription
config&SchemaConfigPoints to the directory containing your schema .rs files.

Returns: SchemaOutput containing entities: Vec<EntityDef>.

Side effects: Emits cargo:rerun-if-changed directives for every .rs file in the schema directory. When you edit an entity, Cargo re-runs build.rs automatically.

let schema = parse_schema(&SchemaConfig {
schema_dir: "src/schema".into(),
})?;
// schema.entities is now available for all downstream generators

pub fn gen_seaorm(
entities: &[EntityDef],
config: &SeaOrmConfig,
) -> Result<SeaOrmOutput, CodegenError>

Generates SeaORM persistence code from your entity definitions:

  • Entity modules — table models with typed columns and #[sea_orm(...)] attributes.
  • Relation enumsBelongsTo, HasMany, and Related implementations for cross-entity navigation.
  • Junction tables — full SeaORM entities for many-to-many relationships.
  • Conversion functionsfrom_model() and to_active_model() methods on your schema types.

Parameters:

ParameterTypeDescription
entities&[EntityDef]Entity definitions from parse_schema.
config&SeaOrmConfigOutput paths for entities and conversions.

Returns: SeaOrmOutput with metadata about generated tables, junction tables, and conversion functions. Downstream generators (especially gen_store) use this to produce exact table and column references rather than guessing from conventions.

let seaorm = gen_seaorm(&schema.entities, &SeaOrmConfig {
entity_output: "src/persistence/db/entities/generated".into(),
conversion_output: "src/persistence/db/conversions/generated".into(),
skip_conversions: vec![],
})?;

pub fn gen_markdown_io(
entities: &[EntityDef],
config: &MarkdownIoConfig,
) -> Result<(), CodegenError>

Generates Markdown I/O code for content-as-code workflows:

  • Parser dispatch — reads a Markdown file, extracts YAML frontmatter, and deserializes it into your schema type.
  • Writers — serialize your schema type back to Markdown with YAML frontmatter.
  • Filesystem operations — read/write entities as Markdown files on disk, with directory-based organization.

This generator is a sibling of gen_seaorm — it consumes SchemaOutput independently and produces no output struct. Use it when your entities live in Markdown files (wikis, knowledge bases, content repos) rather than (or in addition to) a database.

Parameters:

ParameterTypeDescription
entities&[EntityDef]Entity definitions from parse_schema.
config&MarkdownIoConfigOutput directory for generated code.

Returns: (). This generator only writes files; it produces no IR for downstream consumption.

gen_markdown_io(&schema.entities, &MarkdownIoConfig {
output_dir: "src/persistence/markdown".into(),
})?;

pub fn gen_dtos(
entities: &[EntityDef],
config: &DtoConfig,
) -> Result<(), CodegenError>

Generates CreateEntityInput and UpdateEntityInput structs — the input types for create and update operations.

For each entity, you get:

  • CreateEntityInput — all fields except id (if auto-generated). Required fields are required; optional fields are Option.
  • UpdateEntityInput — all non-ID fields wrapped in Option. Only provided fields are applied on update.

Parameters:

ParameterTypeDescription
entities&[EntityDef]Entity definitions from parse_schema.
config&DtoConfigOutput directory for generated DTO files.

Returns: (). Writes files only.

gen_dtos(&schema.entities, &DtoConfig {
output_dir: "src/schema/dto".into(),
})?;

pub fn gen_store(
entities: &[EntityDef],
seaorm: Option<&SeaOrmOutput>,
config: &StoreConfig,
) -> Result<StoreOutput, CodegenError>

Generates the CRUD store layer:

  • CRUD methodslist_*(), get_*(), create_*(), update_*(), delete_*() as impl Store blocks.
  • EntityUpdate structs — partial update types with apply() methods that patch only provided fields.
  • From implementations — conversions from DTOs to entity types and update types.
  • Relation populationpopulate_*_relations() helpers that load junction table data after fetching.
  • Lifecycle hook call sites — generated CRUD methods call hooks::before_create(), hooks::after_update(), etc. at the right points.
  • Hook scaffolding — creates hooks/entity_name.rs files once per entity, with empty hook function bodies you fill in. These files are never overwritten.

Parameters:

ParameterTypeDescription
entities&[EntityDef]Entity definitions from parse_schema.
seaormOption<&SeaOrmOutput>When provided, uses exact table/column names. When None, falls back to naming conventions.
config&StoreConfigOutput directory and optional hooks directory.

Returns: StoreOutput with metadata about generated methods, scaffolded hooks, and change channels.

let store = gen_store(&schema.entities, Some(&seaorm), &StoreConfig {
output_dir: "src/store/generated".into(),
hooks_dir: Some("src/store/hooks".into()),
schema_module_path: ontogen::DEFAULT_SCHEMA_MODULE_PATH.into(),
})?;

pub fn gen_api(
entities: &[EntityDef],
config: &ApiConfig,
) -> Result<ApiOutput, CodegenError>

Generates the API forwarding layer and scans for hand-written endpoints:

  • Generated CRUD modules — per-entity modules with list(), get_by_id(), create(), update(), delete() functions that forward to store methods.
  • Scanned custom modules — parses hand-written .rs files in scan_dirs using syn, extracting function signatures and doc comments.
  • Unified API surface — merges generated and scanned functions into a single ApiOutput. Downstream generators see no difference between generated and custom endpoints.

Parameters:

ParameterTypeDescription
entities&[EntityDef]Entity definitions from parse_schema.
config&ApiConfigOutput directory, exclusions, scan directories, and state type names.

Returns: ApiOutput with all API modules (generated + scanned). Each module contains function metadata with signatures, doc comments, source origin, and classified operation kind.

let api = gen_api(&schema.entities, &ApiConfig {
output_dir: "src/api/v1/generated".into(),
exclude: vec![],
scan_dirs: vec!["src/api/v1".into()],
state_type: "AppState".to_string(),
store_type: Some("Store".to_string()),
schema_module_path: ontogen::DEFAULT_SCHEMA_MODULE_PATH.into(),
})?;

pub fn gen_servers(
api: Option<&ApiOutput>,
scan_dirs: &[PathBuf],
config: &ServersConfig,
) -> Result<ServersOutput, CodegenError>

Generates transport-specific server handlers from the unified API surface:

  • Axum HTTP handlers — route functions with GET/POST/PUT/DELETE methods, path extraction, JSON request/response bodies, and error mapping. Includes an entity_routes() function returning a configured Router.
  • Tauri IPC commands#[tauri::command] functions with State extraction and specta type annotations for TypeScript binding generation.
  • MCP tool definitions — tool name, description (from doc comments), and JSON Schema parameter definitions for the Model Context Protocol.
  • TypeScript clients and admin registry — driven by ServersConfig.client_generators. The same call emits HTTP-only clients, the HTTP-or-IPC split transport, and the admin-UI registry.

Each transport reads the same ApiOutput and produces handlers appropriate for its protocol. The OpKind classification drives HTTP verb selection, route structure, and parameter handling.

Parameters:

ParameterTypeDescription
apiOption<&ApiOutput>Structured API metadata. When None, falls back to scanning source files with syn. Reserved for future enrichment; currently ignored.
scan_dirs&[PathBuf]Additional directories to scan. Reserved for future enrichment; currently ignored — config.api_dir is always scanned.
config&ServersConfigTransport configuration — which server generators to run, which client generators to run, import paths, naming, route prefixes, pagination, etc.

Returns: ServersOutput describing all generated HTTP routes, IPC commands, and MCP tools.

let servers = gen_servers(Some(&api), &[], &ServersConfig {
api_dir: "src/api/v1".into(),
state_type: "AppState".to_string(),
service_import_path: "crate::api::v1".to_string(),
types_import_path: "crate::schema".to_string(),
state_import: "crate::AppState".to_string(),
naming: NamingConfig::default(),
generators: vec![
ServerGenerator::HttpAxum {
output: "src/api/transport/http/generated.rs".into(),
},
ServerGenerator::TauriIpc {
output: "src/api/transport/ipc/generated.rs".into(),
},
],
client_generators: vec![],
// schema_entities is required for the admin-registry client generator;
// pass `schema.entities.clone()` from `parse_schema`. The Pipeline
// builder forwards this automatically.
schema_entities: schema.entities.clone(),
// ... other config fields
})?;

pub struct Pipeline { /* ... */ }
impl Pipeline {
pub fn new(schema_dir: impl Into<PathBuf>) -> Self;
pub fn schema_module_path(self, path: impl Into<String>) -> Self;
pub fn seaorm(self, entity_output: impl Into<PathBuf>, conversion_output: impl Into<PathBuf>) -> Self;
pub fn seaorm_skip_conversions(self, skip: Vec<String>) -> Self;
pub fn markdown_io(self, output_dir: impl Into<PathBuf>) -> Self;
pub fn dtos(self, output_dir: impl Into<PathBuf>) -> Self;
pub fn store<P: Into<PathBuf>>(self, output_dir: impl Into<PathBuf>, hooks_dir: Option<P>) -> Self;
pub fn api(self, output_dir: impl Into<PathBuf>, state_type: impl Into<String>) -> Self;
pub fn api_exclude(self, exclude: Vec<String>) -> Self;
pub fn api_scan_dirs(self, scan_dirs: Vec<PathBuf>) -> Self;
pub fn api_store_type(self, store_type: Option<String>) -> Self;
pub fn servers(self, config: ServersConfig) -> Self;
pub fn servers_scan_dirs(self, scan_dirs: Vec<PathBuf>) -> Self;
pub fn build(self) -> Result<(), CodegenError>;
}

A fluent builder over the generator functions above. Method order on the builder is irrelevant — build() always runs stages in dependency order (schema → seaorm/markdown_io/dtos → store → api → servers) and threads each stage’s typed output into the next.

Sensible defaults are applied automatically:

  • schema_module_path defaults to DEFAULT_SCHEMA_MODULE_PATH ("crate::schema") and propagates to both StoreConfig and ApiConfig.
  • When the store stage is enabled and the API stage hasn’t overridden it, store_type defaults to Some("Store").
  • When the servers stage is enabled, the builder forwards schema.entities into ServersConfig.schema_entities so the admin-registry generator gets the field metadata it needs (without it, admin-registry.ts ships with empty fields: [] per entity).
ontogen::Pipeline::new("src/schema")
.seaorm("src/persistence/db/entities/generated", "src/persistence/db/conversions/generated")
.store("src/store/generated", Some::<std::path::PathBuf>("src/store/hooks".into()))
.api("src/api/v1/generated", "AppState")
.build()
.expect("ontogen pipeline failed");

pub const DEFAULT_SCHEMA_MODULE_PATH: &str = "crate::schema";

Canonical default for the schema_module_path field on StoreConfig and ApiConfig. Use this constant instead of hard-coding "crate::schema" so future changes to the convention propagate to every direct caller. Pipeline callers don’t need to reference it — the builder applies it automatically.


pub fn install_admin_layer(config: &AdminLayerConfig) -> Result<(), CodegenError>

A utility function (not a generator) that patches a Nuxt nuxt.config.ts to include the Ontogen admin layer. Idempotent — safe to call on every build.

Parameters:

ParameterTypeDescription
config&AdminLayerConfigPath to nuxt.config.ts and the relative path to the admin layer package.
install_admin_layer(&AdminLayerConfig {
nuxt_config: "../src-nuxt/nuxt.config.ts".into(),
layer_path: "../crates/ontogen/packages/nuxt_admin_layer".to_string(),
})?;

All generators return Result<T, CodegenError>. In build.rs, you typically handle errors with a helper that emits a cargo:warning before panicking:

fn unwrap_codegen<T>(result: Result<T, CodegenError>, stage: &str) -> T {
result.unwrap_or_else(|e| {
e.emit_cargo_warning();
panic!("{stage}: {e}");
})
}

CodegenError::emit_cargo_warning() prints the error as a cargo:warning=ontogen: line so it appears clearly in your build output rather than buried in a backtrace.