npm package: @orkestrel/validator
Description: Runtime validation toolkit for TypeScript
Latest version: 0.0.1

# @orkestrel/validator

> Runtime validation toolkit for TypeScript — zero dependencies, fully typed.

`@orkestrel/validator` gives you two independent tools that work together:

- **String validation** — a declarative rule engine for validating user input. Define which rules apply, call `validateInput`, and get back a structured result with every failing rule and its message.
- **Type guards** — a composable system for narrowing `unknown` values to precise TypeScript types at runtime. Over 60 built-in `is*` guards plus a full set of builder functions for constructing guards over objects, arrays, tuples, enums, and more.

Both axes are ESM-first, tree-shakeable, and produce no side effects.

---

## Installation

```sh
npm install @orkestrel/validator
```

All exports are available from the package root:

```ts
import { validateInput, isString, objectOf } from '@orkestrel/validator'
```

---

## String Validation

Use the rule engine when you need to validate free-form user input — form fields, query parameters, CLI arguments — and want structured error feedback.

### Defining rules

Describe constraints as a plain object:

```ts
import type { ValidationRules } from '@orkestrel/validator'

const usernameRules: ValidationRules = {
	required: true,
	minimum: 3,
	maximum: 20,
	alphanumeric: true,
}

const emailRules: ValidationRules = {
	required: true,
	email: true,
}

const commentRules: ValidationRules = {
	required: true,
	minimum: 1,
	maximum: 500,
}
```

### Validating input

```ts
import { validateInput } from '@orkestrel/validator'

const result = validateInput('Ada123', usernameRules)

if (result.valid) {
	// result.errors is []
} else {
	for (const error of result.errors) {
		console.log(error.rule, error.message)
	}
}
```

`validateInput` evaluates every active rule and collects **all** failures — it does not stop at the first one. The returned `ValidationResult` always has a `valid` boolean and an `errors` array.

### Quick pass/fail check

When you only need a boolean and don't need the error list:

```ts
import { testInput } from '@orkestrel/validator'

if (testInput('Ada123', usernameRules)) {
	// input is valid
}
```

### Built-in rules

| Rule           | Value               | Fails when                                               |
| -------------- | ------------------- | -------------------------------------------------------- |
| `required`     | `true`              | The trimmed input is empty                               |
| `minimum`      | `number ≥ 0`        | `input.length < minimum`                                 |
| `maximum`      | `number ≥ 0`        | `input.length > maximum`                                 |
| `pattern`      | regex string        | Input does not match the compiled pattern                |
| `email`        | `true`              | Input does not look like `local@domain.tld`              |
| `url`          | `true`              | Input does not begin with `http://` or `https://`        |
| `numeric`      | `true`              | Input is not an integer or decimal (optionally negative) |
| `integer`      | `true`              | Input is not a whole integer (optionally negative)       |
| `alphanumeric` | `true`              | Input contains anything other than letters and digits    |
| `custom`       | `ValidatorFunction` | The function returns `false` or an error string          |

Rules set to `false` or omitted are skipped. Rules are always evaluated in this order:
`required → minimum → maximum → pattern → email → url → numeric → integer → alphanumeric → custom`

### Default error messages

Each built-in rule produces a default message when it fails:

| Rule           | Default message                          |
| -------------- | ---------------------------------------- |
| `required`     | `'This field is required'`               |
| `minimum`      | `'Must be at least N characters'`        |
| `maximum`      | `'Must be at most N characters'`         |
| `pattern`      | `'Must match pattern: <pattern>'`        |
| `email`        | `'Must be a valid email address'`        |
| `url`          | `'Must be a valid URL'`                  |
| `numeric`      | `'Must be a numeric value'`              |
| `integer`      | `'Must be an integer'`                   |
| `alphanumeric` | `'Must contain only letters and digits'` |

### Custom validators

Every rule slot accepts a `ValidatorFunction` instead of its canonical value type. The function replaces the built-in check entirely for that slot.

```ts
type ValidatorFunction = (input: string) => boolean | string
```

Return `true` when the input passes. Return `false` or a message string when it fails.

