npm package: @janus-validator/core
Description: A combinator library for defining validators that validate objects and provide examples
Latest version: 0.6.0

# @janus-validator/core

Core validation library for Janus Validator - composable validators that can both **validate data** and **generate valid examples**. Perfect for testing, API validation, and form handling.

## Features

- 🎯 **Type-safe validation** with clear, helpful error messages
- 🧬 **Structured, recursive errors** (per-field / per-index) with generated examples
- 🎲 **Automatic data generation** from validator definitions (great for tests/fixtures)
- 🧩 **Composable combinators** for complex schemas
- 🎭 **Realistic presets** for names, emails, addresses, etc.
- 🔧 **Custom generators** to override default generation
- 📝 **Optional concise DSL** available in [@janus-validator/dsl](../dsl/README.md)

## Installation

```bash
# Core + DSL package (recommended)
npm install @janus-validator/core @janus-validator/dsl

# Or just core (no DSL)
npm install @janus-validator/core
```

## Quick Start

This demonstrates usage without the DSL, although usable, it's generally recommended you use the DSL. They are functionally equivalent, just the syntax is shorter and cleaner.

```typescript
import { Generator } from '@janus-validator/core';
import {
  Struct,
  UnicodeString,
  Integer,
  Boolean,
  Regex,
} from '@janus-validator/core/combinators';

// Define a validator (core API)
const userValidator = Struct({
  name: UnicodeString(1, 100),
  age: Integer(0, 150),
  email: Regex(/^[\w.]+@[\w.]+\.\w+$/),
  active: Boolean(),
});

// Validate data
const result = userValidator.validate({
  name: 'Alice',
  age: 30,
  email: 'alice@example.com',
  active: true,
});

if (result.valid) {
  console.log('Valid:', result.value);
} else {
  console.log('Error:', result.error);
  console.log('Example:', result.example); // Auto-generated valid example
}

// Generate test data
const generator = new Generator({ random: Math.random });
const testUser = generator.generate(userValidator.domain);
// { name: 'Alice', age: 42, email: 'test@example.com', active: true }
```

## The “two faces”: validate + generate

Janus validators can be used in both directions:

- Forwards: `validator.validate(unknown)` → `ValidationResult<T>`
- Backwards: `generator.generate(validator.domain)` → `T`

This enables “round-trip” testing: anything generated by a validator should validate.

```typescript
import { Generator } from '@janus-validator/core';
import { Struct, UnicodeString, Integer } from '@janus-validator/core/combinators';

const User = Struct({ name: UnicodeString(1, 50), age: Integer(0, 150) });
const generator = new Generator({ random: Math.random });

const sample = generator.generate(User.domain);
const result = User.validate(sample);
// result.valid === true
```

## Structured errors (with examples)

When validation fails, you get:
- `error`: a path-based summary (e.g. `profile.age: Value 999 is greater than maximum 150`)
- `results`: a recursive structure showing which parts passed/failed
- `example`: an auto-generated valid value for the failing validator

```typescript
import { Struct, UnicodeString, Integer } from '@janus-validator/core/combinators';

const Profile = Struct({ name: UnicodeString(1, 50), age: Integer(0, 150) });
const result = Profile.validate({ name: 'Alice', age: 999 });

if (!result.valid) {
  result.error;
  result.results; // per-field ValidationResult
  result.example; // generated valid Profile
}
```

## DSL

The DSL lives in a separate package: [@janus-validator/dsl](../dsl/README.md).
It provides short aliases (`O`, `U`, `I`, `B`, `Or`, `Seq`, …) plus primitive/enum auto-wrapping.

## Realistic Data Presets

Generate realistic test data:

```typescript
import {
  FirstName, LastName, FullName,
  RealisticEmail, CorporateEmailPreset,
  RealisticUSPhone,
  RealisticStreetAddress, RealisticCity, RealisticState, RealisticZipCode,
  CompanyName, ProductName,
  RecentDate, FutureDate,
  RealisticPrice,
} from '@janus-validator/core/lib';
import { O } from '@janus-validator/dsl';
import { Generator } from '@janus-validator/core';

const generator = new Generator({ random: Math.random });

const customer = O({
  name: FullName(),           // "Alice Smith"
  email: RealisticEmail(),    // "alice.smith@gmail.com"
  phone: RealisticUSPhone(),  // "(555) 123-4567"
  address: O({
    street: RealisticStreetAddress(), // "1234 Oak St"
    city: RealisticCity(),            // "New York"
    state: RealisticState(),          // "NY"
    zip: RealisticZipCode(),          // "10001"
  }),
});

const testCustomer = generator.generate(customer.domain);
```

