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.

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. Client-side TypeScript and admin-registry generation lives in the sibling gen_clients entry point.

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&ServersConfigServer transport configuration — which server 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(),
},
],
// ... other config fields
})?;

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

Sibling of gen_servers for the client-side surface. Reads the same ApiOutput and emits:

  • TypeScript bindings — schema-known entity / DTO aliases plus the ontogen-ts long-tail closure, written to every distinct bindings_path declared by the configured generators.
  • TypeScript HTTP-only client (HttpTs) — one httpEntity* function per API operation against fetch.
  • TypeScript HTTP+IPC unified transport (HttpTauriIpcSplit) — a Transport interface with createHttpTransport() / createIpcTransport() implementations.
  • Admin-UI registry (AdminRegistry) — per-entity metadata (label, plural, field types, relation hints) for a generic admin layer.

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&ClientsConfigClient configuration — which client generators to run, ts_skip_commands, schema_entities, pool_extra_roots, etc. The Pipeline builder forwards schema.entities automatically.
gen_clients(Some(&api), &[], &ClientsConfig {
api_dir: "src/api/v1".into(),
state_type: "AppState".into(),
// ... shared with ServersConfig ...
generators: vec![
ClientGenerator::HttpTauriIpcSplit {
output: "../src-nuxt/app/transport/generated.ts".into(),
bindings_path: "../src-nuxt/app/types/bindings.ts".into(),
},
ClientGenerator::AdminRegistry {
output: "../src-nuxt/layers/admin/generated/admin-registry.ts".into(),
},
],
ts_skip_commands: vec![],
schema_entities: schema.entities.clone(),
pool_extra_roots: vec![],
// ... 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 clients(self, config: ClientsConfig) -> Self;
pub fn clients_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 → clients) 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 clients stage is enabled, the builder forwards schema.entities into ClientsConfig.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.