```ts
const rules: ValidationRules = {
	// Replace the built-in minimum check with custom logic
	minimum: (input) => input.startsWith('z') || 'Must start with z',

	// The custom slot has no built-in behaviour — always use a function
	custom: (input) => input !== 'forbidden' || 'That value is not allowed',
}
```

Using a function on a rule slot still uses that slot's position in the evaluation order — a `minimum` function runs in the `minimum` position.

### Collecting multiple errors

```ts
const result = validateInput('', {
	required: true,
	minimum: 3,
	email: true,
})

// {
//   valid: false,
//   errors: [
//     { rule: 'required', message: 'This field is required' },
//     { rule: 'minimum',  message: 'Must be at least 3 characters' },
//     { rule: 'email',    message: 'Must be a valid email address' },
//   ]
// }
```

### Validating rule objects at runtime

If rules arrive from an untrusted source (config file, API), assert them before use:

```ts
import { assertValidationRules } from '@orkestrel/validator'

assertValidationRules(untrustedRules) // throws ValidatorError when invalid
// now untrustedRules is narrowed to ValidationRules
```

`assertValidationRules` checks:

- All keys are supported rule names
- All values are the correct kind for their rule (`boolean` for flag rules, `number ≥ 0` for `minimum`/`maximum`, string for `pattern`)
- `minimum ≤ maximum` when both are provided as numbers
- `pattern` compiles as a valid regular expression

### ValidatorError

Invalid rule configuration throws a `ValidatorError` — a subclass of `Error` with two extra fields:

```ts
import { ValidatorError, isValidatorError } from '@orkestrel/validator'

try {
	assertValidationRules({ minimum: 10, maximum: 3 })
} catch (error) {
	if (isValidatorError(error)) {
		console.log(error.code) // 'INVALID_RULE_VALUE'
		console.log(error.message) // 'Minimum cannot be greater than maximum'
		console.log(error.context.rule) // 'minimum'
		console.log(error.context.value) // 10
	}
}
```

The three error codes:

| Code                 | When thrown                                                          |
| -------------------- | -------------------------------------------------------------------- |
| `INVALID_RULES`      | The rule object has unsupported keys or wrong value kinds            |
| `INVALID_RULE_VALUE` | A numeric rule is out of range (`minimum > maximum`, negative value) |
| `INVALID_PATTERN`    | The `pattern` string is not a valid regular expression               |

Use `isValidatorError` in `catch` blocks to distinguish package errors from unexpected throws.

---

## Type Guards

Use type guards when you need to narrow `unknown` values — incoming API responses, `JSON.parse` output, event payloads, dynamic config — to specific TypeScript types.

All guards follow the same contract:

```ts
// Returns true and narrows the type — never throws
isString(value) // value is string
isNumber(value) // value is number
```

### Primitive guards

```ts
import {
	isNull,
	isUndefined,
	isDefined,
	isString,
	isNumber,
	isBoolean,
	isBigInt,
	isSymbol,
	isFunction,
	isDate,
	isRegExp,
	isError,
	isPromise,
	isPromiseLike,
	isIterable,
	isAsyncIterator,
	isArrayBuffer,
	isSharedArrayBuffer,
} from '@orkestrel/validator'
```

| Guard                    | Narrows to               | Notes                                     |
| ------------------------ | ------------------------ | ----------------------------------------- |
| `isNull(v)`              | `null`                   | Strict `=== null`                         |
| `isUndefined(v)`         | `undefined`              | Strict `=== undefined`                    |
| `isDefined(v)`           | `T`                      | Excludes both `null` and `undefined`      |
| `isString(v)`            | `string`                 | `typeof === 'string'`                     |
| `isNumber(v)`            | `number`                 | Includes `NaN` and `Infinity`             |
| `isBoolean(v)`           | `boolean`                |                                           |
| `isBigInt(v)`            | `bigint`                 |                                           |
| `isSymbol(v)`            | `symbol`                 |                                           |
| `isFunction(v)`          | `AnyFunction`            | `typeof === 'function'`                   |
| `isDate(v)`              | `Date`                   | `instanceof Date`                         |
| `isRegExp(v)`            | `RegExp`                 | `instanceof RegExp`                       |
| `isError(v)`             | `Error`                  | `instanceof Error`                        |
| `isPromise(v)`           | `Promise<T>`             | Native `instanceof Promise` only          |
| `isPromiseLike(v)`       | `Promise<T>` or thenable | Checks `then`, `catch`, `finally` methods |
| `isIterable(v)`          | `Iterable<T>`            | Strings are iterable                      |
| `isAsyncIterator(v)`     | `AsyncIterable<T>`       | Checks `Symbol.asyncIterator`             |
| `isArrayBuffer(v)`       | `ArrayBuffer`            |                                           |
| `isSharedArrayBuffer(v)` | `SharedArrayBuffer`      | Feature-checks availability first         |

