Getting started
Zero to generated output in about five minutes. The example below uses
TypeScript; the same .prim source produces Rust and Python output by
adding another [[output]] block.
Install
cargo install primate --locked
That puts a primate binary at ~/.cargo/bin. Verify:
primate --version
Project layout
primate expects a primate.toml at the project root that points at a
directory of .prim files and lists the targets to generate.
my-app/
├── primate.toml
├── constants/
│ └── limits.prim
└── (generated output, paths defined in primate.toml)
A minimal config
# primate.toml
input = "constants"
[[output]]
generator = "typescript"
path = "web/src/generated/constants/"
Each [[output]] entry enables one target. The built-ins are rust,
typescript, and python. External plugins plug in here too — see
Writing a generator.
path is a directory for typescript and python (one file per
namespace); a single .rs file for rust (one pub mod per
namespace). See primate build for the rationale.
A first .prim file
// constants/limits.prim
/// How long the app waits before giving up on a slow request.
duration TIMEOUT = 30s
u32 MAX_RETRIES = 5
u64 MAX_UPLOAD = 100MiB
enum LogLevel: u8 {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
}
A few things to notice:
- Type-first. The type comes before the name —
duration TIMEOUT, notTIMEOUT: duration. There’s no inference; every constant is explicitly typed. - Suffixed literals.
30sis a duration,100MiBis a byte size. primate normalizes durations to nanoseconds and applies byte-size suffix multipliers at lex time, then bounds-checks the result against the declared type. - Doc comments.
///attaches to the next declaration and shows up in generated output where the target language supports docs. - One declaration per line. Newlines terminate; no semicolons.
- No
namespaceline. The namespace defaults to the file’s path relative toinput.constants/limits.primis in namespacelimits. For nested folders, dirs become::-separated segments (e.g.constants/net/limits.prim→net::limits). You only neednamespace foo::baras an explicit override; see Declarations for when that’s useful.
Generate
primate build
primate reads primate.toml, parses every .prim file under input,
and writes the generated files. You’ll see something like:
Generated: web/src/generated/constants/limits.ts
Generated: web/src/generated/constants/index.ts
What got generated
// web/src/generated/constants/limits.ts
// Generated by primate. Do not edit.
/** Severity, integer-backed for fast filtering. */
export enum LogLevel {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
}
/** How long the app waits before giving up on a slow request. */
export const timeout = 30_000 as const; // milliseconds
export const maxRetries = 5 as const;
export const maxUpload = 104_857_600 as const;
…and an index.ts re-exporting each namespace, so consumers can write
import { limits } from "./generated/constants".
You can also tune each generator through primate.toml — e.g.
options.duration = "temporal" on the TypeScript output emits
Temporal.Duration values instead of milliseconds. See
primate build for everything tunable.
Editor setup
primate ships an LSP server (primate lsp). The dev experience is
significantly better with it on:
- Diagnostics live in your buffer (parse errors, unknown types, length-mismatch on fixed arrays, …).
- Hover docs on type names, including types in other namespaces.
- Go-to-definition for enums and aliases, including across files.
- Find references across the workspace, following
useimports. - Format on save.
Setup per editor:
- VS Code — install from the Marketplace.
- Zed — install from the Zed extensions registry.
- Vim — drop the syntax/ftdetect files into your runtime path.
Next steps
- Language overview — the full set of declarations, types, and value literals.
- Cookbook — common shapes (matrices, lookup tables, platform-specific overrides).
- Plugins — write a generator for a language the built-ins don’t cover.