
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
AI/LLM-native TypeScript validation library optimized for LLM outputs, function calling, and structured generation
AI-native TypeScript validation library optimized for LLM outputs, function calling, and structured generation.
import { v } from 'valai';
const ProductSchema = v.object({
name: v.string().describe('Product name'),
price: v.number().min(0),
category: v.enum(['electronics', 'clothing', 'food']),
inStock: v.boolean().default(true)
});
// Parse messy LLM output with auto-repair
const result = ProductSchema.parseLLM(`
\`\`\`json
{
name: 'iPhone 15', // unquoted key, single quotes
price: 999, // trailing comma
}
\`\`\`
`);
// Export for OpenAI function calling
const tool = ProductSchema.toOpenAI({ name: 'extract_product' });
"25" → 25).describe() and .examples() for better LLM guidancenpm install valai
import { v } from 'valai';
// Define a schema
const UserSchema = v.object({
name: v.string().min(1),
email: v.string().email(),
age: v.number().int().min(0).optional()
});
// Infer TypeScript type
type User = v.infer<typeof UserSchema>;
// { name: string; email: string; age?: number }
// Strict parsing (throws on error)
const user = UserSchema.parse({ name: 'John', email: 'john@example.com' });
// Safe parsing (returns result object)
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data);
} else {
console.log(result.error.issues);
}
// parseLLM() handles common LLM output issues:
const schema = v.object({
name: v.string(),
score: v.number()
});
// Handles: markdown code blocks, single quotes, unquoted keys,
// trailing commas, comments, and type coercion
const result = schema.parseLLM(`
Here's the extracted data:
\`\`\`json
{
name: 'Test', // unquoted key + single quotes
score: "95", // string that should be number
}
\`\`\`
`);
console.log(result.data); // { name: 'Test', score: 95 }
const ProductSchema = v.object({
name: v.string()
.describe('The product name as it appears on the packaging')
.examples(['iPhone 15 Pro', 'MacBook Air M3']),
price: v.number()
.min(0)
.describe('Price in USD'),
category: v.enum(['electronics', 'clothing', 'food', 'other'])
.describe('Primary product category')
});
// Descriptions and examples are included in exported schemas
const jsonSchema = ProductSchema.toJSONSchema();
// OpenAI Function Calling
const openAITool = schema.toOpenAI({
name: 'extract_product',
description: 'Extract product information from text'
});
// Anthropic/Claude Tools
const claudeTool = schema.toClaude({
name: 'extract_product',
description: 'Extract product information from text'
});
// Google Gemini
const geminiFunc = schema.toGemini({
name: 'extract_product',
description: 'Extract product information from text'
});
// Standard JSON Schema (2020-12)
const jsonSchema = schema.toJSONSchema();
v.string() // string
v.number() // number
v.boolean() // boolean
v.null() // null
v.undefined() // undefined
v.any() // any
v.unknown() // unknown
v.string()
.min(1) // Minimum length
.max(100) // Maximum length
.length(10) // Exact length
.email() // Email format
.url() // URL format
.uuid() // UUID format
.regex(/pattern/) // Custom regex
.includes('text') // Contains substring
.startsWith('pre') // Starts with
.endsWith('suf') // Ends with
.trim() // Trim whitespace (transform)
.toLowerCase() // To lowercase (transform)
.toUpperCase() // To uppercase (transform)
v.number()
.min(0) // Minimum (inclusive)
.max(100) // Maximum (inclusive)
.gt(0) // Greater than
.lt(100) // Less than
.int() // Integer only
.positive() // > 0
.negative() // < 0
.nonnegative() // >= 0
.nonpositive() // <= 0
.multipleOf(5) // Multiple of
.finite() // No Infinity
const schema = v.object({
name: v.string(),
age: v.number().optional()
});
// Object methods
schema.extend({ email: v.string() }) // Add properties
schema.merge(otherSchema) // Merge schemas
schema.pick({ name: true }) // Pick properties
schema.omit({ age: true }) // Omit properties
schema.partial() // All optional
schema.required() // All required
schema.strict() // Error on extra keys
schema.passthrough() // Keep extra keys
schema.strip() // Remove extra keys (default)
v.array(v.string()) // string[]
.min(1) // Minimum length
.max(10) // Maximum length
.length(5) // Exact length
.nonempty() // At least 1 element
v.tuple([v.string(), v.number()]) // [string, number]
v.tuple([v.string()]).rest(v.number()) // [string, ...number[]]
// String enum
v.enum(['pending', 'active', 'completed'])
// Native TypeScript enum
enum Status { Pending, Active }
v.nativeEnum(Status)
// Union types
v.union([v.string(), v.number()]) // string | number
// Discriminated union
v.discriminatedUnion('type', [
v.object({ type: v.literal('a'), value: v.string() }),
v.object({ type: v.literal('b'), count: v.number() })
])
// Literal
v.literal('active') // 'active'
v.record(v.number()) // Record<string, number>
v.record(v.string().uuid(), v.any()) // Record with UUID keys
v.string().optional() // string | undefined
v.string().nullable() // string | null
v.string().default('anonymous') // Default value if undefined
valai includes powerful JSON repair utilities for handling malformed LLM outputs:
import { repairJSON, parseAndRepair, isValidJSON } from 'valai';
// Full repair with detailed result
const result = repairJSON(`{
name: 'test', // unquoted key, single quotes
value: 123, // trailing comma
}`);
console.log(result.success); // true
console.log(result.data); // { name: 'test', value: 123 }
console.log(result.repairs); // List of repairs made
// Parse and repair in one step
const data = parseAndRepair('{"a": 1,}');
// Check if valid JSON
isValidJSON('{"a": 1}'); // true
isValidJSON('{a: 1}'); // false
| Issue | Example | Fixed |
|---|---|---|
| Markdown code blocks | ```json {...} ``` | {...} |
| Single quotes | {'a': 'b'} | {"a": "b"} |
| Unquoted keys | {a: 1} | {"a": 1} |
| Trailing commas | {"a": 1,} | {"a": 1} |
| Comments | {"a": 1} // comment | {"a": 1} |
| Unclosed brackets | {"a": 1 | {"a": 1} |
| Special numbers | {"a": NaN} | {"a": null} |
| Hex/binary numbers | {"a": 0xff} | {"a": 255} |
import {
extractFromMarkdown,
extractJSONFromText,
fixSingleQuotes,
fixUnquotedKeys,
fixTrailingCommas,
tryCloseBrackets,
removeJSONComments,
fixSpecialNumbers
} from 'valai';
// Use individual fixers for fine-grained control
const text = extractFromMarkdown('```json\n{}\n```').text;
const fixed = fixTrailingCommas('{"a": 1,}');
const result = schema.safeParse(invalidData);
if (!result.success) {
// Structured errors
console.log(result.error.issues);
// [{ code: 'invalid_type', path: ['name'], message: '...' }]
// Flattened format
console.log(result.error.flatten());
// { formErrors: [], fieldErrors: { name: ['...'] } }
// Formatted string
console.log(result.error.format());
}
import { v } from 'valai';
import type { infer as Infer } from 'valai';
const UserSchema = v.object({
name: v.string(),
age: v.number().optional()
});
// Using v.infer
type User = v.infer<typeof UserSchema>;
// Using imported Infer type
type User2 = Infer<typeof UserSchema>;
// Input type (before transforms)
type UserInput = v.input<typeof UserSchema>;
// Output type (after transforms)
type UserOutput = v.output<typeof UserSchema>;
| Feature | valai | Zod |
|---|---|---|
| Schema definition | Same API | Same API |
| Type inference | Full support | Full support |
| LLM parsing mode | parseLLM() | Manual |
| JSON repair | Built-in | External |
| Markdown extraction | Built-in | Manual |
| Type coercion | Auto in LLM mode | Manual coerce |
| OpenAI export | toOpenAI() | Manual |
| Claude export | toClaude() | Manual |
| Gemini export | toGemini() | Manual |
| AI metadata | .describe(), .examples() | .describe() only |
| Partial on failure | Yes | No |
npx vitest bench --run
| Method | Description |
|---|---|
.parse(data) | Strict parse, throws on error |
.safeParse(data) | Safe parse, returns result object |
.parseLLM(data, options?) | LLM-friendly parse with repair |
.describe(text) | Add description for AI |
.examples(values) | Add examples for AI |
.optional() | Make optional |
.nullable() | Make nullable |
.default(value) | Set default value |
.toJSONSchema(options?) | Export as JSON Schema |
.toOpenAI(options) | Export for OpenAI |
.toClaude(options) | Export for Claude |
.toGemini(options) | Export for Gemini |
interface LLMParseOptions {
coerce?: boolean; // Enable type coercion (default: true)
repair?: boolean; // Enable JSON repair (default: true)
extractFromMarkdown?: boolean; // Extract from code blocks (default: true)
useDefaults?: boolean; // Use default values (default: true)
}
MIT
FAQs
AI/LLM-native TypeScript validation library optimized for LLM outputs, function calling, and structured generation
We found that valai demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.