Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
# ArraySchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`ArraySchema<T>` validates that an input is a JavaScript array and that every element satisfies an inner schema. It collects element-level errors with positional paths (`[0]`, `[1]`, …) so callers can display per-item validation feedback. All constraint builders return new instances; the inner element schema is never mutated.
## Features
- Type check: rejects non-arrays with `VALIDATION.INVALID_TYPE`
- Element validation: every item parsed with the element schema; all errors collected
- `min(n)` / `max(n)` — length constraints
- `length(n)` — exact length
- `nonempty()` — shorthand for `min(1)`
## Usage: Basic Example
```typescript
import { array, string } from "@suaveplan/validation";
const tags = array(string()).min(1).max(10);
tags.parse(["typescript", "validation"]); // ok
tags.parse([]); // throws (too short)
```
## Usage: Advanced Example
```typescript
import { array, object, string, number } from "@suaveplan/validation";
const items = array(
object({ sku: string(), qty: number().int().positive() })
).nonempty();
const cart = items.safeParse([
{ sku: "ABC", qty: 2 },
{ sku: "XYZ", qty: 0 }, // fails positive()
]);
// cart.error.issues[0].path === [1, "qty"]
```
## API Reference
### `array<T>(schema: BaseSchema<unknown, T>): ArraySchema<T>`
Factory accepting the element schema.
### Constraint methods (all return new `ArraySchema<T>`)
| Method | Error code | Description |
|--------|-----------|-------------|
| `min(n)` | `ARRAY.TOO_SHORT` | Length must be ≥ n |
| `max(n)` | `ARRAY.TOO_LONG` | Length must be ≤ n |
| `length(n)` | `ARRAY.TOO_SHORT` | Length must be exactly n |
| `nonempty()` | `ARRAY.TOO_SHORT` | Must have at least one element |
## Implementation Notes
- All element errors are accumulated even when earlier elements fail, giving the caller a complete picture. The parse returns `INVALID` after collecting all issues.
- Element error paths include the numeric index: `ctx.path = [...parentPath, i]`.
- Array length checks run before element validation to avoid unnecessary work on obviously wrong inputs.

View File

@@ -0,0 +1,70 @@
# BooleanSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`BooleanSchema` validates that an input is a native JavaScript `boolean` — exactly `true` or `false`. It does not coerce string representations like `"true"` or numeric values like `1`; use `coerceBoolean` from `@suaveplan/validation` if coercion is needed. The schema is stateless and the `boolean()` factory always returns the same shape.
## Features
- Type check: rejects non-booleans with `VALIDATION.INVALID_TYPE`
- Zero-configuration: no constraints beyond type checking
- Compatible with `optional()`, `nullable()`, `refine()`, and `transform()` from `BaseSchema`
## Usage: Basic Example
```typescript
import { boolean } from "@suaveplan/validation";
const active = boolean();
active.parse(true); // true
active.parse(false); // false
active.parse(1); // throws ValidationError
```
## Usage: Advanced Example
```typescript
import { boolean, object, string } from "@suaveplan/validation";
const featureFlag = object({
name: string(),
enabled: boolean(),
experimental: boolean().optional(),
});
const result = featureFlag.safeParse({
name: "dark-mode",
enabled: true,
});
// { success: true, data: { name: "dark-mode", enabled: true, experimental: undefined } }
```
## API Reference
### `boolean(): BooleanSchema`
Returns a `BooleanSchema` instance.
### `_parse(input, ctx): boolean | INVALID`
Returns the input unchanged if it is a `boolean`, otherwise pushes an `VALIDATION.INVALID_TYPE` issue and returns `INVALID`.
## Implementation Notes
- `BooleanSchema` is the simplest concrete schema — it has no constraint fields and no builder methods beyond those inherited from `BaseSchema`.
- Coercion (string `"true"` → `true`) is intentionally excluded from this schema to keep the boolean validation surface predictable. Use `coerce(boolean())` when HTTP query parameters need coercion.
### Parse Behaviour
`z.boolean()` accepts only actual `true` / `false` values in `parse` / `safeParse`. Use `z.coerce.boolean()` when you want string/number coercion (`"true"`, `1`, `"1"` → `true`). All coercion rules follow the Zod v4 reference table so schemas here are drop-in replacements for Zod schemas elsewhere in the ecosystem.
## Further Usage: Basic Example
```ts
import z from "@suaveplan/validation";
const s = z.boolean();
s.parse(true); // true
```