```ts
const raw: unknown = JSON.parse(text)

if (isString(raw)) {
	// raw: string
}

// isDefined removes both null and undefined
function process<T>(value: T | null | undefined) {
	if (isDefined(value)) {
		// value: T
	}
}
```

### Object and collection guards

```ts
import { isObject, isRecord, isMap, isSet, isWeakMap, isWeakSet } from '@orkestrel/validator'
```

| Guard          | Narrows to                 | Notes                                 |
| -------------- | -------------------------- | ------------------------------------- |
| `isObject(v)`  | `object`                   | Any non-null object, including arrays |
| `isRecord(v)`  | `Record<string, unknown>`  | Non-null, non-array object            |
| `isMap(v)`     | `ReadonlyMap<K, V>`        | `instanceof Map`                      |
| `isSet(v)`     | `ReadonlySet<T>`           | `instanceof Set`                      |
| `isWeakMap(v)` | `WeakMap<object, unknown>` |                                       |
| `isWeakSet(v)` | `WeakSet<object>`          |                                       |

`isRecord` is the right choice for plain objects — it excludes arrays:

```ts
isObject([]) // true  — arrays are objects
isRecord([]) // false — arrays excluded
isRecord({}) // true
isRecord(null) // false
```

### Array and typed array guards

```ts
import {
	isArray,
	isDataView,
	isArrayBufferView,
	isUint8Array,
	isInt32Array,
	isFloat64Array,
	// ... all 11 typed-array guards
} from '@orkestrel/validator'
```

`isArray` wraps `Array.isArray`. For element-level checking use `arrayOf` from the guard builders section.

`isArrayBufferView` accepts any typed array or `DataView` via `ArrayBuffer.isView`.

All 11 typed-array guards: `isInt8Array`, `isUint8Array`, `isUint8ClampedArray`, `isInt16Array`, `isUint16Array`, `isInt32Array`, `isUint32Array`, `isFloat32Array`, `isFloat64Array`, `isBigInt64Array`, `isBigUint64Array`.

### Emptiness guards

```ts
import {
	isEmptyString,
	isEmptyArray,
	isEmptyObject,
	isEmptyMap,
	isEmptySet,
	isNonEmptyString,
	isNonEmptyArray,
	isNonEmptyObject,
	isNonEmptyMap,
	isNonEmptySet,
} from '@orkestrel/validator'
```

**Empty:**

| Guard              | Narrows to                        |
| ------------------ | --------------------------------- |
| `isEmptyString(v)` | `''`                              |
| `isEmptyArray(v)`  | `readonly []`                     |
| `isEmptyObject(v)` | `Record<string \| symbol, never>` |
| `isEmptyMap(v)`    | `ReadonlyMap<never, never>`       |
| `isEmptySet(v)`    | `ReadonlySet<never>`              |

**Non-empty:**

| Guard                 | Narrows to                          |
| --------------------- | ----------------------------------- |
| `isNonEmptyString(v)` | `string` (length > 0)               |
| `isNonEmptyArray(v)`  | `readonly [T, ...T[]]`              |
| `isNonEmptyObject(v)` | `Record<string \| symbol, unknown>` |
| `isNonEmptyMap(v)`    | `ReadonlyMap<K, V>`                 |
| `isNonEmptySet(v)`    | `ReadonlySet<T>`                    |

`isEmptyObject` counts both string keys and **enumerable symbol keys** — an object with only a symbol property is not considered empty:

```ts
isEmptyObject({}) // true
isEmptyObject({ [Symbol('x')]: true }) // false
```

### Function guards

