primate 0.1.0

A small DSL for cross-language constants. Write once, generate typed Rust, TypeScript, and Python.
Documentation

primate

A small DSL for cross-language constants. Write your shared values once, generate typed Rust, TypeScript, and Python — with documentation, enums, type aliases, and bounds-checked numeric literals carried through to each target.

constants/                      ┌────────────────────────────┐
└── limits.prim    ─────────►   │  primate build             │
                                └────────────────────────────┘
                                          │
                ┌─────────────────────────┼─────────────────────────┐
                ▼                         ▼                         ▼
   src/generated/constants.rs    web/src/generated/limits.ts   scripts/generated/limits.py

Why

If you ship a backend, a web client, and a script, you've already written MAX_UPLOAD_SIZE = 100 * 1024 * 1024 three times — and at least one of them is wrong. primate makes that one declaration produce one number per target, in the form each language wants:

// constants/limits.prim
//! Application-wide limits.

/// Maximum upload size for a single request.
u64 MAX_UPLOAD_SIZE = 100MiB

/// Time after which an idle session is considered offline.
duration OFFLINE_THRESHOLD = 20min

/// Severity level. Integer-backed for fast filtering.
enum LogLevel: u8 {
    Debug = 0,
    Info  = 1,
    Warn  = 2,
    Error = 3,
}

LogLevel DEFAULT_LEVEL = Info

Generated Rust (pub mod per namespace, std::time::Duration, #[repr(u8)] enums):

pub mod limits {
    pub const MAX_UPLOAD_SIZE: u64 = 104857600;
    pub const OFFLINE_THRESHOLD: std::time::Duration = std::time::Duration::from_nanos(1200000000000);
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    #[repr(i32)]
    pub enum LogLevel {
        Debug = 0,
        Info = 1,
        Warn = 2,
        Error = 3,
    }
    pub const DEFAULT_LEVEL: LogLevel = LogLevel::Info;
}

Generated TypeScript (one .ts per namespace + index.ts, Temporal.Duration available, real ES enums):

// generated/constants/limits.ts
export enum LogLevel {
  Debug = 0,
  Info = 1,
  Warn = 2,
  Error = 3,
}
export const maxUploadSize = 104857600 as const;
export const offlineThreshold = 1200000 as const;  // milliseconds
export const defaultLevel = LogLevel.Info as const;

Generated Python (package directory, IntEnum, timedelta):

# generated/constants/limits.py
from enum import IntEnum
from datetime import timedelta

class LogLevel(IntEnum):
    DEBUG = 0
    INFO = 1
    WARN = 2
    ERROR = 3

MAX_UPLOAD_SIZE: int = 104857600
OFFLINE_THRESHOLD: timedelta = timedelta(seconds=1200)
DEFAULT_LEVEL: LogLevel = LogLevel.INFO

Highlights

  • Unit-suffixed literals. 30s, 100MiB, 5%, 1w — readable in source, normalized in generated code.
  • Bounds-checked. u32 X = 5GiB is a compile-time error.
  • Cross-namespace use. Files in constants/auth/*.prim can use logging::LogLevel; the right import is emitted in each target.
  • One canonical formatter. primate fmt — no flags.
  • Editor support. A real LSP server (primate lsp) ships with the binary: diagnostics, hover, go-to-definition, find-references, format on save, and contextual completion (enum variants, unit suffixes). Editor integrations for Zed and VS Code are in editors/.
  • Plugin protocol. Add a target language by writing any executable that reads JSON on stdin and writes JSON on stdout — see docs/plugins.

Install

cargo install primate --locked

This puts a primate binary in ~/.cargo/bin. Verify:

primate --version

Quick start

mkdir my-app && cd my-app

cat > primate.toml <<'EOF'
input = "constants"

[[output]]
generator = "typescript"
path      = "web/src/generated/constants/"

[[output]]
generator = "rust"
path      = "src/generated/constants.rs"
EOF

mkdir constants
cat > constants/limits.prim <<'EOF'
duration TIMEOUT     = 30s
u32      MAX_RETRIES = 5
u64      MAX_UPLOAD  = 100MiB
EOF

primate build

You'll see one file per target appear under the configured paths.

Project status

v0.1 — usable, but evolving. The language is stable enough to depend on, generators produce idiomatic output for the three built-ins, and the LSP works in real editors. Things still on the roadmap: @deprecated attributes, configurable formatter rules, plugin distribution. See docs/src/roadmap.md.

Documentation

The full book is at docs/, browsable via mdBook:

cd docs && mdbook serve --open

Top-level pages:

License

MIT — see LICENSE.