View File

@@ -0,0 +1,68 @@
# DateSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`DateSchema` validates that an input is a JavaScript `Date` instance with a valid (non-NaN) time value, and optionally enforces inclusive `min` / `max` date boundaries. It does not coerce ISO strings or timestamps to `Date`; use `coerceDate` from `@suaveplan/validation` for that. The schema is immutable — each constraint builder returns a new `DateSchema`.
## Features
- Type check: rejects non-Date values with `VALIDATION.INVALID_TYPE`
- Invalid date detection: rejects `new Date("invalid")` with `VALIDATION.DATE.INVALID`
- `min(d)` — date must be on or after `d` (inclusive)
- `max(d)` — date must be on or before `d` (inclusive)
## Usage: Basic Example
```typescript
import { date } from "@suaveplan/validation";
const born = date().max(new Date());
born.parse(new Date("1990-01-01")); // valid
born.parse(new Date("2099-01-01")); // throws ValidationError (too late)
```
## Usage: Advanced Example
```typescript
import { date, object } from "@suaveplan/validation";
const booking = object({
checkIn: date().min(new Date()),
checkOut: date(),
}).refine(
(v) => v.checkOut > v.checkIn,
"Check-out must be after check-in",
);
const result = booking.safeParse({
checkIn: new Date("2025-06-01"),
checkOut: new Date("2025-06-07"),
});
```
## API Reference
### `date(): DateSchema`
Returns an unconstrained `DateSchema`.
### `min(d: Date): DateSchema`
Returns a new `DateSchema` requiring the input date to be ≥ `d`. Emits `VALIDATION.DATE.TOO_EARLY` on failure.
### `max(d: Date): DateSchema`
Returns a new `DateSchema` requiring the input date to be ≤ `d`. Emits `VALIDATION.DATE.TOO_LATE` on failure.
## Implementation Notes
- `Date` validity is checked via `Number.isNaN(input.getTime())`, which correctly identifies `new Date("invalid")` without requiring string inspection.
- Boundary comparisons use JavaScript `<` / `>` on `Date` objects, which internally compares millisecond timestamps.
- String-to-Date coercion is intentionally omitted. Pair with `coerceDate` when accepting JSON payloads.
### Parse Behaviour
`z.date()` accepts only `Date` instances; `z.coerce.date()` additionally accepts ISO-8601 strings and millisecond-epoch numbers. Invalid dates (`new Date("nope")` — a `Date` with `NaN` time) are rejected as `invalid_date`. Refinements `min()` / `max()` compare `getTime()` values so time-zone differences fold into the numeric comparison.

View File

@@ -0,0 +1,57 @@
# EnumSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`EnumSchema<T>` validates that an input is one of a fixed set of allowed values (a closed-ended enumeration). It differs from `LiteralSchema` in that it checks membership in an array rather than equality with a single value, and from TypeScript's `enum` keyword in that it operates on plain string/number arrays at runtime.
## Features
- Membership check via `Array.includes`
- Supports `string` and `number` value arrays
- Returns `VALIDATION.ENUM.INVALID_VALUE` with the list of allowed values on failure
## Usage: Basic Example
```typescript
import { enumeration } from "@suaveplan/validation";
const status = enumeration(["active", "inactive", "pending"] as const);
status.parse("active"); // "active"
status.parse("deleted"); // throws ValidationError
```
## Usage: Advanced Example
```typescript
import { enumeration, object, string } from "@suaveplan/validation";
const ROLES = ["admin", "editor", "viewer"] as const;
type Role = typeof ROLES[number];
const RoleAssignment = object({
userId: string().uuid(),
role: enumeration(ROLES),
});
```
## API Reference
### `enumeration<T extends string | number>(values: readonly T[]): EnumSchema<T>`
Factory accepting the array of allowed values. The name `enumeration` avoids conflict with the reserved `enum` keyword.
### `_parse(input, ctx): T | INVALID`
Checks `Array.prototype.includes`. On failure, the error message lists all allowed values via `JSON.stringify`.
## Implementation Notes
- Membership check uses `Array.prototype.includes` which is O(n) in the values array. For very large enumerations (100+ values), consider a `Set`-based lookup — though typical enum sizes (< 20 values) make this irrelevant.
- `as const` on the values array is recommended to get the narrowest literal type for `T`.
### Parse Behaviour
`z.enum(["a", "b", "c"])` accepts only the listed string literal values and produces a branded union type so downstream code gets the narrow literal rather than `string`. A helper `.extract([...])` returns a subset enum; `.exclude([...])` returns the complement. Both preserve the original ordering so telemetry and UI code can rely on it.