```ts
import {
	isAsyncFunction,
	isGeneratorFunction,
	isAsyncGeneratorFunction,
	isZeroArg,
	isZeroArgAsync,
	isZeroArgGenerator,
	isZeroArgAsyncGenerator,
} from '@orkestrel/validator'
```

| Guard                          | Passes when                        |
| ------------------------------ | ---------------------------------- |
| `isZeroArg(fn)`                | `fn.length === 0`                  |
| `isAsyncFunction(fn)`          | Native `async function`            |
| `isGeneratorFunction(fn)`      | `function*`                        |
| `isAsyncGeneratorFunction(fn)` | `async function*`                  |
| `isZeroArgAsync(fn)`           | Async and zero arguments           |
| `isZeroArgGenerator(fn)`       | Generator and zero arguments       |
| `isZeroArgAsyncGenerator(fn)`  | Async generator and zero arguments |

```ts
isAsyncFunction(async () => true) // true
isAsyncFunction(() => Promise.resolve(true)) // false — sync, returns a Promise
isGeneratorFunction(function* () {
	yield 1
}) // true
```

> **Note:** Detection uses `constructor.name`. Minifiers that rename function constructors will break these guards.

---

## Guard Builders

Guard builders compose primitive guards into guards for complex shapes. They are the primary API for narrowing structured data like API responses.

### Building object schemas — `objectOf`

```ts
import { objectOf, isString, isNumber } from '@orkestrel/validator'

const isUser = objectOf({ id: isString, age: isNumber })

const raw: unknown = await fetchUser()
if (isUser(raw)) {
	// raw: Readonly<{ id: string; age: number }>
	console.log(raw.id, raw.age)
}
```

`objectOf` is **exact** by default: it rejects any value with extra string keys not in the shape. Symbol keys on the value are silently ignored.

#### Optional keys

Pass an array of key names to make those keys optional:

```ts
const isUser = objectOf(
	{ id: isString, role: isString, note: isString },
	['note'], // note may be absent
)

isUser({ id: 'u1', role: 'admin' }) // true
isUser({ id: 'u1', role: 'admin', note: 'hi' }) // true
isUser({ id: 'u1', role: 'admin', note: 1 }) // false — note fails its guard
```

Pass `true` to make every key optional:

```ts
const isPartialUser = objectOf({ id: isString, age: isNumber }, true)
isPartialUser({}) // true
isPartialUser({ id: 'u1' }) // true
```

#### `recordOf`

Identical signature to `objectOf`. Use `recordOf` when the value is a dictionary-style record rather than a structured entity — purely a naming convention for readability.

### Arrays — `arrayOf`

```ts
import { arrayOf, isString } from '@orkestrel/validator'

const isStrings = arrayOf(isString)
isStrings(['a', 'b']) // true
isStrings(['a', 1]) // false
```

### Tuples — `tupleOf`

Validates a fixed-length array with per-index guards:

```ts
import { tupleOf, isString, isNumber } from '@orkestrel/validator'

const isEntry = tupleOf(isString, isNumber)
isEntry(['id', 42]) // true
isEntry(['id', 'x']) // false — second element fails
isEntry(['id']) // false — length mismatch
```

### Literals and enums — `literalOf`, `enumOf`

```ts
import { literalOf, enumOf } from '@orkestrel/validator'

// Narrow to a fixed set of literal values
const isRole = literalOf('user', 'admin', 'guest')
isRole('admin') // true
isRole('owner') // false

// Narrow to a TypeScript enum
enum Status {
	Idle,
	Busy,
}
const isStatus = enumOf(Status)
isStatus(Status.Idle) // true
isStatus(0) // true  — numeric enum value
isStatus('Idle') // false — reverse-mapped names are not values

// String enum
const isFlag = enumOf({ active: 'ACTIVE', paused: 'PAUSED' })
isFlag('ACTIVE') // true
isFlag('active') // false — keys are not values
```

### Sets and Maps — `setOf`, `mapOf`

```ts
import { setOf, mapOf, isString, isNumber } from '@orkestrel/validator'

const isTagSet = setOf(isString)
const isScores = mapOf(isString, isNumber)

isTagSet(new Set(['a', 'b'])) // true
isTagSet(new Set(['a', 1])) // false
isScores(new Map([['alice', 98]])) // true
isScores(new Map([['alice', '98']])) // false
```

