Version v2.1.1 Size 10.7 KB gzip
Why Spell?
Spell keeps runtime validation, static inference, and portable definitions in one API. Use s for schema construction; import JSON conversion and predicates from dedicated subpaths.
This example shows the difference between manual branching and a single reusable schema.
ts
// Before
function parseUserBefore(value: unknown) {
if (typeof value !== 'object' || value === null) throw new Error('Expected object');
const candidate = value as Record<string, unknown>;
if (typeof candidate.email !== 'string' || !candidate.email.includes('@')) {
throw new Error('Expected valid email');
}
if (typeof candidate.role !== 'string' || !['admin', 'editor', 'viewer'].includes(candidate.role)) {
throw new Error('Expected valid role');
}
return {
email: candidate.email,
role: candidate.role,
};
}
// After
import { s } from '@vielzeug/spell';
const User = s.object({
email: s.string().email(),
role: s.enum(['admin', 'editor', 'viewer'] as const),
});
const user = User.parse({ email: 'ada@example.com', role: 'admin' });| Feature | Spell | Zod | Yup |
|---|---|---|---|
| Bundle size | 10.7 KB | ~62 kB | ~14 kB |
| Type inference | Infer<T> | Partial | |
| Coercion API | s.coerce.* | ||
| Async validation | .checkAsync() | ||
| Error flattening | flatten() + flattenFirst() | Partial | |
| Zero dependencies |
Use Spell when you want a fluent schema API with strong TypeScript inference, structured errors, and no third-party runtime dependencies.
Consider alternatives when you are already standardized on another validator ecosystem and migration cost outweighs the API benefits.
Installation
Use your workspace package manager to add Spell.
sh
pnpm add @vielzeug/spellsh
npm install @vielzeug/spellsh
yarn add @vielzeug/spellQuick Start
Start with a schema, then parse unknown input and use the inferred output type everywhere else.
ts
import { s, type Infer } from '@vielzeug/spell';
const User = s
.object({
email: s.string().email(),
name: s.string().min(1),
role: s.enum(['admin', 'editor', 'viewer'] as const),
})
.relaxed(); // allow extra keys — omit for strict-mode (default)
type User = Infer<typeof User>;
const payload: unknown = {
email: 'ada@example.com',
name: 'Ada',
role: 'admin',
team: 'platform',
};
const result = User.safeParse(payload);
if (!result.success) throw result.error;
const user = result.data;Features
- Namespace and tree-shakeable schema builders.
- Sync and async parsing with
parse(),safeParse(),parseAsync(), andsafeParseAsync(). - Explicit
check()andcheckAsync()rules; sync parsing never skips an async check. - Wrapper modes for
optional,nullable,nullish,default,catch, andrequired. - Frozen declarative definitions through
definition()and JSON Schema export viafromDefinition()from@vielzeug/spell/json. - Grouped
diagnosticsandpredicatesutilities keep schema construction focused. - Ordered union parsing produces the same selected branch in sync and async modes.
- Structured errors with direct path lookup, flattened views, and best-match union diagnostics.
- Object parsing is hardened against prototype-pollution-style keys.