View File

@@ -0,0 +1,64 @@
# IntersectionSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`IntersectionSchema` validates that an input satisfies two schemas simultaneously and merges their outputs. For object schemas, the merge is performed via `Object.assign`, which implements a right-biased merge where overlapping keys from the second schema take precedence. For non-object schemas (strings, numbers, etc.), the second schema's output is used directly.
## Features
- Both schemas must pass — issues from both arms are aggregated on failure
- Object output merging via `Object.assign({}, resultA, resultB)`
- Non-object fallback: returns second schema's result
## Usage: Basic Example
```typescript
import { intersection, object, string, number } from "@suaveplan/validation";
const Named = object({ name: string() });
const Aged = object({ age: number() });
const Person = intersection(Named, Aged);
Person.parse({ name: "Alice", age: 30 });
// { name: "Alice", age: 30 }
```
## Usage: Advanced Example
```typescript
import { intersection, object, string } from "@suaveplan/validation";
const Timestamped = object({ createdAt: string(), updatedAt: string() });
const Entity = object({ id: string().uuid(), type: string() });
const TimestampedEntity = intersection(Entity, Timestamped);
TimestampedEntity.parse({
id: "550e8400-e29b-41d4-a716-446655440000",
type: "user",
createdAt: "2024-01-01T00:00:00Z",
updatedAt: "2024-06-01T00:00:00Z",
});
```
## API Reference
### `intersection(a: AnySchema, b: AnySchema): IntersectionSchema`
Factory accepting two schemas to intersect.
### `_parse(input, ctx): unknown | INVALID`
Both schemas parse with independent child contexts. If either fails, all issues are pushed to `ctx.issues`. If both succeed and both results are non-null objects, they are merged via `Object.assign`.
## Implementation Notes
- Both arms always run (not short-circuit), ensuring all issues are collected even when the first arm fails.
- `Object.assign` is a shallow merge; deep intersection of nested objects is not performed — callers should compose nested `intersection()` calls explicitly.
- This differs from Zod's `z.intersection()` in that it does not attempt deep merging of arrays or complex types.
### Parse Behaviour
`z.intersection(a, b)` runs both schemas against the input and merges the results; either failure fails the whole schema. Object schemas merge shallowly. For deep merges, prefer `a.merge(b)` on object schemas directly — `intersection` exists for the rarer case where the operands are not both plain objects (e.g. object + record, union + discriminated-union, etc.).

View File

@@ -0,0 +1,65 @@
# LiteralSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`LiteralSchema<T>` validates that an input is strictly equal (`===`) to a specific primitive value. It supports strings, numbers, booleans, `null`, and `undefined` as literal values. This is the foundation for discriminated union patterns and exhaustive type guards.
## Features
- Strict equality check (`===`) against the literal value
- Supports `string | number | boolean | null | undefined` as literal types
- Returns `VALIDATION.LITERAL.MISMATCH` on failure
## Usage: Basic Example
```typescript
import { literal } from "@suaveplan/validation";
const ok = literal("ok");
ok.parse("ok"); // "ok"
ok.parse("error"); // throws ValidationError
```
## Usage: Advanced Example
```typescript
import { union, literal, object, string } from "@suaveplan/validation";
// Discriminated union pattern
const SuccessResponse = object({ type: literal("success"), data: string() });
const ErrorResponse = object({ type: literal("error"), message: string() });
const Response = union([SuccessResponse, ErrorResponse]);
Response.parse({ type: "success", data: "ok" });
Response.parse({ type: "error", message: "not found" });
```
## API Reference
### `literal<T extends string | number | boolean | null | undefined>(value: T): LiteralSchema<T>`
Factory accepting the literal value to match.
### `_parse(input, ctx): T | INVALID`
Uses `===` comparison. Returns `VALIDATION.LITERAL.MISMATCH` on failure with a message showing both expected and received values (via `JSON.stringify`).
## Implementation Notes
- `JSON.stringify` is used in error messages to safely represent all primitive types including `undefined` (shown as `undefined` string) and `null`.
- Literal schemas are particularly efficient: the `_parse` body is a single equality check with no iteration.
### Parse Behaviour
`z.literal("ready")` accepts only the exact value `"ready"` and infers the literal type `"ready"` rather than the widened type `string`. Literal unions are written as `z.union([z.literal("a"), z.literal("b")])` or the sugar `z.enum(["a", "b"])` — the enum form is marginally faster because it compiles to a single `Set` membership check.
## Further Usage: Basic Example
```ts
import z from "@suaveplan/validation";
const ready = z.literal("ready");
ready.parse("ready"); // "ready"
```