### Iterables — `iterableOf`

Accepts any value with `Symbol.iterator` — arrays, sets, generators — and validates every yielded element:

```ts
import { iterableOf, isNumber } from '@orkestrel/validator'

const isNumbers = iterableOf(isNumber)
isNumbers([1, 2, 3]) // true
isNumbers(new Set([1, 2])) // true
isNumbers([1, '2']) // false
```

### Keys and instance checks — `keyOf`, `instanceOf`

```ts
import { keyOf, instanceOf } from '@orkestrel/validator'

// Accept only keys present on a specific object
const COLORS = { red: '#f00', blue: '#00f', green: '#0f0' } as const
const isColorKey = keyOf(COLORS)
isColorKey('red') // true
isColorKey('yellow') // false

// Accept instances of a class
class ApiError extends Error {
	constructor(
		readonly status: number,
		message: string,
	) {
		super(message)
	}
}
const isApiError = instanceOf(ApiError)
isApiError(new ApiError(404, 'Not found')) // true
isApiError(new Error('generic')) // false
```

### Shape transforms — `pickOf`, `omitOf`

Build a new guard shape by selecting or removing keys from an existing one. The result is a `GuardsShape` — pass it to `objectOf` or `recordOf`.

```ts
import { pickOf, omitOf, objectOf, isString, isNumber } from '@orkestrel/validator'

const userShape = { id: isString, age: isNumber, name: isString, role: isString }

// Keep only the keys you need
const isPublicUser = objectOf(pickOf(userShape, ['id', 'name']))
isPublicUser({ id: 'u1', name: 'Ada' }) // true
isPublicUser({ id: 'u1', name: 'Ada', age: 30 }) // false — exact shape, extra key rejected

// Remove keys you don't want
const isUserWithoutAge = objectOf(omitOf(userShape, ['age']))
isUserWithoutAge({ id: 'u1', name: 'Ada', role: 'admin' }) // true
```

---

## Logical Combinators

### Refinement — `whereOf`

The most common combinator. Refines a base guard with an additional predicate. The predicate receives a typed value (the base guard already narrowed it), so TypeScript provides full autocomplete.

```ts
import { whereOf, isString, testInput } from '@orkestrel/validator'

// Simple predicate
const isNonEmptyString = whereOf(isString, (value) => value.length > 0)

// Embed a rule check inside a guard
const usernameRules = { required: true, minimum: 3, maximum: 20, alphanumeric: true }
const isUsername = whereOf(isString, (value) => testInput(value, usernameRules))
```

### AND — `andOf`

```ts
import { andOf, isString } from '@orkestrel/validator'

const isNonEmptyString = andOf(isString, (value: string) => value.length > 0)
```

### OR — `orOf`

```ts
import { orOf, isString, isNumber } from '@orkestrel/validator'

const isStringOrNumber = orOf(isString, isNumber)
```

### NOT — `notOf`

```ts
import { notOf, isString } from '@orkestrel/validator'

const isNotString = notOf(isString)
```

### Union — `unionOf`

Variadic OR across any number of guards:

```ts
import { unionOf, literalOf } from '@orkestrel/validator'

const isDirection = unionOf(
	literalOf('north'),
	literalOf('south'),
	literalOf('east'),
	literalOf('west'),
)
```

### Intersection — `intersectionOf` and `composedOf`

Both require every guard to pass. Use `intersectionOf` for true type intersections; use `composedOf` when layering constraints on the same base type:

```ts
import { intersectionOf, composedOf, isString } from '@orkestrel/validator'

const isShortString = intersectionOf(
	isString,
	(value: unknown): value is string => isString(value) && value.length < 10,
)

// composedOf reads more clearly when all guards share the same type
const isAlphaCode = composedOf(
	(value: unknown): value is string => isString(value) && /^[A-Za-z]+$/.test(value),
	(value: unknown): value is string => isString(value) && value.length === 2,
)
isAlphaCode('en') // true
isAlphaCode('en1') // false
```

### Complement — `complementOf`

Subtracts a narrower type from a broader one — passes when the value satisfies the base guard but not the excluded guard:

