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 enums — BelongsTo, HasMany, and Related implementations for cross-entity navigation.
  • Junction tables — full SeaORM entities for many-to-many relationships.
  • Conversion functions — from_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<MarkdownIoOutput, 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 the sibling of gen_seaorm: both consume SchemaOutput independently, and both return metadata that gen_store consumes through StoreConfig::backend. Use it when your entities live in Markdown files — wikis, knowledge bases, planning vaults — rather than (or in addition to) a database.

Parameters:

ParameterTypeDescription
entities&[EntityDef]Entity definitions from parse_schema.
config&MarkdownIoConfigOutput directory plus the runtime vault shape (root, layout, id strategy, list cap).

Returns: MarkdownIoOutput — the vault layout, id strategy, and per-entity directory mapping. Pass it into Backend::Markdown to wire the generated store at the markdown vault runtime; the markdown emitter genuinely needs this metadata, so unlike SeaOrmOutput it isn’t optional.

let md = gen_markdown_io(&schema.entities, &MarkdownIoConfig {
output_dir: "src/persistence/markdown/generated".into(),
vault_root: "data/vault".into(),
layout: ontogen::MarkdownLayout::PerEntityDir,
id_strategy: ontogen::IdStrategy::SlugFromField("title".into()),
list_cap: 10_000,
})?;

See the markdown backend guide for the full picture.


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],
config: &StoreConfig,
) -> Result<StoreOutput, CodegenError>

Generates the CRUD store layer:

  • CRUD methods — list_*(), 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 population — populate_*_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.
config&StoreConfigOutput directory, optional hooks directory, and the Backend the CRUD bodies are wired to.

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

// SeaORM-backed
let store = gen_store(&schema.entities, &StoreConfig {
output_dir: "src/store/generated".into(),
hooks_dir: Some("src/store/hooks".into()),
schema_module_path: ontogen::DEFAULT_SCHEMA_MODULE_PATH.into(),
backend: ontogen::Backend::Seaorm(Some(seaorm)),
wikilink_policy: None,
})?;
// Markdown-backed -- same call, one field different
let store = gen_store(&schema.entities, &StoreConfig {
output_dir: "src/store/generated".into(),
hooks_dir: Some("src/store/hooks".into()),
schema_module_path: ontogen::DEFAULT_SCHEMA_MODULE_PATH.into(),
backend: ontogen::Backend::Markdown(md),
wikilink_policy: None,
})?;

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
extra_surfaces: vec![],
})?;

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, plus ts_formatter, ts_skip_commands, schema_entities, pool_extra_roots, pool_exclude_paths. See the ClientsConfig reference for every field. The Pipeline builder forwards schema.entities automatically.
gen_clients(Some(&api), &[], &ClientsConfig {
api_dir: "src/api/v1".into(),
state_type: "AppState".into(),
service_import_path: "crate::api::v1".into(),
types_import_path: "crate::schema".into(),
state_import: "crate::AppState".into(),
naming: NamingConfig::default(),
generators: vec![
ClientGenerator::HttpTauriIpcSplit {
output: "../src-nuxt/app/generated/transport.ts".into(),
bindings_path: "../src-nuxt/app/generated/types.ts".into(),
},
ClientGenerator::AdminRegistry {
output: "../src-nuxt/app/admin/generated/admin-registry.ts".into(),
},
],
ts_formatter: ontogen::TsFormatter::None,
sse_route_overrides: Default::default(),
ts_skip_commands: vec![],
route_prefix: None,
store_type: Some("Store".into()),
store_import: Some("crate::store::Store".into()),
pagination: None,
schema_entities: schema.entities.clone(),
pool_extra_roots: vec![],
pool_exclude_paths: vec!["src/persistence/db/entities/generated".into()],
// ... other config fields
extra_surfaces: vec![],
})?;

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>, options: MarkdownIoOptions) -> Self;
pub fn store_backend(self, choice: StoreBackendChoice) -> 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).
  • ClientsConfig.pool_exclude_paths is populated from the seaorm() stage’s entity_output, so the per-entity Relation enums SeaORM emits can’t make the long-tail resolver ambiguous.
  • The store backend is inferred when exactly one persistence stage is configured. Enable both seaorm(...) and markdown_io(...) and you must disambiguate with .store_backend(StoreBackendChoice::Seaorm) or .store_backend(StoreBackendChoice::Markdown).
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");

The same pipeline on the markdown backend — seaorm() swaps for markdown_io(), nothing else changes:

use ontogen::{IdStrategy, MarkdownIoOptions, MarkdownLayout};
ontogen::Pipeline::new("src/schema")
.markdown_io(
"src/persistence/markdown/generated",
MarkdownIoOptions {
vault_root: "data/vault".into(),
layout: MarkdownLayout::PerEntityDir,
id_strategy: IdStrategy::SlugFromField("title".into()),
list_cap: 10_000,
},
)
.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.