View File

@@ -0,0 +1,67 @@
# NullableSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`NullableSchema<T>` wraps an inner schema so that `null` passes through without validation. Any other value is delegated to the inner schema. It mirrors the behavior of `OptionalSchema` but for `null` rather than `undefined`. Both can be composed: `optional(nullable(string()))` accepts `undefined | null | string`.
## Features
- Passes `null` through without calling the inner schema
- Delegates all other values to the inner schema
- Composable with `optional()` for full nullish handling
## Usage: Basic Example
```typescript
import { nullable, string } from "@suaveplan/validation";
const bio = nullable(string());
bio.parse(null); // null
bio.parse("hello"); // "hello"
bio.parse(42); // throws ValidationError
```
## Usage: Advanced Example
```typescript
import { object, string, nullable, optional } from "@suaveplan/validation";
const User = object({
name: string(),
deletedAt: nullable(string()), // null when not deleted
nickname: optional(nullable(string())), // undefined or null or string
});
```
## API Reference
### `nullable<T>(schema: BaseSchema<unknown, T>): NullableSchema<T>`
Factory wrapping an inner schema.
### `NullableSchema<T>`
Class extending `BaseSchema<T | null, T | null>`. The `_parse` method returns `null` immediately for `null` inputs.
## Implementation Notes
- `NullableSchema` is also defined in the core `schema.ts` module and re-exported here for convenience.
- Only `null` bypasses the inner schema; `undefined` is not treated as null (use `optional()` for that).
### Parse Behaviour
`schema.nullable()` wraps any schema so `null` is accepted in addition to the underlying type. The inferred output type is `T | null`. Combine with `optional()` (either order) for `T | null | undefined`. Nullable schemas round-trip cleanly through JSON because `null` is a JSON primitive.
## Further Usage: Basic Example
```ts
import z from "@suaveplan/validation";
const s = z.string().nullable();
s.parse(null); // null
s.parse("hi"); // "hi"
```
Internally `.nullable()` is a thin wrapper that returns a new schema whose parse function short-circuits on a `null` input and delegates everything else to the inner schema. The inner schemas `refinements` and `transforms` run only on the non-null branch, so nullability is cleanly orthogonal to the rest of the schema pipeline.

View File

