
Research
/Security News
Weaponizing Discord for Command and Control Across npm, PyPI, and RubyGems.org
Socket researchers uncover how threat actors weaponize Discord across the npm, PyPI, and RubyGems ecosystems to exfiltrate sensitive data.
@oxog/schema-validator
Advanced tools
A powerful, zero-dependency validation library with comprehensive type inference and an elegant API
npm install @oxog/schema-validator
import v from '@oxog/schema-validator';
// Define your schema
const userSchema = v.object({
name: v.string().min(2),
email: v.string().email(),
age: v.number().int().positive(),
roles: v.array(v.enum(['admin', 'user', 'moderator'])).default([])
});
// TypeScript knows the exact type!
type User = v.infer<typeof userSchema>;
// Parse with confidence
const user = userSchema.parse(data); // Throws if invalid
const result = userSchema.safeParse(data); // Returns { success, data/error }
if (result.success) {
console.log(result.data); // Fully typed!
} else {
console.log(result.error.issues); // Detailed error info
}
v.string() // String validation
v.number() // Number validation
v.boolean() // Boolean validation
v.date() // Date validation
v.bigint() // BigInt validation
v.null() // Null validation
v.undefined() // Undefined validation
v.any() // Any type (use sparingly!)
v.string()
.min(3) // Minimum length
.max(100) // Maximum length
.email() // Valid email format
.url() // Valid URL
.uuid() // Valid UUID v4
.regex(/^[A-Z]/) // Custom pattern
.startsWith('https://') // URL must be HTTPS
.endsWith('.com') // Domain check
.includes('@') // Contains substring
.trim() // Remove whitespace
.toLowerCase() // Normalize case
v.number()
.int() // Integer only
.positive() // Greater than 0
.min(0) // Minimum value
.max(100) // Maximum value
.multipleOf(5) // Divisible by 5
.finite() // No Infinity or -Infinity
.safe() // Within safe integer range
const personSchema = v.object({
name: v.string(),
age: v.number().optional(), // Optional field
email: v.string().nullable(), // Can be null
bio: v.string().default('') // Default value
});
// Advanced object methods
personSchema.pick({ name: true }); // Only 'name' field
personSchema.omit({ age: true }); // All except 'age'
personSchema.partial(); // All fields optional
personSchema.deepPartial(); // Nested optional
personSchema.merge(otherSchema); // Combine schemas
personSchema.extend({ city: v.string() }); // Add fields
// Arrays
const tagsSchema = v.array(v.string()).min(1).max(5);
const numbersSchema = v.array(v.number()).nonempty();
// Tuples (fixed-length arrays)
const coordinateSchema = v.tuple([v.number(), v.number()]);
const rgbSchema = v.tuple([
v.number().min(0).max(255),
v.number().min(0).max(255),
v.number().min(0).max(255)
]);
// Simple union
const idSchema = v.union([
v.string().uuid(),
v.number().int()
]);
// Discriminated union (better performance!)
const resultSchema = v.discriminatedUnion('status', [
v.object({
status: v.literal('success'),
data: v.any()
}),
v.object({
status: v.literal('error'),
code: v.number(),
message: v.string()
})
]);
Transform data during parsing:
const normalizedEmail = v.string()
.email()
.transform(email => email.toLowerCase().trim());
const result = normalizedEmail.parse(' USER@EXAMPLE.COM ');
// Result: 'user@example.com'
Add custom validation logic:
const passwordSchema = v.string()
.min(8)
.refine(
password => /[A-Z]/.test(password),
'Password must contain uppercase letter'
)
.refine(
password => /[0-9]/.test(password),
'Password must contain number'
);
Automatically convert types:
const schema = v.object({
age: v.coerce.number(), // "25" → 25
active: v.coerce.boolean(), // "true" → true
date: v.coerce.date() // "2024-01-01" → Date object
});
Process input before validation:
const preprocessedEmail = v.preprocess(
(input) => String(input).trim().toLowerCase(),
v.string().email()
);
Handle recursive data structures:
type Comment = {
text: string;
author: string;
replies?: Comment[];
};
const commentSchema: v.ZodType<Comment> = v.lazy(() =>
v.object({
text: v.string(),
author: v.string(),
replies: v.array(commentSchema).optional()
})
);
Create nominal types for extra type safety:
const UserIdSchema = v.string().uuid().brand('UserId');
const PostIdSchema = v.string().uuid().brand('PostId');
type UserId = v.infer<typeof UserIdSchema>;
type PostId = v.infer<typeof PostIdSchema>;
// TypeScript prevents mixing them up!
function getUser(id: UserId) { /* ... */ }
function getPost(id: PostId) { /* ... */ }
Extend with custom validators:
import { createPlugin } from '@oxog/schema-validator/plugins';
const customPlugin = createPlugin({
name: 'my-validators',
validators: {
creditCard: (value: string) => {
// Luhn algorithm implementation
return isValidCreditCard(value);
},
phoneNumber: (value: string) => {
return /^\\+?[1-9]\\d{1,14}$/.test(value);
}
}
});
// Register globally
v.use(customPlugin);
// Now use anywhere!
const paymentSchema = v.object({
cardNumber: v.string().creditCard(),
phone: v.string().phoneNumber()
});
// Automatic type inference
const userSchema = v.object({
id: v.string().uuid(),
name: v.string(),
age: v.number()
});
// Extract the TypeScript type
type User = v.infer<typeof userSchema>;
// Result: { id: string; name: string; age: number }
// Input vs Output types
const transformSchema = v.object({
name: v.string().transform(s => s.toUpperCase())
});
type Input = v.input<typeof transformSchema>; // { name: string }
type Output = v.output<typeof transformSchema>; // { name: string }
function processData(data: unknown) {
const result = userSchema.safeParse(data);
if (result.success) {
// TypeScript knows result.data is User
console.log(result.data.name);
} else {
// Handle validation errors
console.log(result.error.issues);
}
}
// Or create a type predicate
function isUser(data: unknown): data is User {
return userSchema.safeParse(data).success;
}
const result = schema.safeParse(invalidData);
if (!result.success) {
// Detailed error information
result.error.issues.forEach(issue => {
console.log({
path: issue.path.join('.'), // e.g., "user.email"
message: issue.message, // e.g., "Invalid email"
code: issue.code // e.g., "invalid_string"
});
});
// Format errors for display
const formatted = result.error.format();
// { email: { _errors: ["Invalid email"] } }
}
Simple object validation: 2.3x faster
Complex nested objects: 2.8x faster
Large array validation: 3.1x faster
Union type checking: 2.5x faster
Most Zod code works with minimal changes:
// Zod
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2),
age: z.number().int()
});
// @oxog/schema-validator
import v from '@oxog/schema-validator';
const schema = v.object({
name: v.string().min(2),
age: v.number().int()
});
Key differences:
v
instead of z
Check out the examples directory for:
We welcome contributions! See CONTRIBUTING.md for guidelines.
MIT © Ersin KOC
Inspired by Zod and other great validation libraries. Built with ❤️ for the TypeScript community.
If you find this project useful, please consider giving it a ⭐
Made with ❤️ by developers, for developers
FAQs
Type-safe schema validation library with zero dependencies
We found that @oxog/schema-validator 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.
Research
/Security News
Socket researchers uncover how threat actors weaponize Discord across the npm, PyPI, and RubyGems ecosystems to exfiltrate sensitive data.
Security News
Socket now integrates with Bun 1.3’s Security Scanner API to block risky packages at install time and enforce your organization’s policies in local dev and CI.
Research
The Socket Threat Research Team is tracking weekly intrusions into the npm registry that follow a repeatable adversarial playbook used by North Korean state-sponsored actors.