# nautilus-codegen
Code generator that reads a validated `.nautilus` schema IR and emits ready-to-use client code for Rust and Python.
---
## Purpose
`nautilus-codegen` takes a `SchemaIR` produced by `nautilus-schema` and writes:
- **Rust** — a full crate with typed model structs, enum definitions, and CRUD builders (`Create`, `Find`, `FindMany`, `Update`, `Delete`, `CreateMany`) that call into `nautilus-core` and `nautilus-connector`.
- **Python** — a package with Pydantic-compatible models, async client code, and a bundled runtime (`_engine.py`, `_client.py`, `_protocol.py`, `_errors.py`) that communicates with the `nautilus-engine` binary over stdio.
---
## Public API
| `resolve_schema_path(schema)` | `fn` | Auto-detect the first `.nautilus` file in the CWD, or return the explicit path. |
| `parse_schema(source)` | `fn` | Lex + parse raw schema source text into an `Ast`. |
| `generate_command(path, opts)` | `fn` | Full pipeline: read → parse → validate → generate → write. |
| `validate_command(path)` | `fn` | Read, parse, and validate only — no code is written. |
| `GenerateOptions` | `struct` | Options for `generate_command`; see fields below. |
### `GenerateOptions`
```rust
pub struct GenerateOptions {
/// Copy the output into site-packages (Python) or register in the
/// workspace Cargo.toml (Rust) after generation.
pub install: bool,
/// Print verbose progress and IR debug output to stdout.
pub verbose: bool,
/// (Rust only) Also emit a `Cargo.toml` for the generated crate.
/// When `false` (default) bare source files are written, suitable for
/// inclusion in an existing Cargo workspace.
pub standalone: bool,
}
```
---
## Usage within the project
Two binaries use this crate as a library:
| `nautilus-cli` | `crates/nautilus-cli/src/commands/generate.rs` — the unified `nautilus` CLI |
| `nautilus-codegen` (binary) | `crates/nautilus-codegen/src/main.rs` — the standalone code-gen binary |
Both construct a `GenerateOptions` value from their CLI flags and call
`nautilus_codegen::generate_command(&path, options)`.
Downstream crates that consume the *generated* output (not this crate directly):
- `nautilus-connector` — the generated Rust builders call its `NautilusClient` trait.
- `nautilus-core` — the generated builders construct `Select`, `Update`, `Delete`, and `Insert` values defined here.
---
## Architecture
### Template registries
Code generation is driven by two independent [Tera](https://keats.github.io/tera/) template registries, each initialised once via `std::sync::LazyLock`:
| `generator::TEMPLATES` | `src/generator.rs` | Everything under `templates/rust/` |
| `python::generator::PYTHON_TEMPLATES` | `src/python/generator.rs` | Everything under `templates/python/` |
Both registries expose a private `render(template_name, ctx)` helper that terminates the process on a Tera error rather than propagating `Result` through every call site — generation errors at this stage always indicate a bug in the template, not a user error.
### `is_optional` in `FieldContext`
When building the context for `update.tera` and related templates, each field carries an `is_optional: bool` flag. A field is optional when it is neither required nor an array — i.e., it maps to `Option<T>` in the generated `UpdateInput` struct. The `update.tera` template uses this flag to emit the correct match arm for the outer `Option` (skip vs. set `NULL` vs. set a concrete value).
### `create_many` — batch INSERT
The `create_many.tera` template generates a single multi-row `INSERT INTO … VALUES (…), (…) RETURNING *` statement for dialects that support `RETURNING` (Postgres, SQLite). For MySQL, which lacks `RETURNING`, it falls back to a per-row loop through the single-item `Create` builder.
### Python runtime — async subprocess I/O
The Python runtime files (`_engine.py`, `_client.py`) use `asyncio.create_subprocess_exec` and the resulting `asyncio.subprocess.Process` streams (`stdin`, `stdout`, `stderr`) throughout. There is no `subprocess.Popen`, no `loop.run_in_executor`, and no `get_event_loop()` call. This ensures full compatibility with running event loops (e.g., inside a Jupyter notebook or `asyncio.run`), where `asyncio.get_event_loop()` is deprecated or raises `DeprecationWarning`.
### Static runtime files
Two Python runtime files contain no Tera variables. Their generators (`generate_errors_init`, `generate_internal_init`) return `&'static str` via `include_str!` rather than rendering them through the Tera engine, avoiding the overhead of a template parse + render cycle for files that never change.
### `Cargo.toml.tpl`
`templates/rust/Cargo.toml.tpl` is processed by simple `str::replace` in `writer.rs`, not by Tera. The `.tpl` extension reflects this — it is not a Tera template and is therefore never registered in `TEMPLATES`.