@@ -0,0 +1,70 @@
# NumberSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`NumberSchema` validates that an input is a JavaScript `number` and enforces numeric constraints. By default, NaN and Infinity are rejected (the `finite` check runs for all `NumberSchema` instances unless explicitly disabled). All constraint methods return new instances, preserving immutability across builder chains.
This schema covers the full range of server-side numeric validation: integer enforcement, range clamping, sign checks, and divisibility rules.
## Features
- Type check: rejects non-numbers with `VALIDATION.INVALID_TYPE`
- Finite check by default: rejects NaN and Infinity with `VALIDATION.NUMBER.NOT_FINITE`
- `min(n)` / `max(n)` — inclusive range
- `int()` — integer-only (via `Number.isInteger`)
- `positive()` — must be > 0
- `negative()` — must be < 0
- `multipleOf(n)` — divisibility check
- `finite()` — explicit finite constraint (same as default)
## Usage: Basic Example
```typescript
import { number } from "@suaveplan/validation";
const age = number().int().min(0).max(150);
const result = age.safeParse(25);
// { success: true, data: 25 }
```
## Usage: Advanced Example
```typescript
import { number } from "@suaveplan/validation";
// Pagination offset: non-negative integer, multiple of 10
const offset = number().int().min(0).multipleOf(10);
// Latitude: float in range
const latitude = number().min(-90).max(90);
// Positive price with two-decimal precision (via multipleOf)
const cents = number().int().positive();
```
## API Reference
### `number(): NumberSchema`
Factory returning a `NumberSchema` with default finite enforcement.
### Constraint methods (all return new `NumberSchema`)
| Method | Error code | Description |
|--------|-----------|-------------|
| `min(n)` | `NUMBER.TOO_SMALL` | Value must be ≥ n |
| `max(n)` | `NUMBER.TOO_LARGE` | Value must be ≤ n |
| `int()` | `NUMBER.NOT_INT` | Must be integer |
| `positive()` | `NUMBER.TOO_SMALL` | Must be > 0 |
| `negative()` | `NUMBER.TOO_LARGE` | Must be < 0 |
| `multipleOf(n)` | `NUMBER.NOT_MULTIPLE` | Must satisfy `value % n === 0` |
| `finite()` | `NUMBER.NOT_FINITE` | Must be finite (explicit) |
## Implementation Notes
- The finite check runs first on every `NumberSchema` unless the `finite` constraint property is explicitly `false`. This prevents NaN from silently passing other checks.
- `positive()` and `negative()` use strict inequality (0 is neither positive nor negative).
- `multipleOf` uses the JavaScript modulo operator, which preserves sign for negative dividends — callers should be aware when combining with `negative()`.

View File

@@ -0,0 +1,77 @@
# ObjectSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`ObjectSchema<S>` validates plain JavaScript objects against a named-key shape. Each key in the shape is validated against its corresponding schema, and errors carry full dot-path information. Three modes control unknown key handling: `strip` (default, silently removes extras), `strict` (rejects extras), and `passthrough` (copies extras to output). Rich shape-manipulation builders — `pick`, `omit`, `partial`, `extend`, `merge` — make it easy to derive new schemas from existing ones without duplication.
All builder methods return new `ObjectSchema` instances; the original is never modified.
## Features
- Type check: rejects non-objects, null, and arrays with `VALIDATION.INVALID_TYPE`
- Field validation with per-field error paths
- Three unknown-key modes: `strip`, `strict`, `passthrough`
- `pick(keys)` — project to a subset of keys
- `omit(keys)` — exclude keys
- `partial()` — make all fields optional
- `required()` — return schema as-is (identity, for symmetry)
- `extend(shape)` — add/override fields
- `merge(other)` — combine with another `ObjectSchema`
## Usage: Basic Example
```typescript
import { object, string, number } from "@suaveplan/validation";
const User = object({ name: string(), age: number() });
const user = User.parse({ name: "Alice", age: 30 });
// { name: "Alice", age: 30 }
```
## Usage: Advanced Example
```typescript
import { object, string, number } from "@suaveplan/validation";
const BaseUser = object({ id: string().uuid(), name: string() });
const AdminUser = BaseUser.extend({ role: string(), level: number().int() });
const PartialUpdate = AdminUser.partial().omit(["id"]);
const update = PartialUpdate.parse({ name: "Bob" });
// { name: "Bob" } — all fields optional, id omitted
const strict = AdminUser.strict();
strict.parse({ id: "...", name: "Alice", role: "admin", level: 1, extra: true });
// throws: Unrecognized key(s): extra
```
## API Reference
### `object<S extends ObjectShape>(shape: S): ObjectSchema<S>`
Factory with default `strip` mode.
### Mode methods (return new `ObjectSchema<S>`)
- `strict()` — reject unknown keys with `VALIDATION.OBJECT.UNRECOGNIZED_KEYS`
- `passthrough()` — copy unknown keys to output
- `strip()` — silently drop unknown keys (default)
### Shape manipulation methods
- `pick<K>(keys: K[]): ObjectSchema<Pick<S, K>>` — subset of keys
- `omit<K>(keys: K[]): ObjectSchema<Omit<S, K>>` — exclude keys
- `partial(): ObjectSchema<...>` — wrap all values in `OptionalSchema`
- `required(): ObjectSchema<S>` — identity (for symmetry with `partial`)
- `extend<E>(shape: E): ObjectSchema<S & E>` — add/override fields
- `merge<E>(other: ObjectSchema<E>): ObjectSchema<S & E>` — merge shapes
## Implementation Notes
- Field errors are reported under the field's key path. Nested object errors propagate the full path chain.
- `partial()` wraps each field schema with `.optional()`, so the output type has all values as `T | undefined`.
- `passthrough` copies unknown keys before iterating known ones, then known-key parsing runs normally on top.
- `strict` checks for unknown keys using a `Set` of shape keys — O(n) where n = number of input keys.

