@nexus-state/form-schema-dsl
v0.2.0
Published
Custom DSL schema validator for Nexus State forms
Maintainers
Readme
@nexus-state/form-schema-dsl
Custom DSL schema validator for Nexus State forms
🎯 When to Use DSL
✅ Choose DSL if you need:
| Use Case | Why DSL | |----------|---------| | Simple syntax | Easy to read and write | | Composable rules | Combine validators easily | | Async support | Debouncing, retry, cache built-in | | Lightweight | Minimal bundle size |
❌ Use alternatives if:
| Use Case | Better Alternative | |----------|-------------------| | Rich ecosystem | @nexus-state/form-schema-zod | | Mature library | @nexus-state/form-schema-yup | | JSON Schema standard | @nexus-state/form-schema-ajv |
🎯 Overview
DSL plugin provides a custom Domain-Specific Language for form validation in Nexus State. It offers a simple, declarative way to define validation rules without the complexity of full schema libraries.
Why DSL?
- ✅ Simple syntax — easy to read and write
- ✅ Composable — combine rules easily
- ✅ Async support — debouncing, retry, cache built-in
- ✅ Lightweight — minimal bundle size
- ✅ Extensible — create custom validators
📦 Installation
npm install @nexus-state/form-schema-dslPeer dependencies:
@nexus-state/form— core form package
🚀 Quick Start
Basic Form with DSL
import { createForm } from '@nexus-state/form';
import { createStore } from '@nexus-state/core';
import { dslPlugin, required, minLength, email } from '@nexus-state/form-schema-dsl';
const store = createStore();
// Define schema
const loginSchema = {
email: [required, email],
password: [required, minLength(8)],
};
// Create form with DSL plugin
const loginForm = createForm(store, {
schemaPlugin: dslPlugin,
schemaConfig: loginSchema,
initialValues: {
email: '',
password: '',
},
});
// Validate form
const isValid = await loginForm.validate();
if (isValid) {
console.log('Form is valid!');
} else {
console.log('Errors:', loginForm.errors);
}📖 API Reference
DSL Schema
interface DSLSchema<TValues = Record<string, unknown>> {
[fieldName: string]: DSLRule | DSLRule[];
}
interface DSLRule<TValue = any> {
validate: (value: TValue, allValues: Record<string, unknown>) => string | null | Promise<string | null>;
code?: string;
message?: string;
options?: {
debounce?: number;
retry?: number;
timeout?: number;
};
}Schema Configuration
interface DSLFormOptions {
schemaType: 'dsl';
schemaConfig: DSLSchema;
initialValues: Record<string, unknown>;
defaultValidationMode?: 'onChange' | 'onBlur' | 'onSubmit';
showErrorsOnTouched?: boolean;
}📝 Examples
1. Registration Form
import {
required,
minLength,
maxLength,
email,
pattern,
} from '@nexus-state/form-schema-dsl';
const registrationSchema = {
username: [required, minLength(3), maxLength(20), pattern(/^[a-zA-Z0-9_]+$/)],
email: [required, email],
password: [required, minLength(8)],
age: [required, minValue(18), maxValue(120)],
terms: [required, equalTo(true)],
};2. Async Validation (Uniqueness Check)
import { required, minLength, email, unique } from '@nexus-state/form-schema-dsl';
const registrationSchema = {
username: [
required,
minLength(3),
unique('users', 'username', {
debounce: 500, // Wait 500ms after typing
retry: 2, // Retry on network error
timeout: 5000, // 5 second timeout
}),
],
email: [
required,
email,
unique('users', 'email', {
debounce: 500,
}),
],
};3. Custom Validators
import { custom } from '@nexus-state/form-schema-dsl';
const strongPassword = custom(
(value) => {
if (!/[A-Z]/.test(value)) return 'Need uppercase letter';
if (!/[a-z]/.test(value)) return 'Need lowercase letter';
if (!/[0-9]/.test(value)) return 'Need number';
if (!/[!@#$%^&*]/.test(value)) return 'Need special character';
return null;
},
'strong_password'
);
const schema = {
password: [required, minLength(8), strongPassword],
};4. Cross-field Validation
import { required, minLength, equalTo } from '@nexus-state/form-schema-dsl';
const passwordSchema = {
password: [required, minLength(8)],
confirmPassword: [required, equalTo('password')], // Must match 'password' field
};5. Conditional Validation
import { required, conditional, matchesField } from '@nexus-state/form-schema-dsl';
const schema = {
needsShipping: [],
shippingAddress: [
conditional({
field: 'needsShipping',
operator: 'equals',
value: true,
then: [required],
}),
],
};6. Phone Number Validation
import { required, phone } from '@nexus-state/form-schema-dsl';
const schema = {
phone: [required, phone('US')], // US phone format
};
// Or with custom pattern
import { pattern } from '@nexus-state/form-schema-dsl';
const schema = {
phone: [required, pattern(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number')],
};7. URL Validation
import { required, url } from '@nexus-state/form-schema-dsl';
const schema = {
website: [url], // Optional URL
portfolio: [required, url('https')], // Required HTTPS URL
};8. Credit Card Validation
import { required, creditCard } from '@nexus-state/form-schema-dsl';
const schema = {
cardNumber: [required, creditCard],
};9. Array Length Validation
import { required, arrayLength } from '@nexus-state/form-schema-dsl';
const schema = {
skills: [required, arrayLength(1, 10)], // 1-10 skills
};10. One Of / Not One Of
import { oneOf, notOneOf } from '@nexus-state/form-schema-dsl';
const schema = {
role: [oneOf(['admin', 'user', 'guest'])],
status: [notOneOf(['banned', 'deleted'])],
};🔧 Built-in Validators
String Validators
| Validator | Description | Example |
|-----------|-------------|---------|
| required | Field is required | [required] |
| minLength(n) | Minimum string length | [minLength(3)] |
| maxLength(n) | Maximum string length | [maxLength(100)] |
| length(n) | Exact string length | [length(5)] |
| lengthRange(min, max) | Length range | [lengthRange(3, 20)] |
| pattern(regex, message?) | Regex pattern | [pattern(/^[a-z]+$/)] |
| email | Email format | [email] |
| url(protocol?) | URL format | [url('https')] |
| phone(country?) | Phone format | [phone('US')] |
Number Validators
| Validator | Description | Example |
|-----------|-------------|---------|
| minValue(n) | Minimum value | [minValue(18)] |
| maxValue(n) | Maximum value | [maxValue(120)] |
| valueRange(min, max) | Value range | [valueRange(1, 10)] |
| positive | Must be positive | [positive] |
| negative | Must be negative | [negative] |
| integer | Must be integer | [integer] |
Boolean Validators
| Validator | Description | Example |
|-----------|-------------|---------|
| equalTo(value) | Must equal value | [equalTo(true)] |
| notEqualTo(value) | Must not equal value | [notEqualTo(false)] |
Array Validators
| Validator | Description | Example |
|-----------|-------------|---------|
| arrayLength(min, max?) | Array length | [arrayLength(1, 10)] |
Selection Validators
| Validator | Description | Example |
|-----------|-------------|---------|
| oneOf(values) | Must be one of values | [oneOf(['a', 'b'])] |
| notOneOf(values) | Must not be any of values | [notOneOf(['x', 'y'])] |
Cross-field Validators
| Validator | Description | Example |
|-----------|-------------|---------|
| equalTo(field) | Must equal another field | [equalTo('password')] |
| matchesField(field) | Must match another field | [matchesField('password')] |
| conditional(config) | Conditional validation | See below |
Async Validators
| Validator | Description | Example |
|-----------|-------------|---------|
| unique(table, field, options?) | Check uniqueness | [unique('users', 'email')] |
| exists(table, field, options?) | Check existence | [exists('users', 'id')] |
| asyncCustom(fn, options?) | Custom async validator | See below |
🔧 Advanced Usage
Conditional Validation
import { conditional, required } from '@nexus-state/form-schema-dsl';
const schema = {
needsShipping: [],
shippingAddress: [
conditional({
field: 'needsShipping',
operator: 'equals',
value: true,
then: [required],
}),
],
};Custom Async Validator
import { asyncCustom } from '@nexus-state/form-schema-dsl';
const promoCodeValidator = asyncCustom(
async (value) => {
const response = await fetch(`/api/check-promo?code=${value}`);
const data = await response.json();
return data.valid ? null : 'Invalid promo code';
},
{
debounce: 500,
retry: 2,
timeout: 5000,
}
);
const schema = {
promoCode: [required, promoCodeValidator],
};Combining Validators
import { required, minLength, maxLength, pattern } from '@nexus-state/form-schema-dsl';
const schema = {
username: [
required,
minLength(3),
maxLength(20),
pattern(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores allowed'),
],
};Custom Error Messages
import { minLength } from '@nexus-state/form-schema-dsl';
const schema = {
password: [
minLength(8, 'Password must be at least 8 characters long'),
],
};⚠️ Troubleshooting
Issue: "validateField always returns null"
Explanation: DSL validators require all values for cross-field validation. The plugin's validateField method returns null and relies on full validate() for proper validation.
Solution: Use form.validate() instead of field-level validation.
Issue: Async validator not working
Solution: Ensure your validator returns a Promise:
// ✅ Correct
const validator = asyncCustom(async (value) => {
const result = await checkValue(value);
return result ? null : 'Error';
});
// ❌ Wrong
const validator = asyncCustom((value) => {
checkValue(value); // Missing return and async
});Issue: Conditional validation not triggering
Solution: Ensure the conditional field name matches exactly:
// ✅ Correct
conditional({
field: 'needsShipping', // Must match field name exactly
operator: 'equals',
value: true,
then: [required],
});
// ❌ Wrong
conditional({
field: 'needShipping', // Typo - doesn't match
// ...
});📚 Resources
🔗 See Also
- Core: @nexus-state/core — Foundation (atoms, stores)
- Forms:
- @nexus-state/form — Form management
- @nexus-state/form-schema-zod — Zod validation
- @nexus-state/form-schema-yup — Yup validation
- @nexus-state/form-schema-ajv — JSON Schema validation
- Framework integration:
- @nexus-state/react — React hooks
Full ecosystem: Nexus State Packages
📄 License
MIT © Nexus State Contributors