```ts
import { complementOf, unionOf, objectOf, literalOf, isNumber } from '@orkestrel/validator'

const isCircle = objectOf({ kind: literalOf('circle'), radius: isNumber })
const isRectangle = objectOf({ kind: literalOf('rectangle'), width: isNumber, height: isNumber })
const isShape = unionOf(isCircle, isRectangle)
const isNotCircle = complementOf(isShape, isCircle)

isNotCircle({ kind: 'rectangle', width: 2, height: 3 }) // true
isNotCircle({ kind: 'circle', radius: 3 }) // false
```

### Nullable — `nullableOf`

Extends any guard to also accept `null`:

```ts
import { nullableOf, isString } from '@orkestrel/validator'

const isMaybeString = nullableOf(isString)
isMaybeString(null) // true
isMaybeString('value') // true
isMaybeString(undefined) // false
```

### Transform — `transformOf`

Validates the original input by projecting it and checking the projected value. The original (not the projection) is what gets narrowed:

```ts
import { transformOf, isString, isNumber } from '@orkestrel/validator'

// Accept strings with a positive character count
const isNonEmptyString = transformOf(
	isString,
	(value) => value.length,
	(n): n is number => typeof n === 'number' && n > 0,
)
isNonEmptyString('abc') // true
isNonEmptyString('') // false
```

### Lazy — `lazyOf`

Defers guard creation until first call. Required when building guards for **recursive types**, because the guard variable isn't defined yet at the point of the `objectOf` call:

```ts
import type { Guard } from '@orkestrel/validator'
import { lazyOf, objectOf, arrayOf, isNumber } from '@orkestrel/validator'

interface Tree {
	readonly value: number
	readonly children?: readonly Tree[]
}

const isTree: Guard<Tree> = lazyOf(() =>
	objectOf(
		{
			value: isNumber,
			children: arrayOf(isTree), // safe — isTree is resolved at call time
		},
		['children'],
	),
)

isTree({ value: 1, children: [{ value: 2 }] }) // true
isTree({ value: 'x' }) // false
```

---

## Combining Guards and Validation

The two axes compose naturally. Use `whereOf` with `testInput` to embed field-level validation inside a structural guard:

```ts
import {
	objectOf,
	arrayOf,
	literalOf,
	isString,
	whereOf,
	testInput,
	validateInput,
} from '@orkestrel/validator'

const usernameRules = { required: true, minimum: 3, maximum: 20, alphanumeric: true }
const emailRules = { required: true, email: true }

const isSignupForm = objectOf(
	{
		username: whereOf(isString, (v) => testInput(v, usernameRules)),
		email: whereOf(isString, (v) => testInput(v, emailRules)),
		role: literalOf('user', 'admin'),
		tags: arrayOf(whereOf(isString, (v) => v.length > 0)),
	},
	['tags'], // tags is optional
)

const payload: unknown = JSON.parse(request.body)

if (isSignupForm(payload)) {
	// payload is fully typed:
	// {
	//   username: string
	//   email:    string
	//   role:     'user' | 'admin'
	//   tags?:    readonly string[]
	// }
	submit(payload)
} else {
	// Get per-field error details with validateInput
	const usernameResult = validateInput(isString(payload) ? payload : '', usernameRules)
	return { errors: usernameResult.errors }
}
```

---

## TypeScript Types

These types are exported for use in your own function signatures.

### Guard types

```ts
import type { Guard, GuardType, GuardsShape } from '@orkestrel/validator'

// Guard<T> — the type predicate signature
const isRole: Guard<'user' | 'admin'> = literalOf('user', 'admin')

// GuardType — extract T from a Guard<T>
type Role = GuardType<typeof isRole> // 'user' | 'admin'

// GuardsShape — a map of string keys to guards, input to objectOf / recordOf
const userShape: GuardsShape = { id: isString, age: isNumber }
```

### Output types from shapes