View File

@@ -0,0 +1,68 @@
# OptionalSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`OptionalSchema<T>` wraps an inner schema so that `undefined` passes through without validation. Any other value is delegated to the inner schema. It is the canonical way to mark fields as optional in object shapes and as the return type of `.optional()` on any `BaseSchema`.
## Features
- Passes `undefined` through without calling the inner schema
- Delegates all other values to the inner schema
- Composable: `optional(nullable(string()))` is valid
## Usage: Basic Example
```typescript
import { optional, string } from "@suaveplan/validation";
const nickname = optional(string().min(2));
nickname.parse(undefined); // undefined
nickname.parse("Al"); // "Al"
nickname.parse(42); // throws ValidationError
```
## Usage: Advanced Example
```typescript
import { object, string, number, optional } from "@suaveplan/validation";
const ProfileUpdate = object({
displayName: optional(string().min(1).max(50)),
bio: optional(string().max(500)),
age: optional(number().int().min(13)),
});
ProfileUpdate.parse({}); // all optional, empty object valid
```
## API Reference
### `optional<T>(schema: BaseSchema<unknown, T>): OptionalSchema<T>`
Factory wrapping an inner schema.
### `OptionalSchema<T>`
Class extending `BaseSchema<T | undefined, T | undefined>`. The `_parse` method returns `undefined` immediately for `undefined` inputs.
## Implementation Notes
- `OptionalSchema` is also defined in the core `schema.ts` module and re-exported here for convenience.
- `undefined` is the only value that bypasses the inner schema; `null` is not treated as absent (use `nullable()` for that).
### Parse Behaviour
`schema.optional()` wraps any schema so `undefined` is accepted in addition to the underlying type. The inferred output type is `T | undefined`. Object schemas treat missing keys as `undefined`, so `z.object({ x: z.string().optional() }).parse({})` is valid.
## Further Usage: Basic Example
```ts
import z from "@suaveplan/validation";
const s = z.string().optional();
s.parse(undefined); // undefined
```
Internally `.optional()` is a thin wrapper that short-circuits on `undefined` and delegates everything else to the inner schema. Optionality composes cleanly with nullability, defaults, and refinements — the inner transforms only see defined, non-null inputs so their implementations stay simple.

View File

@@ -0,0 +1,56 @@
# RecordSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`RecordSchema<T>` validates that an input is a plain object where every value satisfies a given schema. Unlike `ObjectSchema`, which checks a fixed set of named keys, `RecordSchema` iterates all present keys and validates each value dynamically — making it ideal for dictionaries, maps, and index-signature types.
## Features
- Type check: rejects non-objects, null, and arrays with `VALIDATION.INVALID_TYPE`
- Value validation: every value parsed against the element schema
- All value errors collected with key-based paths
## Usage: Basic Example
```typescript
import { record, number } from "@suaveplan/validation";
const scores = record(number());
scores.parse({ alice: 95, bob: 88 }); // { alice: 95, bob: 88 }
scores.parse({ alice: "high" }); // throws ValidationError
```
## Usage: Advanced Example
```typescript
import { record, object, string, number } from "@suaveplan/validation";
// Indexed config map
const config = record(object({ value: string(), ttl: number().int() }));
config.parse({
featureA: { value: "enabled", ttl: 3600 },
featureB: { value: "disabled", ttl: 0 },
});
```
## API Reference
### `record<T>(valueSchema: BaseSchema<unknown, T>): RecordSchema<T>`
Factory accepting the value schema.
### `_parse(input, ctx): Record<string, T> | INVALID`
Validates `typeof input === "object"`, not null, not array, then iterates `Object.keys(input)` and parses each value. All errors collected; returns `INVALID` if any fail.
## Implementation Notes
- `record` does not validate keys (keys are always strings in JS objects). Key validation would require a `RecordSchema<K, V>` variant — not currently implemented.
- Error paths use the key string as a path segment: `[...parentPath, key]`.
### Parse Behaviour
`z.record(keySchema, valueSchema)` validates every key/value pair in the input object against the supplied schemas. Unknown keys are preserved. Pair with `z.string()` / `z.enum([...])` as the key schema to narrow the allowed key set. `strict()` / `strip()` / `passthrough()` are not applicable — records are inherently open-shape.

