# Variable
Variable is a type-safe feature flag system. You define features in `.var` files, and Variable generates typed code so you never misspell a flag name, pass the wrong type, or forget a default.
```
Feature Checkout {
Variable enabled Boolean = true
Variable max_items Number = 50
Variable header_text String = "Complete your purchase"
}
```
Run one command:
```sh
variable generate --out ./src/generated features.var
```
Get type-safe TypeScript:
```typescript
import { VariableClient } from "@variable/runtime";
import { getCheckoutVariables } from "./generated/features.generated";
const client = new VariableClient();
const checkout = getCheckoutVariables(client);
checkout.enabled // boolean
checkout.max_items // number
checkout.header_text // string
```
No string lookups. No `as any`. Full autocompletion.
## Getting Started
### Prerequisites
- [Rust](https://rustup.rs/) (1.85+)
- [Node.js](https://nodejs.org/) (for TypeScript projects)
### Build from source
```sh
git clone <repo-url> && cd variable
cargo build --release
```
The binary is at `target/release/variable`.
### Quick start
**1. Write a `.var` file**
```
Feature Search {
Variable enabled Boolean = false
Variable max_results Number = 25
Variable placeholder String = "Search..."
}
```
**2. Generate TypeScript**
```sh
variable generate --out ./src/generated search.var
```
This creates `src/generated/search.generated.ts` containing:
- A `SearchVariables` interface with typed fields
- A `getSearchVariables()` function that returns values with defaults baked in
**3. Use in your code**
```typescript
import { VariableClient } from "@variable/runtime";
import { getSearchVariables } from "./generated/search.generated";
const client = new VariableClient();
const search = getSearchVariables(client);
console.log(search.max_results); // 25
```
The `VariableClient` accepts a `Provider` that can supply overrides (from a remote service, local config, etc.). With no provider, you get the defaults from your `.var` file.
## The `.var` DSL
A `.var` file contains one or more `Feature` blocks. Each feature has typed variables with mandatory defaults.
```
Feature Checkout {
Variable enabled Boolean = true
Variable max_items Number = 50
Variable header_text String = "Complete your purchase"
}
Feature Search {
Variable fuzzy_matching Boolean = false
Variable max_results Number = 25
Variable placeholder String = "Search..."
}
```
### Supported types
| `Boolean` | `boolean` | `true`, `false` |
| `Number` | `number` | `42`, `3.14` |
| `String` | `string` | `"hello"`, `"line\nnewline"` |
### Rules
- Feature names must be unique across the file
- Variable names must be unique within a feature
- Every variable must have a default value
- String literals support escape sequences: `\n`, `\t`, `\\`, `\"`
## What gets generated
For a feature named `Checkout`, Variable generates:
```typescript
// This file is generated by Variable. Do not edit.
import { VariableClient } from "@variable/runtime";
export interface CheckoutVariables {
enabled: boolean;
max_items: number;
header_text: string;
}
const checkoutDefaults: CheckoutVariables = {
enabled: true,
max_items: 50,
header_text: "Complete your purchase",
};
export function getCheckoutVariables(client: VariableClient): CheckoutVariables {
const overrides = client.getFeatureValues("Checkout");
return {
...checkoutDefaults,
...overrides,
} as CheckoutVariables;
}
```
Defaults are baked in. Overrides from a provider are spread on top. You always get a complete, typed object.
## Runtime
The `@variable/runtime` package (in `runtime/ts/`) provides the types that generated code imports:
```typescript
import { VariableClient, NoopProvider } from "@variable/runtime";
// Default: no overrides, just use .var defaults
const client = new VariableClient();
// Or bring your own provider
const client = new VariableClient(myProvider);
```
A `Provider` implements one method:
```typescript
interface Provider {
getManifest(): Record<string, Record<string, unknown>>;
}
```
The `NoopProvider` returns an empty manifest, so all values fall back to the defaults in generated code.
## Examples
Working examples are in [`examples/`](examples/). Each is a self-contained project you can run.
### [Basic](examples/ts/basic/)
Generates code from a `.var` file and uses it at runtime:
```sh
cd examples/ts/basic
npm install
npm start
```
```
=== Checkout Feature ===
enabled: true
max_items: 50
header_text: Complete your purchase
=== Search Feature ===
enabled: false
max_results: 10
placeholder: Search...
All assertions passed!
```
## Project Structure
```
variable-core/ Lexer, parser, AST, validation
variable-codegen/ TypeScript code generation
variable-cli/ CLI binary (the `variable` command)
runtime/ts/ TypeScript runtime (@variable/runtime)
examples/ts/ TypeScript usage examples
fixtures/ Sample .var files for testing
```
## Development
```sh
# Run all tests (39 total)
cargo test
# Build the CLI
cargo build --release
# Update codegen snapshots after changing output format
cargo insta review
```
## License
MIT