# AkiVal

### <mark style="color:red;">Installation</mark>

```bash
pnpm add @akinon/akival
```

### <mark style="color:red;">Basic Usage</mark>

```tsx
import { object, string, number, type InferType } from '@akinon/akival';

// Define a schema
const productSchema = object({
  name: string().required(),
  price: number().positive().required(),
  description: string().optional()
});

// Infer TypeScript type from schema
type Product = InferType<typeof productSchema>;

// Validate data
const validateProduct = async (data: unknown) => {
  try {
    const product = await productSchema.validate(data);
    return { success: true, data: product };
  } catch (error) {
    return { success: false, error };
  }
};
```

### <mark style="color:red;">API Reference</mark>

Akival re-exports all Yup utilities. Below are the most commonly used exports.

#### Schema Builders

| Export                 | Description                |
| ---------------------- | -------------------------- |
| `string()`             | String schema              |
| `number()`             | Number schema              |
| `boolean()` / `bool()` | Boolean schema             |
| `date()`               | Date schema                |
| `array()`              | Array schema               |
| `object()`             | Object schema              |
| `mixed()`              | Mixed/any type schema      |
| `lazy()`               | Lazy evaluation schema     |
| `ref()`                | Reference to another field |

#### Types

| Export            | Description                       |
| ----------------- | --------------------------------- |
| `InferType<T>`    | Infer TypeScript type from schema |
| `AnySchema`       | Any schema type                   |
| `AnyObjectSchema` | Any object schema type            |
| `ISchema`         | Schema interface                  |
| `LocaleObject`    | Locale configuration type         |

#### Schema Classes

| Export          | Description          |
| --------------- | -------------------- |
| `Schema`        | Base schema class    |
| `StringSchema`  | String schema class  |
| `NumberSchema`  | Number schema class  |
| `BooleanSchema` | Boolean schema class |
| `DateSchema`    | Date schema class    |
| `ArraySchema`   | Array schema class   |
| `ObjectSchema`  | Object schema class  |
| `MixedSchema`   | Mixed schema class   |

#### Utilities

| Export        | Description                           |
| ------------- | ------------------------------------- |
| `setLocale()` | Set global validation messages        |
| `akival`      | Full Yup namespace for advanced usage |

***

### <mark style="color:red;">Parsing and Transforms</mark>

Schemas can parse and transform input values. Use `cast()` to coerce values without validation:

```tsx
import { number, object, string } from '@akinon/akival';

// Coerce string to number
const num = number().cast('1'); // 1

// Transform with object schema
const userSchema = object({
  firstName: string().lowercase().trim()
});

userSchema.cast({ firstName: '  JOHN  ' }); // { firstName: 'john' }
```

#### Custom Transforms

```tsx
import { string } from '@akinon/akival';

const reversedString = string()
  .transform((currentValue) => currentValue.split('').reverse().join(''))
  .cast('dlrow olleh'); // "hello world"
```

***

### <mark style="color:red;">Custom Tests</mark>

Create custom validation tests for complex logic:

```tsx
import { string } from '@akinon/akival';

const skuSchema = string().test({
  name: 'is-valid-sku',
  skipAbsent: true,
  test(value, ctx) {
    if (!value.startsWith('s-')) {
      return ctx.createError({ message: 'SKU missing correct prefix' });
    }
    if (!value.endsWith('-42a')) {
      return ctx.createError({ message: 'SKU missing correct suffix' });
    }
    if (value.length < 10) {
      return ctx.createError({ message: 'SKU is not the right length' });
    }
    return true;
  }
});
```

***

### <mark style="color:red;">Schema Immutability</mark>

Schemas are immutable. Each method call returns a new schema object:

```tsx
import { string } from '@akinon/akival';

const optionalString = string().optional();
const definedString = optionalString.defined();

const value = undefined;
optionalString.isValid(value); // true
definedString.isValid(value); // false
```

This allows safe reuse and composition of schemas.

***

### <mark style="color:red;">String Validation</mark>

```tsx
import { string } from '@akinon/akival';

const schema = string()
  .required('Name is required')
  .min(2, 'Minimum 2 characters')
  .max(100, 'Maximum 100 characters')
  .email('Invalid email format')
  .url('Invalid URL')
  .matches(/^[a-z]+$/, 'Only lowercase letters');
```

#### Common String Methods

| Method                     | Description            |
| -------------------------- | ---------------------- |
| `required(message?)`       | Field is required      |
| `min(limit, message?)`     | Minimum length         |
| `max(limit, message?)`     | Maximum length         |
| `email(message?)`          | Valid email format     |
| `url(message?)`            | Valid URL format       |
| `matches(regex, message?)` | Match regex pattern    |
| `trim()`                   | Trim whitespace        |
| `lowercase()`              | Transform to lowercase |
| `uppercase()`              | Transform to uppercase |
| `nullable()`               | Allow null             |
| `optional()`               | Field is optional      |

***

### <mark style="color:red;">Number Validation</mark>

```tsx
import { number } from '@akinon/akival';

const schema = number()
  .required('Price is required')
  .positive('Must be positive')
  .min(0, 'Minimum is 0')
  .max(1000000, 'Maximum is 1,000,000')
  .integer('Must be a whole number');
```

#### Common Number Methods

| Method                      | Description        |
| --------------------------- | ------------------ |
| `required(message?)`        | Field is required  |
| `min(limit, message?)`      | Minimum value      |
| `max(limit, message?)`      | Maximum value      |
| `positive(message?)`        | Must be positive   |
| `negative(message?)`        | Must be negative   |
| `integer(message?)`         | Must be an integer |
| `moreThan(value, message?)` | Greater than value |
| `lessThan(value, message?)` | Less than value    |

***

### <mark style="color:red;">Object Validation</mark>