View File

@@ -0,0 +1,90 @@
# StringSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`StringSchema` validates that an input is a JavaScript `string` and optionally enforces structural constraints: minimum/maximum length, exact length, regex pattern, email format, URL format, UUID format, prefix/suffix/substring checks. It also supports output transforms (trim, toLowerCase, toUpperCase) applied before constraint evaluation. Every builder method returns a new instance, making the API fully immutable.
This schema is the most commonly used primitive in form and API validation. All constraints are evaluated in a single `_parse` call with no heap allocation beyond the output string, making it suitable for high-frequency validation paths.
## Features
- Type check: rejects non-string with `VALIDATION.INVALID_TYPE`
- `min(n)` / `max(n)` / `length(n)` — length constraints
- `regex(re)` — arbitrary pattern matching
- `email()` — basic RFC-style email check (`user@domain.tld`)
- `url()` — requires `http://` or `https://` prefix
- `uuid()` — 36-character UUID v4-style regex
- `startsWith(prefix)` / `endsWith(suffix)` / `includes(substr)` — substring checks
- `trim()` / `toLowerCase()` / `toUpperCase()` — output transforms
## Usage: Basic Example
```typescript
import { string } from "@suaveplan/validation";
const username = string().min(3).max(20);
const result = username.safeParse("alice");
// { success: true, data: "alice" }
```
## Usage: Advanced Example
```typescript
import { string } from "@suaveplan/validation";
// Email with domain block refinement
const email = string()
.email()
.toLowerCase()
.trim()
.refine((v) => !v.endsWith("@spam.com"), "Spam domains not allowed")
.describe("User contact email");
// UUID validator
const id = string().uuid().describe("Resource identifier");
// URL with path check
const webhookUrl = string()
.url()
.startsWith("https://")
.includes("/webhook");
```
## API Reference
### `string(): StringSchema`
Factory returning an unconstrained `StringSchema`.
### Constraint methods (all return new `StringSchema`)
| Method | Error code | Description |
|--------|-----------|-------------|
| `min(n)` | `STRING.TOO_SHORT` | Length must be ≥ n |
| `max(n)` | `STRING.TOO_LONG` | Length must be ≤ n |
| `length(n)` | `STRING.INVALID_FORMAT` | Length must be exactly n |
| `regex(re)` | `STRING.INVALID_FORMAT` | Must match regex |
| `email()` | `STRING.INVALID_FORMAT` | Must be valid email |
| `url()` | `STRING.INVALID_FORMAT` | Must start with `http://` or `https://` |
| `uuid()` | `STRING.INVALID_FORMAT` | Must be valid UUID |
| `startsWith(p)` | `STRING.INVALID_FORMAT` | Must begin with prefix p |
| `endsWith(s)` | `STRING.INVALID_FORMAT` | Must end with suffix s |
| `includes(sub)` | `STRING.INVALID_FORMAT` | Must contain substring |
### Transform methods (all return new `StringSchema`)
| Method | Effect |
|--------|--------|
| `trim()` | `String.prototype.trim()` before checks |
| `toLowerCase()` | Converts to lowercase before checks |
| `toUpperCase()` | Converts to uppercase before checks |
## Implementation Notes
- Transforms are applied before constraint checks. This means `string().trim().min(3)` validates the trimmed string.
- The email regex (`/^[^\s@]+@[^\s@]+\.[^\s@]+$/`) is intentionally simple — RFC 5321 compliance is not the goal; rejecting obviously malformed inputs is.
- `uuid()` uses a case-insensitive regex matching the standard 8-4-4-4-12 hex format.
- All builder methods create new `StringSchema` instances via spread of `_constraints`, so chaining is safe across branches.

View File

