# vague-lang

> A declarative language for generating realistic test data

Latest version **3.4.3** (published 2026-08-11) · MIT license · 0 weekly downloads

## Install

```sh
npm install vague-lang
pnpm add vague-lang
yarn add vague-lang
bun add vague-lang
```

Provides the command `vague`.

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 3.4.3 |
| Published | 2026-08-11 |
| First published | 2025-12-16 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Node | >=20 |
| Dependencies | 8 |
| Unpacked size | 1.4 MB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 1 |
| Author | Max Clayton Clowes |
| Maintainers | mcclowes |
| Keywords | test-data, mock-data, fixture, faker, generator, dsl, openapi, json-schema, testing |

## Links

- npm: https://www.npmjs.com/package/vague-lang
- Repository: https://github.com/mcclowes/vague
- Homepage: https://github.com/mcclowes/vague#readme
- Issues: https://github.com/mcclowes/vague/issues
- Funding: https://github.com/sponsors/mcclowes
- npm.io page: https://npm.io/package/vague-lang

## Dependencies (8)

- [ajv](https://npm.io/package/ajv.md) ^8.17.1
- [randexp](https://npm.io/package/randexp.md) ^0.5.3
- [ajv-formats](https://npm.io/package/ajv-formats.md) ^3.0.1
- [openapi-types](https://npm.io/package/openapi-types.md) ^12.1.3
- [@faker-js/faker](https://npm.io/package/@faker-js/faker.md) ^10.1.0
- [@stoplight/spectral-core](https://npm.io/package/@stoplight/spectral-core.md) ^1.20.0
- [@stoplight/spectral-rulesets](https://npm.io/package/@stoplight/spectral-rulesets.md) ^1.22.0
- [@apidevtools/json-schema-ref-parser](https://npm.io/package/@apidevtools/json-schema-ref-parser.md) ^15.5.1

## Alternatives

- [pagerjs](https://npm.io/package/pagerjs.md) — 60 weekly downloads
- [whistle.savefor-mock](https://npm.io/package/whistle.savefor-mock.md) — 4 weekly downloads
- [sinon-typed-stub](https://npm.io/package/sinon-typed-stub.md) — 0 weekly downloads
- [named-patch](https://npm.io/package/named-patch.md) — 0 weekly downloads
- [@anil-labs/factory](https://npm.io/package/@anil-labs/factory.md) — 0 weekly downloads

## Recent versions

- 3.4.3 (latest) — 2026-08-11
- 3.4.2 — 2026-08-11
- 3.3.0 — 2025-12-21
- 3.2.0 — 2025-12-18
- 3.1.0 — 2025-12-17
- 3.0.0 — 2025-12-17
- 2.0.1 — 2025-12-16

## README

![Abstract representation of data and Vague](./banner.png)

# Vague

A declarative language for describing and generating realistic data. Vague treats ambiguity as a first-class primitive — declare the shape of valid data and let the runtime figure out how to populate it.

## Why Vague?

**Vague is a data description model for APIs, not just a fake data tool.**

Think of it as OpenAPI meets property-based testing: you describe *what valid data looks like* — its structure, constraints, distributions, and edge cases — and Vague handles generation. The same schema that generates test data can validate production data.

| What You Need | Traditional Tools | Vague |
|---------------|-------------------|-------|
| **Intent** — "80% of users are active" | Random selection | `status: 0.8: "active" \| 0.2: "inactive"` |
| **Constraints** — "due date ≥ issued date" | Manual validation | `assume due_date >= issued_date` |
| **Relationships** — "payment references an invoice" | Manual wiring | `invoice: any of invoices where .status == "open"` |
| **Edge cases** — "test with Unicode exploits" | Manual creation | `name: issuer.homoglyph("admin")` |
| **Validation** — "does this data match the schema?" | Separate tool | Same `.vague` file with `--validate-data` |

The question isn't "which fake data library?" — it's "how do we formally describe what valid data looks like for our APIs?"

For a detailed comparison, see [COMPARISON.md](COMPARISON.md).

## Installation

```bash
npm install vague-lang
```

Or install globally for CLI usage:

```bash
npm install -g vague-lang
```

[![npm version](https://img.shields.io/npm/v/vague-lang.svg)](https://www.npmjs.com/package/vague-lang)

## Quick Start

Create a `.vague` file:

```vague
schema Customer {
  name: string,
  status: 0.8: "active" | 0.2: "inactive"
}

schema Invoice {
  customer: any of customers,
  amount: decimal in 100..10000,
  status: "draft" | "sent" | "paid",

  assume amount > 0
}

dataset TestData {
  customers: 50 of Customer,
  invoices: 200 of Invoice
}
```

Generate JSON:

```bash
npx vague your-file.vague       # Local install
vague your-file.vague           # Global install
```

(Working from a clone of this repo? Use `node dist/cli.js` instead of `vague` after `npm run build`.)

## Syntax Cheat Sheet

For a quick reference of all syntax, see **[SYNTAX.md](SYNTAX.md)**.

## Library Usage

Vague also works as a TypeScript/JavaScript library:

```typescript
import { fromFile, vague } from 'vague-lang';

// File-based (recommended)
const data = await fromFile('./fixtures.vague', { seed: 42 });

// Tagged template
const data = await vague`
  schema Person { name: string, age: int in 18..65 }
  dataset Test { people: 10 of Person }
`;
```

See [CLAUDE.md](CLAUDE.md) for the full programmatic API, including validation, reporting, dataset comparison, and schema diff.

## Language Features

### Superposition (Random Choice)

```vague
// Equal probability
status: "draft" | "sent" | "paid"

// Weighted probability
status: 0.6: "paid" | 0.3: "pending" | 0.1: "draft"

// Mixed: unweighted options share remaining probability
status: 0.85: "Active" | "Archived"         // "Archived" gets 15%
category: 0.6: "main" | "side" | "dessert"  // "side" and "dessert" get 20% each
```

### Ranges

```vague
age: int in 18..65
price: decimal in 0.01..999.99
founded: date in 2000..2023

// Decimal with explicit precision
score: decimal(1) in 0..10       // 1 decimal place
amount: decimal(2) in 10..100    // 2 decimal places
```

### Collections

```vague
line_items: 1..5 of LineItem    // 1-5 items
employees: 100 of Employee       // Exactly 100
```

### Constraints

```vague
schema Invoice {
  issued_date: int in 1..28,
  due_date: int in 1..90,
  status: "draft" | "paid",
  amount: int in 0..10000,

  // Hard constraint
  assume due_date >= issued_date,

  // Conditional constraint
  assume if status == "paid" {
    amount == 0
  }
}
```

Logical operators: `and`, `or`, `not`

### Cross-Record References

```vague
schema Invoice {
  // Reference any customer from the collection
  customer: any of customers,

  // Filtered reference
  active_customer: any of customers where .status == "active"
}
```

### Parent References

```vague
schema LineItem {
  // Inherit currency from parent invoice
  currency: ^base_currency
}

schema Invoice {
  base_currency: "USD" | "GBP" | "EUR",
  line_items: 1..5 of LineItem
}
```

### Computed Fields

```vague
schema Invoice {
  line_items: 1..10 of LineItem,

  total: sum(line_items.amount),
  item_count: count(line_items),
  avg_price: avg(line_items.unit_price),
  min_price: min(line_items.unit_price),
  max_price: max(line_items.unit_price),
  median_price: median(line_items.unit_price),
  first_item: first(line_items.unit_price),
  last_item: last(line_items.unit_price),
  price_product: product(line_items.unit_price)
}
```

### Nullable Fields

```vague
nickname: string?           // Shorthand: sometimes null
notes: string | null        // Explicit
```

### Ternary Expressions

```vague
status: amount_paid >= total ? "paid" : "pending"
grade: score >= 90 ? "A" : score >= 70 ? "B" : "C"
```

### Match Expressions

```vague
// Pattern matching for multi-way branching
display: match status {
  "pending" => "Awaiting shipment",
  "shipped" => "On the way",
  "delivered" => "Complete"
}

// Returns null if no pattern matches
```

### Conditional Fields

```vague
schema Account {
  type: "personal" | "business",
  companyNumber: string when type == "business"  // Only exists for business accounts
}
```

### Dynamic Cardinality

```vague
schema Order {
  size: "small" | "large",
  items: (size == "large" ? 5..10 : 1..3) of LineItem
}
```

### Side Effects (`then` blocks)

```vague
schema Payment {
  invoice: any of invoices,
  amount: int in 10..500
} then {
  invoice.amount_paid += amount,
  invoice.status = invoice.amount_paid >= invoice.total ? "paid" : "partial"
}
```

### Unique Values

```vague
id: unique int in 1000..9999    // No duplicates in collection
```

### Private Fields

```vague
schema Person {
  age: private int in 0..105,                    // Generated but excluded from output
  age_bracket: age < 18 ? "minor" : "adult"    // Computed from private field
}
// Output: { "age_bracket": "adult" } -- no "age" field
```

### Ordered Sequences

```vague
pitch: [48, 52, 55, 60]   // Cycles in order: 48, 52, 55, 60, 48...
color: ["red", "green", "blue"]
```

### Statistical Distributions

```vague
age: gaussian(35, 10, 18, 65)     // mean, stddev, min, max
income: lognormal(10.5, 0.5)      // mu, sigma
wait_time: exponential(0.5)       // rate
daily_orders: poisson(5)          // lambda
conversion: beta(2, 5)            // alpha, beta
```

### Date Functions

```vague
created_at: now()                 // Full ISO 8601 timestamp
today_date: today()               // Date only
past: daysAgo(30)                 // 30 days ago
future: daysFromNow(90)           // 90 days from now
random: datetime(2020, 2024)      // Random datetime in range
between: dateBetween("2023-01-01", "2023-12-31")
```

### Sequential Generation

```vague
id: sequence("INV-", 1001)        // "INV-1001", "INV-1002", ...
order_num: sequenceInt("orders")  // 1, 2, 3, ...
prev_value: previous("amount")    // Reference previous record
```

### String Transformations

```vague
// Case transformations
upper: uppercase(name)             // "HELLO WORLD"
lower: lowercase(name)             // "hello world"
capitalized: capitalize(name)      // "Hello World"

// Case style conversions
slug: kebabCase(title)             // "hello-world"
snake: snakeCase(title)            // "hello_world"
camel: camelCase(title)            // "helloWorld"

// String manipulation
trimmed: trim("  hello  ")         // "hello"
combined: concat(first, " ", last) // "John Doe"
part: substring(name, 0, 5)        // First 5 characters
replaced: replace(name, "foo", "bar")
len: length(name)                  // String length
```

### Negative Testing

```vague
// Generate data that violates constraints (for testing error handling)
dataset Invalid violating {
  bad_invoices: 100 of Invoice
}
```

### Contracts and Invariants

```vague
// Invariants always hold, even in violating mode (unlike assume)
contract PositiveAmount {
  invariant amount > 0 "Amount must be positive"
}

schema Invoice implements PositiveAmount {
  amount: decimal in 1..1000,
  invariant amount <= 1000
}
```

See [SYNTAX.md](SYNTAX.md#contracts-and-invariants) for details, including golden dataset comparison (`compareDatasets`) and schema diff (`diffSchemas`) APIs.

## Built-in Plugins

Vague includes several plugins for generating realistic domain-specific data. For complete documentation, see [SYNTAX.md](SYNTAX.md#generators-semantic-data).

| Plugin | Description | Example |
|--------|-------------|---------|
| **faker** | Realistic personal/business data | `email()`, `fullName()`, `companyName()` |
| **issuer** | Edge case testing values | `issuer.homoglyph("admin")`, `issuer.maxInt()` |
| **regex** | Pattern-based generation | `regex("[A-Z]{3}-[0-9]{4}")`, `semver()` |
| **date** | Day-of-week filtering | `date.weekday(2024, 2025)` |
| **http** | HTTP testing data | `http.method()`, `http.statusCode()`, `env("API_KEY")` |
| **sql** | SQL test data | `sql.tableName()`, `sql.connectionString("postgres")` |
| **graphql** | GraphQL test data | `graphql.query()`, `graphql.error()` |

## Examples

The `examples/` directory contains organized examples for learning and reference:

**Getting Started:**
- `data-description-model/` - **Start here:** Intent encoding, constraint encoding, edge-case bias
- `basics/` - Core language features (schemas, constraints, computed fields, cross-refs)

**OpenAPI Integration:**
- `openapi-importing/` - Import schemas from OpenAPI specs
- `openapi-examples-generation/` - Populate OpenAPI specs with generated examples

**Real-World API Examples:**
- `stripe/` - Payment processing (invoices, charges, subscriptions)
- `github/` - GitHub API patterns (repos, issues, PRs)
- `slack/` - Slack webhook payloads
- `shopify/` - E-commerce data
- `codat/` - Accounting/fintech APIs
- `twilio/` - Communications platform
- `graphql/` - GraphQL-specific patterns

**Schema Inference:**
- `codegen-inference/` - Generating Vague schemas from JSON data
- `wine-inference/` - Inferring a schema from a real dataset

**Advanced Topics:**
- `http-testing/` - HTTP request/response patterns
- `custom-plugins/` - Creating custom plugins
- `vitest-fixtures/` - Test data for Vitest
- `multiple-schemas/` - Composing several schemas
- `graphs/` - Graph-shaped data
- `fpl/` - Fantasy football data
- `music/` - Generative music data
- `contracts.vague`, `match-expressions.vague` - Single-file feature demos

## CLI Usage

```bash
# Generate JSON to stdout
node dist/cli.js file.vague

# Save to file
node dist/cli.js file.vague -o output.json

# Pretty print
node dist/cli.js file.vague -p

# Reproducible output (seeded random)
node dist/cli.js file.vague --seed 123

# Watch mode - regenerate on file change
node dist/cli.js file.vague -o output.json -w

# CSV output
node dist/cli.js file.vague -f csv -o output.csv

# CSV with options
node dist/cli.js file.vague -f csv --csv-delimiter ";" -o output.csv

# Validate against OpenAPI spec
node dist/cli.js file.vague -v openapi.json -m '{"invoices": "Invoice"}'

# Validate only (exit code 1 on failure, useful for CI)
node dist/cli.js file.vague -v openapi.json -m '{"invoices": "Invoice"}' --validate-only
```

### OpenAPI Example Population

Generate realistic examples and embed them directly in your OpenAPI spec:

```bash
# Populate OpenAPI spec with inline examples
node dist/cli.js data.vague --oas-output api-with-examples.json --oas-source api.json

# Multiple examples per schema
node dist/cli.js data.vague --oas-output api.json --oas-source api.json --oas-example-count 3

# External file references instead of inline
node dist/cli.js data.vague --oas-output api.json --oas-source api.json --oas-external
```

Auto-detection maps collection names to schema names (e.g., `invoices` → `Invoice`).

### Mock server

Serve generated data over HTTP instead of writing files:

```bash
node dist/cli.js file.vague --serve          # http://localhost:3000
node dist/cli.js file.vague --serve 8080     # Custom port
node dist/cli.js file.vague --serve --seed 42
```

Each collection in the dataset becomes an endpoint (`GET /invoices`, `GET /invoices/:index`).

### Enterprise reporting

Generate audit trails and generation reports for compliance:

```bash
node dist/cli.js file.vague -o data.json --report report.html   # Also .md, .json
node dist/cli.js file.vague --audit-log audit.jsonl             # Append JSONL audit entry
node dist/cli.js file.vague --report new.json --baseline old.json  # Distribution drift
```

### CLI Options

| Option | Description |
|--------|-------------|
| `-o, --output <file>` | Write output to file |
| `-f, --format <fmt>` | Output format: `json` (default), `csv`, `ndjson` |
| `-p, --pretty` | Pretty-print JSON |
| `-s, --seed <number>` | Seed for reproducible generation |
| `-w, --watch` | Watch input file and regenerate on changes (requires `-o`) |
| `-v, --validate <spec>` | Validate against OpenAPI spec |
| `-m, --mapping <json>` | Schema mapping `{"collection": "SchemaName"}` |
| `--validate-only` | Only validate, don't output data |
| `--validate-data <file>` | Validate external JSON data against Vague schema (requires `--schema`) |
| `--schema <file>` | Schema file for data validation |
| `--dataset <name>` | Dataset name for `validate {}` block constraints |
| `--csv-delimiter <char>` | CSV field delimiter (default: `,`) |
| `--csv-no-header` | Omit CSV header row |
| `--csv-arrays <mode>` | Array handling: `json` (default), `first`, `count` |
| `--csv-nested <mode>` | Nested objects: `flatten` (default), `json` |
| `--infer <file>` | Infer Vague schema from JSON or CSV data |
| `--collection-name <name>` | Collection name for CSV inference |
| `--infer-delimiter <char>` | CSV delimiter for inference (default: `,`) |
| `--dataset-name <name>` | Dataset name for inference |
| `--no-formats` | Disable format detection during inference (uuid, email, etc.) |
| `--no-weights` | Disable weighted superpositions during inference |
| `--max-enum <n>` | Max unique values for enum detection (default: 10) |
| `--typescript` | Generate TypeScript definitions (inference mode only) |
| `--ts-only` | Generate only TypeScript definitions, no .vague (inference mode only) |
| `--oas-source <spec>` | Source OpenAPI spec to populate with examples |
| `--oas-output <file>` | Output path for populated OpenAPI spec |
| `--oas-example-count <n>` | Number of examples per schema (default: 1) |
| `--oas-external` | Use external file references instead of inline |
| `--lint-spec <file>` | Lint OpenAPI spec with Spectral |
| `--lint-verbose` | Show detailed lint results (includes hints) |
| `--serve [port]` | Start HTTP mock server (default: 3000) |
| `--report <file>` | Generate enterprise report (`.html`/`.md` by extension, otherwise JSON) |
| `--report-format <fmt>` | Report format override: `json`, `html`, `markdown` |
| `--audit-log <file>` | Append audit log entry to JSONL file |
| `--baseline <file>` | Compare against baseline report for distribution drift (requires `--report`) |
| `-c, --config <file>` | Use specific config file (default: auto-detect `vague.config.js`) |
| `--no-config` | Skip loading config file |
| `--plugins <dir>` | Load plugins from directory (can be used multiple times) |
| `--no-auto-plugins` | Disable automatic plugin discovery |
| `-d, --debug` | Enable debug logging |
| `--log-level <level>` | Set log level: `none`, `error`, `warn`, `info`, `debug` |
| `--verbose` | Show verbose output (e.g., discovered plugins) |
| `-h, --help` | Show help |

## Configuration File

Create a `vague.config.js` in your project root for persistent settings:

```javascript
// vague.config.js
export default {
  seed: 42,              // Reproducible output
  format: 'json',        // 'json', 'csv', or 'ndjson'
  pretty: true,          // Pretty-print JSON
  plugins: [
    './my-plugin.js',    // Local plugin
    'vague-plugin-foo',  // npm package
  ],
  logging: {
    level: 'info',       // 'none', 'error', 'warn', 'info', 'debug'
    components: ['generator', 'constraint'],
  },
};
```

Config files are auto-discovered by searching up from the current directory.

## Troubleshooting

### Constraint failures (100 retries exceeded)

If generation fails with "Maximum constraint retries exceeded":

1. **Check constraint compatibility**: Ensure your constraints don't conflict
   ```vague
   // BAD: Impossible constraint
   value: int in 1..10,
   assume value > 100

   // GOOD: Compatible constraint
   value: int in 1..100,
   assume value > 50
   ```

2. **Widen ranges**: If constraints are too tight, generation may fail frequently
   ```vague
   // BAD: Very narrow valid range
   age: int in 0..100,
   assume age >= 18 and age <= 21  // Only 4 valid values

   // GOOD: Use range directly
   age: int in 18..21
   ```

3. **Use `--debug` to diagnose**: See which constraints are failing
   ```bash
   node dist/cli.js file.vague --debug
   ```

### Cross-reference "No matching items" errors

If you get "No matching items found for reference":

1. **Check generation order**: Referenced collections must be generated first
   ```vague
   dataset Data {
     customers: 10 of Customer,    // Generated first
     invoices: 50 of Invoice       // Can reference customers
   }
   ```

2. **Ensure filter matches**: Check that `where` conditions can be satisfied
   ```vague
   // If no customers have status "vip", this will fail
   customer: any of customers where .status == "vip"
   ```

### Plugin not found

1. Check plugin path is relative to config file location
2. For npm packages, ensure they're installed: `npm install vague-plugin-foo`
3. Use `--verbose` to see discovered plugins

### Debug logging

```bash
# Enable all debug output
node dist/cli.js file.vague --debug

# Filter by component
VAGUE_DEBUG=generator,constraint node dist/cli.js file.vague
```

## Development

```bash
npm run build     # Compile TypeScript
npm run test:run  # Run tests once
npm test          # Run tests in watch mode
npm run dev       # Watch mode compilation
```

## Project Structure

```
src/
├── lexer/             # Tokenizer
├── parser/            # Recursive descent parser
├── ast/               # AST node definitions
├── interpreter/       # JSON generator
├── validator/         # Schema validation (Ajv)
├── openapi/           # OpenAPI import support
├── infer/             # Schema inference from data
├── compare/           # Golden dataset comparison and schema diff
├── reporting/         # Enterprise reporting and audit trails
├── csv/               # CSV input/output formatting
├── ndjson/            # NDJSON (newline-delimited JSON) formatting
├── config/            # Configuration file loading (vague.config.js)
├── logging/           # Debug logging utilities
├── plugins/           # Built-in plugins (faker, issuer, date, regex, http, sql, graphql)
├── spectral/          # OpenAPI linting with Spectral
├── server/            # HTTP mock server (--serve)
├── utils/             # Shared type guards and helpers
├── cli/               # CLI argument parsing and handlers
├── format-registry.ts # Output format registry
├── warnings.ts        # Warning collector for non-fatal generation issues
├── index.ts           # Library exports
└── cli.ts             # CLI entry point
```

## Roadmap

Planned features are tracked as [GitHub issues](https://github.com/mcclowes/vague/issues). Highlights:

- Probabilistic constraints (`assume X with probability 0.7`) — [#77](https://github.com/mcclowes/vague/issues/77)
- Conditional values and probabilities — [#74](https://github.com/mcclowes/vague/issues/74)
- Constraint solving (SMT integration) — [#77](https://github.com/mcclowes/vague/issues/77)

## Working with Claude

This project includes Claude Code skills that help Claude assist you more effectively when working with Vague files and OpenAPI specifications.

### Available Skills

| Skill | Description |
|-------|-------------|
| `vague` | Writing Vague (.vague) files - syntax, constraints, cross-references |
| `vague-plugin-faker` | Using faker generators in .vague files |
| `openapi` | Working with OpenAPI specs - validation, schemas, best practices |

### Installation via OpenSkills

Install the skills using [OpenSkills](https://github.com/anthropics/openskills):

```bash
npm i -g openskills
openskills install mcclowes/vague
```

This installs the skills to your `.claude/skills/` directory, making them available when you use Claude Code in this project.

### Manual Installation

Alternatively, copy the skills directly:

```bash
git clone https://github.com/mcclowes/vague.git
cp -r vague/.claude/skills/* ~/.claude/skills/
```

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License

MIT License - see [LICENSE](LICENSE)

---
_Source: https://npm.io/package/vague-lang · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
