@hy_ong/zod-kit
v0.2.1
Published
A comprehensive TypeScript library providing pre-built Zod validation schemas with full internationalization support for common data types and Taiwan-specific formats
Maintainers
Readme
Zod Kit
A comprehensive TypeScript library that provides pre-built validation schemas on top of Zod with full internationalization support. Streamline your form validation with battle-tested schemas for common data types and Taiwan-specific formats.
✨ Features
- 🔍 Pre-built schemas - Common validation patterns ready to use
- 🌐 i18n support - English and Traditional Chinese localization
- 📝 TypeScript-first - Full type safety and IntelliSense support
- ⚡ Lightweight - Minimal dependencies with Zod as peer dependency
- 🎯 Highly configurable - Flexible options for every use case
- 🇹🇼 Taiwan-specific - National ID, business ID, phone numbers, postal codes, etc.
- ⏰ Date/Time support - Comprehensive datetime, time, and date validation
- 📎 File validation - MIME type filtering, size constraints, and file type checking
- 🧪 Battle-tested - Comprehensive test suite with 700+ tests
📦 Installation
npm install @hy_ong/zod-kit zodyarn add @hy_ong/zod-kit zodpnpm add @hy_ong/zod-kit zodNote: Zod is a peer dependency (
^4.3.6) and must be installed separately.
🚀 Quick Start
import { email, password, text, mobile, datetime, time, postalCode } from '@hy_ong/zod-kit'
// Simple email validation (required by default)
const emailSchema = email(true)
emailSchema.parse('[email protected]') // ✅ "[email protected]"
// Optional email
const optionalEmail = email(false)
optionalEmail.parse(null) // ✅ null
// Password with complexity requirements
const passwordSchema = password(true, {
minLength: 8,
requireUppercase: true,
requireDigits: true,
requireSpecialChars: true
})
// Taiwan mobile phone validation
const phoneSchema = mobile(true)
phoneSchema.parse('0912345678') // ✅ "0912345678"
// DateTime validation
const datetimeSchema = datetime(true)
datetimeSchema.parse('2024-03-15 14:30') // ✅ "2024-03-15 14:30"
// Time validation
const timeSchema = time(true)
timeSchema.parse('14:30') // ✅ "14:30"
// Taiwan postal code validation
const postalSchema = postalCode(true)
postalSchema.parse('100001') // ✅ "100001"📚 API Reference
Common Validators
email(required?, options?)
Validates email addresses with comprehensive format checking.
Parameters:
required(boolean, optional): Whether the field is required. Default:falseoptions(object, optional): Configuration options
import { email } from '@hy_ong/zod-kit'
// Required email (recommended)
const requiredEmail = email(true)
// Optional email
const optionalEmail = email(false)
optionalEmail.parse(null) // ✅ null
// With options
const advancedEmail = email(true, {
allowedDomains: ['gmail.com', 'company.com'],
minLength: 5,
maxLength: 100,
transform: (val) => val.toLowerCase(),
defaultValue: '[email protected]',
i18n: {
en: { invalid: 'Please enter a valid email' },
'zh-TW': { invalid: '請輸入有效的電子郵件' }
}
})password(required?, options?)
Validates passwords with customizable complexity requirements.
Parameters:
required(boolean, optional): Whether the field is required. Default:falseoptions(object, optional): Configuration options
import { password } from '@hy_ong/zod-kit'
// Required password with complexity rules
const passwordSchema = password(true, {
minLength: 8, // Minimum length
maxLength: 128, // Maximum length
requireUppercase: true, // Require A-Z
requireLowercase: true, // Require a-z
requireDigits: true, // Require 0-9
requireSpecialChars: true,// Require !@#$%^&*
customPatterns: [
{ pattern: /[A-Z]/, message: 'Need uppercase' }
]
})
// Optional password
const optionalPassword = password(false)text(required?, options?)
General text validation with length and pattern constraints.
import { text } from '@hy_ong/zod-kit'
const nameSchema = text(true, {
minLength: 2,
maxLength: 50,
pattern: /^[a-zA-Z\s]+$/,
transform: (val) => val.trim()
})
const optionalText = text(false)number(required?, options?)
Validates numeric values with range and type constraints.
import { number } from '@hy_ong/zod-kit'
const ageSchema = number(true, {
min: 0,
max: 150,
integer: true,
positive: true
})
const optionalNumber = number(false)url(required?, options?)
URL validation with protocol and domain restrictions.
import { url } from '@hy_ong/zod-kit'
const urlSchema = url(true, {
protocols: ['https'], // Only HTTPS allowed
allowedDomains: ['safe.com'], // Domain whitelist
requireTLD: true // Require top-level domain
})
const optionalUrl = url(false)boolean(required?, options?)
Boolean validation with flexible input handling.
import { boolean } from '@hy_ong/zod-kit'
const consentSchema = boolean(true, {
trueValues: ['yes', '1', 'true'], // Custom truthy values
falseValues: ['no', '0', 'false'] // Custom falsy values
})
const optionalBoolean = boolean(false)datetime(required?, options?)
Validates datetime with comprehensive format support and timezone handling.
import { datetime } from '@hy_ong/zod-kit'
// Basic datetime validation
const basicSchema = datetime(true)
basicSchema.parse('2024-03-15 14:30') // ✓ Valid
// Business hours validation
const businessHours = datetime(true, {
format: 'YYYY-MM-DD HH:mm',
minHour: 9,
maxHour: 17,
weekdaysOnly: true
})
// Timezone-aware validation
const timezoneSchema = datetime(true, {
timezone: 'Asia/Taipei',
mustBeFuture: true
})
// Multiple format support
const flexibleSchema = datetime(true, {
format: 'DD/MM/YYYY HH:mm'
})
flexibleSchema.parse('15/03/2024 14:30') // ✓ Valid
// Optional datetime
const optionalDatetime = datetime(false)time(required?, options?)
Time validation with multiple formats and constraints.
import { time } from '@hy_ong/zod-kit'
// Basic time validation (24-hour format)
const basicSchema = time(true)
basicSchema.parse('14:30') // ✓ Valid
// 12-hour format with AM/PM
const ampmSchema = time(true, { format: 'hh:mm A' })
ampmSchema.parse('02:30 PM') // ✓ Valid
// Business hours validation
const businessHours = time(true, {
format: 'HH:mm',
minHour: 9,
maxHour: 17,
minuteStep: 15 // Only :00, :15, :30, :45
})
// Time range validation
const timeRangeSchema = time(true, {
min: '09:00',
max: '17:00'
})
// Optional time
const optionalTime = time(false)date(required?, options?)
Date validation with range and format constraints.
import { date } from '@hy_ong/zod-kit'
const birthdateSchema = date(true, {
format: 'YYYY-MM-DD',
minDate: '1900-01-01',
maxDate: new Date(),
timezone: 'Asia/Taipei'
})
const optionalDate = date(false)file(required?, options?)
File validation with MIME type filtering and size constraints.
import { file } from '@hy_ong/zod-kit'
// Basic file validation
const basicSchema = file(true)
basicSchema.parse(new File(['content'], 'test.txt'))
// Size restrictions
const sizeSchema = file(true, {
maxSize: 1024 * 1024, // 1MB
minSize: 1024 // 1KB
})
// Extension restrictions
const imageSchema = file(true, {
extension: ['.jpg', '.png', '.gif'],
maxSize: 5 * 1024 * 1024 // 5MB
})
// MIME type restrictions
const documentSchema = file(true, {
type: ['application/pdf', 'application/msword'],
maxSize: 10 * 1024 * 1024 // 10MB
})
// Image files only
const imageOnlySchema = file(true, { imageOnly: true })
// Optional file
const optionalFile = file(false)id(required?, options?)
Flexible ID validation supporting multiple formats.
import { id } from '@hy_ong/zod-kit'
const userIdSchema = id(true, {
type: 'uuid', // 'uuid', 'nanoid', 'objectId', 'auto', etc.
allowedTypes: ['uuid', 'nanoid'], // Multiple allowed types
customRegex: /^USR_[A-Z0-9]+$/ // Custom pattern
})
const optionalId = id(false)Taiwan-Specific Validators
nationalId(required?, options?)
Validates Taiwan National ID (身份證字號).
import { nationalId } from '@hy_ong/zod-kit'
const idSchema = nationalId(true, {
normalize: true, // Convert to uppercase
whitelist: ['A123456789'] // Allow specific IDs
})
idSchema.parse('A123456789') // ✅ Valid Taiwan National ID
const optionalId = nationalId(false)businessId(required?, options?)
Validates Taiwan Business ID (統一編號).
import { businessId } from '@hy_ong/zod-kit'
const bizSchema = businessId(true)
bizSchema.parse('12345675') // ✅ Valid business ID with checksum
const optionalBizId = businessId(false)mobile(required?, options?)
Validates Taiwan mobile phone numbers.
import { mobile } from '@hy_ong/zod-kit'
const phoneSchema = mobile(true, {
allowInternational: true, // Allow +886 prefix
allowSeparators: true, // Allow 0912-345-678
operators: ['09'] // Restrict to specific operators
})
const optionalMobile = mobile(false)tel(required?, options?)
Validates Taiwan landline telephone numbers.
import { tel } from '@hy_ong/zod-kit'
const landlineSchema = tel(true, {
allowSeparators: true, // Allow 02-1234-5678
areaCodes: ['02', '03'] // Restrict to specific areas
})
const optionalTel = tel(false)fax(required?, options?)
Validates Taiwan fax numbers (same format as landline).
import { fax } from '@hy_ong/zod-kit'
const faxSchema = fax(true)
faxSchema.parse('02-2345-6789') // ✅ Valid fax number
const optionalFax = fax(false)postalCode(required?, options?)
Validates Taiwan postal codes with support for 3-digit, 5-digit, and 6-digit formats.
import { postalCode } from '@hy_ong/zod-kit'
// Accept 3-digit or 6-digit formats (recommended)
const modernSchema = postalCode(true)
modernSchema.parse('100') // ✅ Valid 3-digit
modernSchema.parse('100001') // ✅ Valid 6-digit
// Accept all formats
const flexibleSchema = postalCode(true, { format: 'all' })
flexibleSchema.parse('100') // ✅ Valid
flexibleSchema.parse('10001') // ✅ Valid (5-digit legacy)
flexibleSchema.parse('100001') // ✅ Valid
// Only 6-digit format (current standard)
const modernOnlySchema = postalCode(true, { format: '6' })
modernOnlySchema.parse('100001') // ✅ Valid
modernOnlySchema.parse('100') // ❌ Invalid
// With dashes allowed
const dashSchema = postalCode(true, { allowDashes: true })
dashSchema.parse('100-001') // ✅ Valid (normalized to '100001')
// Specific areas only
const taipeiSchema = postalCode(true, {
allowedPrefixes: ['100', '103', '104', '105', '106']
})
taipeiSchema.parse('100001') // ✅ Valid (Taipei area)
taipeiSchema.parse('200001') // ❌ Invalid (not in allowlist)
// Optional postal code
const optionalPostal = postalCode(false)🌐 Internationalization
Zod Kit supports multiple languages for error messages:
import { setLocale } from '@hy_ong/zod-kit'
// Set global locale
setLocale('zh-TW') // Traditional Chinese
setLocale('en') // English (default)
// Or use custom messages per validator
const emailSchema = email(true, {
i18n: {
en: {
required: 'Email is required',
invalid: 'Please enter a valid email address'
},
'zh-TW': {
required: '請輸入電子郵件',
invalid: '請輸入有效的電子郵件格式'
}
}
})🎨 Advanced Usage
Optional Fields
Make any field optional by passing false as the first argument:
const optionalEmail = email(false)
optionalEmail.parse(null) // ✅ null
optionalEmail.parse('') // ✅ null
optionalEmail.parse('[email protected]') // ✅ "[email protected]"Custom Transformations
Transform values during validation:
const trimmedText = text(true, {
transform: (val) => val.trim().toLowerCase(),
minLength: 1
})
trimmedText.parse(' HELLO ') // ✅ "hello"Default Values
Provide default values for empty inputs:
const emailWithDefault = email(true, {
defaultValue: '[email protected]'
})
emailWithDefault.parse('') // ✅ "[email protected]"
emailWithDefault.parse(null) // ✅ "[email protected]"Whitelist Validation
Allow specific values regardless of format:
const flexibleId = id(true, {
type: 'uuid',
whitelist: ['admin', 'system', 'test-user']
})
flexibleId.parse('admin') // ✅ "admin"
flexibleId.parse('550e8400-e29b-41d4-a716-446655440000') // ✅ Valid UUIDCombining Validators
Use with Zod's composition features:
import { z } from 'zod'
import { email, password } from '@hy_ong/zod-kit'
const userSchema = z.object({
email: email(true),
password: password(true, { minLength: 8 }),
confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
message: "Passwords don't match"
})🛠️ Development
# Clone the repository
git clone https://github.com/hy-ong/zod-kit.git
cd zod-kit
# Install dependencies
npm install
# Run tests
npm test
# Build the package
npm run build
# Run tests in watch mode
npx vitest --watch🧪 Testing
The library includes comprehensive tests covering:
- ✅ All validator functions
- ✅ Edge cases and error conditions
- ✅ Internationalization
- ✅ TypeScript type safety
- ✅ Taiwan-specific format validation
npm test # Run all tests
npx vitest --coverage # Run with coverage report📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📊 Project Stats
🙏 Acknowledgments
- Built on top of the amazing Zod library
- Inspired by real-world validation needs in Taiwan's tech ecosystem
- Thanks to all contributors and users
Made with ❤️ by Ong Hoe Yuan
For questions or support, please open an issue on GitHub.