@@ -0,0 +1,55 @@
# TupleSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`TupleSchema<T>` validates fixed-length arrays where each positional element has its own schema. Unlike `ArraySchema` (homogeneous elements), `TupleSchema` is heterogeneous: position 0 can be a `string`, position 1 a `number`, etc. It strictly enforces both the array type and the exact length.
## Features
- Type check: rejects non-arrays with `VALIDATION.INVALID_TYPE`
- Length check: rejects arrays with wrong length with `VALIDATION.INVALID_TYPE`
- Per-position schema validation with positional error paths
## Usage: Basic Example
```typescript
import { tuple, string, number } from "@suaveplan/validation";
const coord = tuple([number(), number()]);
coord.parse([48.8566, 2.3522]); // [48.8566, 2.3522]
coord.parse([48.8566]); // throws (wrong length)
```
## Usage: Advanced Example
```typescript
import { tuple, string, number, boolean } from "@suaveplan/validation";
// CSV row parser: [id, name, score, active]
const row = tuple([string().uuid(), string().min(1), number(), boolean()]);
const result = row.safeParse(["550e8400-e29b-41d4-a716-446655440000", "Alice", 99, true]);
```
## API Reference
### `tuple<T extends AnySchema[]>(schemas: T): TupleSchema<T>`
Factory accepting an ordered array of schemas.
### `_parse(input, ctx): TupleOutputs<T> | INVALID`
Validates input is an array of the exact expected length, then parses each element against its positional schema.
## Implementation Notes
- Length mismatch is reported as `VALIDATION.INVALID_TYPE` (no dedicated `TUPLE` code) with a descriptive message including both expected and actual lengths.
- Error paths include the element index: `[...parentPath, i]`.
- Empty tuples `tuple([])` are valid and accept only `[]`.
### Parse Behaviour
`z.tuple([a, b, c])` validates fixed-length arrays where each position has a distinct schema. Extra trailing items are rejected unless you add a rest element with `.rest(schema)`. Inferred output is a typed tuple (`[A, B, C]`) rather than an array, so downstream destructuring stays fully typed.

View File

@@ -0,0 +1,60 @@
# UnionSchema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`UnionSchema` validates that an input satisfies at least one of a list of member schemas. It short-circuits on the first successful parse, returning that result without checking remaining members. When all members fail, it aggregates all their issues into the `ValidationError`, giving the caller visibility into why each arm failed.
## Features
- Short-circuit success: first matching schema wins
- All-failure aggregation: every arm's issues collected into one `ValidationError`
- Empty union: always fails with `VALIDATION.UNION.NO_MATCH`
## Usage: Basic Example
```typescript
import { union, string, number } from "@suaveplan/validation";
const stringOrNumber = union([string(), number()]);
stringOrNumber.parse("hello"); // "hello"
stringOrNumber.parse(42); // 42
stringOrNumber.parse(true); // throws ValidationError
```
## Usage: Advanced Example
```typescript
import { union, literal, object, string } from "@suaveplan/validation";
// Discriminated union
const Event = union([
object({ type: literal("created"), id: string() }),
object({ type: literal("deleted"), id: string(), reason: string() }),
]);
Event.parse({ type: "created", id: "abc" });
// { type: "created", id: "abc" }
```
## API Reference
### `union(schemas: AnySchema[]): UnionSchema`
Factory accepting an array of member schemas.
### `_parse(input, ctx): unknown | INVALID`
Iterates schemas in order. Returns the first non-`INVALID` result. On all-failure, pushes all collected issues to `ctx.issues` and returns `INVALID`. Empty union always adds a single `VALIDATION.UNION.NO_MATCH` issue.
## Implementation Notes
- Each arm runs with its own `childCtx` so its issues don't contaminate the parent context until the all-failure path is confirmed.
- The order of schemas matters: place more specific schemas first to avoid false positives from permissive schemas like `string()`.
- The aggregated issue list can be long for wide unions; callers may want to use `flatten()` to extract meaningful field-level errors.
### Parse Behaviour
`z.union([a, b, c])` tries each member in order and returns the first success; all failures are aggregated into a `ZodUnionError` with per-member diagnostics so callers can show a useful error. For discriminated unions (a shared literal key across members), prefer `z.discriminatedUnion("kind", [...])` which is faster and produces cleaner errors.