lite-schema-check
Version:
A tiny, zero-dependency TypeScript validation library. Competitive alternative to Zod with clearer API, solving real pain points like circular validation, codecs, and mixed async/sync validation.
561 lines (418 loc) • 15.4 kB
Markdown
# lite-schema-check 🔥
**Zero-dependency TypeScript validation library. A competitive alternative to Zod.**
[](https://www.npmjs.com/package/lite-schema-check)
[](https://opensource.org/licenses/MIT)


## What's New in v2.1 🔥
**We implemented the TOP REQUESTED features from Zod's issue tracker:**
### New in v2.1 (Just Added!)
- ✅ **FormData validation** ([60+ votes](https://github.com/colinhacks/zod/discussions)) - Zod doesn't have it!
- ✅ **Partial validation** - Return valid data even with errors
- ✅ **JSON Schema export** - Better than zod-to-json-schema
- ✅ **Schema metadata extraction** - Auto-generate forms
- ✅ **XOR (exclusive OR)** - Cleaner than Zod's approach
- ✅ **File validation** - Size & MIME type checking
- ✅ **Schema descriptions** - Rich metadata support
### From v2.0 (Already Have It!)
- ✅ **Circular/recursive validation** ([Zod Issue #5346](https://github.com/colinhacks/zod/issues/5346)) - They don't have it yet
- ✅ **Clearer optional/nullable API** ([Zod Issue #5348](https://github.com/colinhacks/zod/issues/5348)) - Less confusing than Zod
- ✅ **Mixed sync/async validation** ([Zod Issue #5379](https://github.com/colinhacks/zod/issues/5379)) - Selective async support
- ✅ **Reversible transforms (Codecs)** ([Zod Issues #5374, #5377](https://github.com/colinhacks/zod/issues/5374)) - Encode AND decode
**That's 11 features Zod doesn't have!**
## Quick Comparison
| Feature | Zod | lite-schema-check v2.1 |
|---------|-----|----------------------|
| Basic validation | ✅ | ✅ |
| Nested objects | ✅ | ✅ |
| Optional/nullable | ⚠️ (confusing) | ✅ **Clearer** |
| **FormData validation** | ❌ | ✅ **We have it** 🔥 |
| **Partial validation** | ❌ | ✅ **We have it** 🔥 |
| **JSON Schema export** | ⚠️ (external) | ✅ **Built-in** 🔥 |
| **Schema metadata** | ⚠️ (hard) | ✅ **Easy** 🔥 |
| **XOR support** | ⚠️ (clunky) | ✅ **Clean** 🔥 |
| Circular validation | ❌ | ✅ **We have it** |
| Mixed async/sync | ❌ | ✅ **We have it** |
| Reversible transforms | ❌ | ✅ **We have it** |
| Bundle size | ~2KB | ~3KB minified |
| Battle-tested | ✅ Years | ⚠️ Growing |
## Installation
```bash
npm install lite-schema-check
```
## Quick Start
### 🔥 FormData Validation (NEW!)
```typescript
import { validateFormData, object, string, number, file } from 'lite-schema-check/v2';
const schema = object({
name: string.min(3),
age: number.positive(),
avatar: file({ maxSize: 5_000_000, mimeTypes: ['image/png', 'image/jpeg'] })
});
// In Remix/Next.js server action:
export async function action({ request }) {
const formData = await request.formData();
const result = validateFormData(formData, schema);
if (result.success) {
// Automatic type conversion! age is number, not string
await createUser(result.data);
}
}
```
### 🔄 Partial Validation (NEW!)
```typescript
import { validatePartial, object, string, number } from 'lite-schema-check/v2';
const schema = object({
name: string.min(3),
email: string.email(),
age: number.positive()
});
const data = {
name: 'John', // ✅ Valid
email: 'not-email', // ❌ Invalid
age: 30 // ✅ Valid
};
const result = validatePartial(data, schema);
// Save what's valid, show errors for what's not
await saveDraft(result.validData); // { name: 'John', age: 30 }
showErrors(result.invalidFields); // { email: {...} }
```
### 📄 JSON Schema Export (NEW!)
```typescript
import { toJSONSchema, describe, optional } from 'lite-schema-check/v2';
const schema = object({
name: describe(string.min(3), 'User full name'),
age: optional(number.positive())
});
const jsonSchema = toJSONSchema(schema);
// {
// type: 'object',
// properties: {...},
// required: ['name'] // ✅ 'age' correctly NOT required!
// }
```
### Basic Validation
```typescript
import { validate, object, optional, array, union, literal } from 'lite-schema-check/v2';
// Define a schema with all the features
const userSchema = object({
name: 'string',
age: 'number',
email: optional('string'), // Can be missing
tags: array('string', { min: 1, max: 10 }),
role: union(
literal('admin'),
literal('user'),
literal('guest')
),
profile: object({
bio: 'string',
website: optional('string')
})
});
// Validate data
const result = validate(userData, userSchema);
if (result.success) {
console.log('Valid!', result.data);
} else {
console.log('Errors:', result.errors);
// Detailed error paths: ['profile', 'bio']
}
```
### V1 API (Still works)
```typescript
import { validate } from 'lite-schema-check';
const schema = { name: 'string', age: 'number' };
const result = validate({ name: 'Alice', age: 30 }, schema);
// { isValid: true, errors: [] }
```
## Why Choose Us Over Zod?
### 1. 🔥 FormData Validation (60+ Votes!)
**Problem:** Zod has NO FormData support. Every Remix/Next.js dev needs this.
**Our Solution:**
```typescript
import { validateFormData, object, string, number, file } from 'lite-schema-check/v2';
const schema = object({
name: string.min(3),
age: number.positive(),
avatar: file({ maxSize: 5_000_000, mimeTypes: ['image/png'] })
});
// Automatic type conversion: strings → numbers, File validation
const result = validateFormData(formData, schema);
```
**Status:** ✅ We have it | ❌ Zod doesn't
---
### 2. 🔥 Partial Validation
**Problem:** Zod returns nothing if ANY field fails. Can't save partial data.
**Our Solution:**
```typescript
import { validatePartial } from 'lite-schema-check/v2';
const result = validatePartial(data, schema);
// Returns:
// {
// validData: { name: 'John', age: 30 }, // Save these!
// invalidFields: { email: {...} } // Show errors
// }
await saveDraft(result.validData); // Save what works
```
**Use Cases:** Auto-save forms, progressive validation, data migration
**Status:** ✅ We have it | ❌ Zod doesn't
---
### 3. 🔥 JSON Schema Export (Better than zod-to-json-schema)
**Problem:** Zod needs external package with bugs (optional fields marked required)
**Our Solution:**
```typescript
import { toJSONSchema, toOpenAPISchema, describe } from 'lite-schema-check/v2';
const schema = object({
name: describe(string.min(3), 'User full name'),
age: optional(number.positive())
});
const jsonSchema = toJSONSchema(schema);
// ✅ Optional fields correctly NOT in required array
// ✅ Descriptions preserved
// ✅ OpenAPI 3.0 compatible
```
**Status:** ✅ Built-in | ⚠️ Zod needs external package
---
### 4. ✅ Circular/Recursive Validation (Zod Issue #5346)
**Problem:** Zod users struggle with tree structures, categories, comment threads.
**Our Solution:**
```typescript
import { object, array, lazy } from 'lite-schema-check/v2';
const categorySchema = object({
name: 'string',
children: array(lazy(() => categorySchema))
});
// Validates infinitely nested categories!
```
**Status:** ✅ We have it | ❌ Zod doesn't (issue open since Oct 16, 2025)
---
### 2. ✅ Clearer Optional/Nullable API (Zod Issue #5348)
**Problem:** Zod's `.required()` on `.optional()` confuses users.
**Our Solution:**
```typescript
import { object, optional, nullable, nullish } from 'lite-schema-check/v2';
const schema = object({
email: optional('string'), // Can be missing entirely
phone: nullable('string'), // Can be null (must be present)
bio: nullish('string') // Can be null OR undefined
});
```
**Status:** ✅ Clearer than Zod's API
---
### 3. ✅ Mixed Sync/Async Validation (Zod Issue #5379)
**Problem:** Zod is all-sync or all-async, no mixing.
**Our Solution:**
```typescript
import { object, async } from 'lite-schema-check/v2';
import { string } from 'lite-schema-check/presets';
const schema = object({
username: async('string', async (val) => {
// Check database
const exists = await checkUsernameExists(val);
if (exists) throw new Error('Username taken');
}),
email: string.email() // Sync validation
});
// Validates efficiently
```
**Status:** ✅ We support selective async | ❌ Zod doesn't
---
### 4. ✅ Reversible Transforms (Codecs) (Zod Issues #5374, #5377)
**Problem:** Zod transforms are one-way. Users want to serialize back.
**Our Solution:**
```typescript
import { codec } from 'lite-schema-check/v2';
const dateCodec = codec(
'string',
(str) => new Date(str), // Parse (decode)
(date) => date.toISOString() // Serialize (encode)
);
// Can parse API responses AND serialize back!
```
**Status:** ✅ Bidirectional | ❌ Zod is one-way only
---
## Feature Showcase
### Arrays with Constraints
```typescript
import { object, array } from 'lite-schema-check/v2';
const schema = object({
tags: array('string', { min: 1, max: 5 })
});
validate({ tags: ['js', 'ts'] }); // ✅ Valid
validate({ tags: [] }); // ❌ Too few
validate({ tags: ['a','b','c','d','e','f'] }); // ❌ Too many
```
### Nested Objects
```typescript
const orderSchema = object({
customer: object({
name: 'string',
address: object({
street: 'string',
city: 'string',
zip: 'number'
})
}),
items: array(object({
product: 'string',
quantity: 'number'
}))
});
// Validates deeply nested structures
```
### Unions & Literals (Enums)
```typescript
const taskSchema = object({
status: union(
literal('todo'),
literal('in_progress'),
literal('done')
),
priority: union(literal(1), literal(2), literal(3))
});
// Type-safe enum-like validation
```
### String Refinements
```typescript
import { string } from 'lite-schema-check/presets';
const schema = object({
email: string.email(),
url: string.url(),
uuid: string.uuid(),
password: string.min(8),
username: string.regex(/^[a-z0-9_]+$/)
});
```
### Number Refinements
```typescript
import { number } from 'lite-schema-check/presets';
const schema = object({
age: number.positive(),
rating: number.int(),
price: number.min(0),
discount: number.max(100)
});
```
## API Reference
### Core Functions
#### `validate(input: unknown, schema: Schema): ValidationResult`
Validates data against a schema.
#### `assertValid(input: unknown, schema: Schema): asserts input`
Throws if validation fails.
#### `createValidator(schema: Schema): (input: unknown) => ValidationResult`
Creates a reusable validator.
### Schema Builders
- `object(shape)` - Define object schema
- `optional(schema)` - Mark field as optional
- `nullable(schema)` - Mark field as nullable
- `nullish(schema)` - Mark field as nullable OR optional
- `array(items, constraints?)` - Define array with optional min/max
- `union(...options)` - Multiple type options
- `literal(value)` - Exact value match
### Advanced Features
- `lazy(factory)` - Circular/recursive schemas
- `async(schema, validator)` - Async validation
- `codec(schema, decode, encode)` - Reversible transforms
- `refine(schema, refiner)` - Custom validation
- `transform(schema, transformer)` - Data transformation
## TypeScript Support
Full TypeScript support with type inference:
```typescript
import { validate, object, ValidationResult } from 'lite-schema-check/v2';
const schema = object({
name: 'string',
age: 'number'
});
const result: ValidationResult = validate(data, schema);
if (result.success) {
// result.data is typed!
}
```
## When to Use lite-schema-check
### ✅ Use Us When:
- 🔥 **You're using Remix / Next.js** - FormData validation built-in
- 🔥 **You need auto-save forms** - Partial validation
- 🔥 **You need API docs** - JSON Schema / OpenAPI export
- 🔥 **You're building forms** - Schema metadata extraction
- You need circular/recursive validation
- You want clearer optional/nullable API
- You need reversible transforms (codecs)
- You need mixed sync/async validation
- Bundle size matters (edge functions)
- You prefer simpler, more predictable API
### ⚠️ Use Zod When:
- You need battle-tested production stability
- You need the richest ecosystem
- You need advanced TypeScript type inference
- You're already using it (migration cost)
- You don't need the features we have
## Migration from Zod
Most Zod schemas translate directly:
```typescript
// Zod
z.object({
name: z.string(),
age: z.number(),
email: z.string().optional()
})
// lite-schema-check
object({
name: 'string',
age: 'number',
email: optional('string')
})
```
Most schemas migrate directly with minimal changes.
## Documentation
- **[Contributing Guide](CONTRIBUTING.md)** - How to contribute
- **[Code of Conduct](CODE_OF_CONDUCT.md)** - Community guidelines
- **[Security Policy](SECURITY.md)** - Security vulnerability reporting
- **[Changelog](CHANGELOG.md)** - Version history
## Examples
Check the `/examples` folder:
- 🔥 `formdata-validation.ts` - **NEW!** 5 FormData examples (Remix/Next.js)
- 🔥 `partial-validation.ts` - **NEW!** 5 partial validation examples (auto-save)
- 🔥 `json-schema-metadata.ts` - **NEW!** 6 JSON Schema examples (OpenAPI)
- `v2-vs-zod.ts` - Feature comparison
- `zod-pain-points.ts` - Solutions to Zod issues
## Roadmap
### ✅ Completed (v2.1)
- [x] Basic validation
- [x] Optional/nullable fields
- [x] Nested objects
- [x] Arrays with constraints
- [x] Unions & literals
- [x] String/number refinements
- [x] Circular validation (lazy)
- [x] Async validation
- [x] Codecs (reversible transforms)
- [x] 🔥 **FormData validation**
- [x] 🔥 **Partial validation**
- [x] 🔥 **JSON Schema export**
- [x] 🔥 **Schema metadata**
- [x] 🔥 **XOR support**
- [x] 🔥 **File validation**
- [x] 🔥 **Schema descriptions**
### 📋 Coming Soon
- [ ] Schema composition (extend, merge, pick, omit)
- [ ] Discriminated unions
- [ ] Better error messages
- [ ] Performance optimization
- [ ] Framework integrations (Remix, Next.js plugins)
## Contributing
Contributions welcome! We're actively building features Zod users are asking for.
**How to contribute:**
1. Watch [Zod's issues](https://github.com/colinhacks/zod/issues)
2. Implement requested features
3. Submit PR with tests
4. Help us compete!
## License
MIT
## Acknowledgments
- [Zod](https://github.com/colinhacks/zod) - Inspiration and competition
- [ArkType](https://github.com/arktypeio/arktype) - Performance ideas
- [io-ts](https://github.com/gcanti/io-ts) - Type safety concepts
---
**Made with ❤️ by developers frustrated with existing validation libraries.**
**Give us a try! We're actively solving problems Zod users are asking for.** ⭐