```ts
import type { FromGuards, OptionalFromGuards, AllOptionalFromGuards } from '@orkestrel/validator'

const userShape = { id: isString, age: isNumber, note: isString }

// All keys required
type User = FromGuards<typeof userShape>
// Readonly<{ id: string; age: number; note: string }>

// Selected keys optional
type FlexUser = OptionalFromGuards<typeof userShape, ['note']>
// Readonly<{ id: string; age: number; note?: string }>

// All keys optional
type PartialUser = AllOptionalFromGuards<typeof userShape>
// Readonly<Partial<{ id: string; age: number; note: string }>>
```

### Validation types

```ts
import type {
	ValidationRules,
	ValidationResult,
	ValidationError,
	ValidationRuleName,
	ValidatorFunction,
	ValidatorErrorCode,
	ValidatorErrorContext,
} from '@orkestrel/validator'
```

### Function type aliases

```ts
import type {
	AnyFunction, // (...args: unknown[]) => unknown
	AnyAsyncFunction, // (...args: unknown[]) => Promise<unknown>
	ZeroArgFunction, // () => unknown
	ZeroArgAsyncFunction, // () => Promise<unknown>
	AnyConstructor, // new (...args: never[]) => T
} from '@orkestrel/validator'
```

---

## Low-level APIs

These are exported for advanced use cases but most consumers won't need them directly.

### `evaluateRule`

Evaluate a single rule against a string without constructing a full rule set:

```ts
import { evaluateRule } from '@orkestrel/validator'

evaluateRule('required', true, '') // 'This field is required'
evaluateRule('required', true, 'x') // undefined (passed)
evaluateRule('minimum', 3, 'ab') // 'Must be at least 3 characters'
```

### `cloneValidationRules`

Shallow-clone a rule object (validates it first):

```ts
import { cloneValidationRules } from '@orkestrel/validator'

const copy = cloneValidationRules({ required: true, minimum: 3 })
```

### `createEnumValues`

Extract the runtime values from a TypeScript enum. Numeric enum reverse-mapping is handled automatically:

```ts
import { createEnumValues } from '@orkestrel/validator'

createEnumValues({ idle: 'IDLE', busy: 'BUSY' }) // ['IDLE', 'BUSY']

enum Status {
	Idle,
	Busy,
}
createEnumValues(Status) // [0, 1] — not ['Idle', 'Busy']
```

### `createShapeGuard`

The factory behind `objectOf` and `recordOf`. Prefer those functions in application code:

```ts
import { createShapeGuard } from '@orkestrel/validator'

const isUser = createShapeGuard({ id: isString, age: isNumber })
const isFlexUser = createShapeGuard({ id: isString, note: isString }, ['note'])
const isPartialUser = createShapeGuard({ id: isString, age: isNumber }, true)
```

### Domain-level type guards

Guards for the validation domain types themselves — useful when processing rule objects or results from unknown sources:

```ts
import {
	isValidationRuleName, // 'required' | 'minimum' | ...
	isValidationRules, // ValidationRules
	isValidationError, // ValidationError
	isValidationResult, // ValidationResult
	isValidatorFunction, // ValidatorFunction
} from '@orkestrel/validator'

isValidationRuleName('required') // true
isValidationRuleName('unknown') // false
isValidationRules({ required: true, minimum: 3 }) // true
isValidationRules({ required: 'yes' }) // false
isValidationError({ rule: 'required', message: 'This field...' }) // true
isValidationResult({ valid: true, errors: [] }) // true
```

### `recordValue`

Read a property from an object by any key type (including symbols):

```ts
import { recordValue } from '@orkestrel/validator'

const sym = Symbol('id')
const obj = { name: 'Ada', [sym]: 42 }

recordValue(obj, 'name') // 'Ada'
recordValue(obj, sym) // 42
recordValue(obj, 'missing') // undefined
```

### Built-in regex constants

```ts
import {
	EMAIL_PATTERN, // /^[^\s@]+@[^\s@]+\.[^\s@]+$/
	URL_PATTERN, // /^https?:\/\/.+/
	NUMERIC_PATTERN, // /^-?\d+(\.\d+)?$/
	INTEGER_PATTERN, // /^-?\d+$/
	ALPHANUMERIC_PATTERN, // /^[a-zA-Z0-9]+$/
	VALIDATION_RULE_NAMES, // ordered list of all rule names
} from '@orkestrel/validator'
```

Import these when building custom validators that should match the built-in rule semantics exactly.