## Custom Generators

Override default generation with custom logic:

```typescript
import { fromValues, templateGenerator, withGenerator } from '@janus-validator/core/combinators';
import { U, I, R } from '@janus-validator/dsl';

// Generate from a fixed list
const country = fromValues(R(/^[A-Z]{2}$/), ['US', 'CA', 'GB', 'DE', 'FR']);

// Template-based generation
const sku = templateGenerator(U(10, 20), (pick, rng) => {
  const categories = ['ELEC', 'FURN', 'CLTH'];
  const num = Math.floor(rng.random() * 10000).toString().padStart(4, '0');
  return `${pick(categories)}-${num}`;
});
// Generates: "ELEC-0042", "FURN-1337", etc.

// Custom generation function
const userId = withGenerator(I(1, 1000000), (rng) => {
  return Math.floor(rng.random() * 1000000) + 1;
});
```

## Error Messages with Examples

All DSL validators automatically include valid examples in error messages:

```typescript
import { I } from '@janus-validator/dsl';

const age = I(18, 100);
const result = age.validate('not a number');

// result = {
//   valid: false,
//   error: 'Expected number, got string',
//   example: 42  // Auto-generated valid example
// }
```

## Real-World Examples

### API Request Validation

```typescript
import { O, U, I, R, Or, Null } from '@janus-validator/dsl';

const createUserRequest = O({
  username: R(/^[a-zA-Z][a-zA-Z0-9_]{2,19}$/),
  email: R(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/),
  password: U(8, 128),
  profile: O({
    firstName: U(1, 50),
    lastName: U(1, 50),
    age: Or(I(13, 150), Null()),
  }),
});

// In your API handler
const result = createUserRequest.validate(req.body);
if (!result.valid) {
  return res.status(400).json({ error: result.error });
}
```

### Form Validation

```typescript
import { O, U, R, createCaptureGroup } from '@janus-validator/dsl';

const { capture, ref, context } = createCaptureGroup();

const signupForm = O({
  email: R(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/),
  password: capture('pwd', U(8, 100)),
  confirmPassword: ref('pwd'),
  acceptTerms: true, // Must be true (auto-wrapped)
});

function validateForm(data: unknown) {
  context.clear();
  return signupForm.validate(data);
}
```

### E-Commerce Order

```typescript
import { O, U, I, N, R, Or, oneOrMore } from '@janus-validator/dsl';

const orderItem = O({
  productId: U(10, 50),
  name: U(1, 200),
  quantity: I(1, 100),
  unitPrice: N(0.01, 100000),
});

const order = O({
  orderId: R(/^ORD-\d{8}$/),
  customerId: I(1, 1000000),
  items: oneOrMore(orderItem),
  subtotal: N(0, 1000000),
  tax: N(0, 100000),
  total: N(0, 1100000),
  status: Or('pending', 'processing', 'shipped', 'delivered'), // Auto-wrapped!
  createdAt: R(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/),
});
```

### Test Data Generation

```typescript
import { Generator } from '@janus-validator/core';

const rng = { random: Math.random };
const generator = new Generator(rng);

// Generate 100 test users
const testUsers = Array.from({ length: 100 }, () => 
  generator.generate(userValidator.domain)
);

// Use in tests
describe('UserService', () => {
  it('should create users', () => {
    const user = generator.generate(userValidator.domain);
    const created = userService.create(user);
    expect(created.id).toBeDefined();
  });
});
```

## API Reference

### Validator Interface

```typescript
interface Validator<T> {
  validate(input: unknown): ValidationResult<T>;
  domain: Domain<T>;
}

type ValidationResult<T> =
  | { valid: true; value: T }
  | {
      valid: false;
      error: string;
      example?: T;
      results?: { [key: string]: ValidationResult<any> } | ValidationResult<any>[];
    };
```

### Type Utilities

Helper types for working with validators:

```typescript
import {
  InferValidatorType,   // Extract T from Validator<T>
  UnionOfValidators,    // [Validator<A>, Validator<B>] => A | B
  TupleOfValidators,    // [Validator<A>, Validator<B>] => [A, B]
  ValidatorSchema,      // { [key: string]: Validator<any> }
  InferSchemaType,      // { a: Validator<A> } => { a: A }
} from '@janus-validator/core';

// Example: Infer type from any validator
type UserType = InferValidatorType<typeof userValidator>;
```

### Generator Class

```typescript
class Generator {
  constructor(rng: RNG);
  generate<T>(validator: Validator<T>): T;
}

interface RNG {
  random(): number; // Returns 0-1
}
```

## License

MIT