```tsx
import { object, string, number, array } from '@akinon/akival';

const orderSchema = object({
  customer: object({
    name: string().required(),
    email: string().email().required()
  }),
  items: array().of(
    object({
      productId: number().required(),
      quantity: number().positive().integer().required()
    })
  ).min(1, 'At least one item required'),
  notes: string().optional()
});
```

***

### <mark style="color:red;">Array Validation</mark>

```tsx
import { array, string, number } from '@akinon/akival';

// Array of strings
const tagsSchema = array()
  .of(string().required())
  .min(1, 'At least one tag required')
  .max(10, 'Maximum 10 tags');

// Array of numbers
const scoresSchema = array()
  .of(number().min(0).max(100))
  .required();
```

***

### <mark style="color:red;">Conditional Validation</mark>

```tsx
import { object, string, number } from '@akinon/akival';

const paymentSchema = object({
  type: string().oneOf(['credit', 'debit', 'bank']).required(),
  cardNumber: string().when('type', {
    is: (type: string) => type === 'credit' || type === 'debit',
    then: (schema) => schema.required('Card number is required'),
    otherwise: (schema) => schema.optional()
  }),
  bankAccount: string().when('type', {
    is: 'bank',
    then: (schema) => schema.required('Bank account is required'),
    otherwise: (schema) => schema.optional()
  })
});
```

***

### <mark style="color:red;">Cross-Field Validation</mark>

```tsx
import { object, string, ref } from '@akinon/akival';

const passwordSchema = object({
  password: string()
    .min(8, 'Minimum 8 characters')
    .required('Password is required'),
  confirmPassword: string()
    .oneOf([ref('password')], 'Passwords must match')
    .required('Please confirm your password')
});
```

***

### <mark style="color:red;">Custom Validation Messages</mark>

#### Per-Field Messages

```tsx
import { string } from '@akinon/akival';

const schema = string()
  .required('This field cannot be empty')
  .min(3, 'Please enter at least 3 characters')
  .email('Please enter a valid email address');
```

#### Global Locale

```tsx
import { setLocale } from '@akinon/akival';

setLocale({
  mixed: {
    required: 'This field is required',
    notType: 'Invalid value'
  },
  string: {
    min: 'Must be at least ${min} characters',
    max: 'Must be at most ${max} characters',
    email: 'Must be a valid email'
  },
  number: {
    min: 'Must be at least ${min}',
    max: 'Must be at most ${max}',
    positive: 'Must be a positive number'
  }
});
```

#### With i18n Integration

Use `setLocale` with your translation function for dynamic messages:

```tsx
import { setLocale } from '@akinon/akival';

import { t } from './locale'; // Your Akilocale instance

setLocale({
  mixed: {
    required: ({ path }) => t('validation.required', { field: t(path) })
  },
  string: {
    min: ({ path, min }) => t('validation.minLength', { field: t(path), min })
  }
});
```

***

### <mark style="color:red;">Integration with Akiform</mark>

Use Akival schemas with Akiform for form validation. The `akivalResolver` is exported from `@akinon/akiform`:

```tsx
import { object, string, number } from '@akinon/akival';
import { Akiform, FormItem, useForm, akivalResolver } from '@akinon/akiform';
import { Input } from '@akinon/ui-input';
import { InputNumber } from '@akinon/ui-input-number';

const schema = object({
  name: string().required('Name is required'),
  price: number().positive('Must be positive').required('Price is required')
});

const ProductForm = () => {
  const form = useForm({
    resolver: akivalResolver(schema)
  });

  return (
    <Akiform form={form} onFinish={handleSubmit}>
      <FormItem name="name" label="Name">
        <Input />
      </FormItem>
      <FormItem name="price" label="Price">
        <InputNumber />
      </FormItem>
    </Akiform>
  );
};
```

***

### <mark style="color:red;">Type Inference</mark>

```tsx
import { object, string, number, array, type InferType } from '@akinon/akival';

const userSchema = object({
  id: number().required(),
  name: string().required(),
  email: string().email().required(),
  roles: array().of(string()).required()
});

// Automatically infer the type
type User = InferType<typeof userSchema>;
// Result: { id: number; name: string; email: string; roles: string[] }
```

***

### <mark style="color:red;">Async Validation</mark>

```tsx
import { string } from '@akinon/akival';

const usernameSchema = string()
  .required()
  .test('unique', 'Username is already taken', async (value) => {
    if (!value) return true;
    const response = await api.checkUsername(value);
    return response.available;
  });
```

***

### <mark style="color:red;">Transform Values</mark>

```tsx
import { string, number } from '@akinon/akival';

// Trim and lowercase
const emailSchema = string()
  .trim()
  .lowercase()
  .email();

// Parse string to number
const priceSchema = number()
  .transform((value, originalValue) => {
    return originalValue === '' ? undefined : value;
  })
  .positive();
```

***

### <mark style="color:red;">Best Practices</mark>

1. **Infer types from schemas** - Use `InferType` to keep types in sync with validation
2. **Use with Akiform** - Combine with `yupResolver` for form validation
3. **Set global locale** - Configure `setLocale` for consistent error messages across the app
4. **Keep schemas in separate files** - Export schemas from dedicated files for reusability
5. **Use conditional validation** - Leverage `.when()` for dynamic field requirements

### <mark style="color:red;">Related</mark>

* [Akiform](/akinon-ui/ui-kit/components/akiform.md) - Form library with validation support
* [Akiform Builder](/akinon-ui/ui-kit/components/akiform-builder.md) - Schema-driven form builder

### <mark style="color:red;">More Information</mark>

For complete Yup documentation, visit [Yup GitHub](https://github.com/jquense/yup).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.akinon.com/akinon-ui/ui-kit/utilities/akival.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
