Field Transforms
Transforms automatically modify field values before they're saved to the database. They run on create and update operations, ensuring consistent data formatting.
Available Transforms
trim()
Removes leading and trailing whitespace from string values.
import { entity, string, text, email } from "nevr"
const user = entity("user", {
// Remove accidental spaces
name: string.trim(),
username: string.trim(),
email: email.trim(),
bio: text.trim().optional(),
})Input: " John Doe "Output: "John Doe"
Use cases:
- User-submitted form data
- Usernames and identifiers
- Any text that shouldn't have surrounding whitespace
lower()
Converts string values to lowercase.
const user = entity("user", {
// Normalize to lowercase
email: string.lower().unique(),
username: string.lower().unique(),
slug: string.lower(),
})Input: "John.Doe@Example.COM"Output: "john.doe@example.com"
Use cases:
- Email addresses (case-insensitive matching)
- Usernames
- URL slugs
- Search terms
upper()
Converts string values to uppercase.
const product = entity("product", {
// Uppercase for consistency
sku: string.upper().unique(),
countryCode: string.upper().length(2, 2),
currencyCode: string.upper().length(3, 3),
})Input: "abc-12345"Output: "ABC-12345"
Use cases:
- SKU codes
- Country codes (US, GB, FR)
- Currency codes (USD, EUR, GBP)
- Reference numbers
Chaining Transforms
Transforms execute in order from left to right:
const user = entity("user", {
// trim → lower (remove spaces, then lowercase)
email: string.trim().lower().email().unique(),
// trim → upper (remove spaces, then uppercase)
referralCode: string.trim().upper().optional(),
// Full chain with validation
username: string
.trim() // 1. Remove whitespace
.lower() // 2. Convert to lowercase
.min(3) // 3. Validate minimum length
.max(20) // 4. Validate maximum length
.regex(/^[a-z0-9_]+$/, "Invalid characters") // 5. Validate format
.unique(), // 6. Database constraint
})Transform Order
Input → trim() → lower()/upper() → DatabaseFor example, with .trim().lower():
" Hello World "→"Hello World"(after trim)"Hello World"→"hello world"(after lower)"hello world"→ saved to database
Transform + Validation
Transforms run before validation, so validation operates on the transformed value:
const user = entity("user", {
// Transform first, then validate
email: string
.trim() // " USER@EMAIL.com " → "USER@EMAIL.com"
.lower() // "USER@EMAIL.com" → "user@email.com"
.email() // Validate the normalized email
.unique(),
username: string
.trim() // " MyUser123 " → "MyUser123"
.lower() // "MyUser123" → "myuser123"
.min(3) // Validate length after transform
.regex(/^[a-z0-9_]+$/), // Pattern matches lowercase
})Order Matters
If you use .upper() but validate with a lowercase regex pattern, validation will fail:
// Wrong - upper() + lowercase regex
username: string.trim().upper().regex(/^[a-z]+$/) // Will always fail!
// Correct - upper() + uppercase regex
username: string.trim().upper().regex(/^[A-Z]+$/)Transform + Security
Transforms also run before security operations:
const user = entity("user", {
// Transform → Hash → Save
email: string
.trim() // Clean input
.lower() // Normalize
.email() // Validate
.unique(),
// Transform → Hash → Omit
password: string
.trim() // Remove accidental whitespace
.min(8) // Validate after trim
.password() // Hash with scrypt
.omit(), // Never return
})Examples
User Profile
const user = entity("user", {
// Email - normalized for consistent lookup
email: string.trim().lower().email().unique(),
// Username - normalized and validated
username: string
.trim()
.lower()
.min(3, "Username too short")
.max(30, "Username too long")
.regex(/^[a-z0-9_]+$/, "Only letters, numbers, underscore")
.unique(),
// Display name - trimmed but preserves case
displayName: string.trim().min(1).max(100),
// Bio - trimmed but otherwise unmodified
bio: text.trim().max(500).optional(),
})Product Catalog
const product = entity("product", {
// SKU - uppercase standard
sku: string.trim().upper().unique(),
// Name - preserve case, remove extra spaces
name: string.trim().min(1).max(200),
// Slug - URL-friendly format
slug: string.trim().lower().regex(/^[a-z0-9-]+$/).unique(),
// Description - clean up whitespace
description: text.trim().optional(),
// Category code - uppercase
categoryCode: string.trim().upper().optional(),
})Contact Form
const contact = entity("contact", {
// Email for replies
email: string.trim().lower().email(),
// Name
name: string.trim().min(1).max(100),
// Subject
subject: string.trim().min(1).max(200),
// Message
message: text.trim().min(10).max(5000),
// Optional phone - preserve format
phone: string.trim().optional(),
})Configuration
const setting = entity("setting", {
// Config key - normalized
key: string.trim().lower().regex(/^[a-z][a-z0-9_.]*$/).unique(),
// Value - trimmed
value: text.trim(),
// Environment
environment: string.trim().lower().default("production"),
})Transform Reference
| Method | Description | Example |
|---|---|---|
.trim() | Remove leading/trailing whitespace | " hi " → "hi" |
.lower() | Convert to lowercase | "HELLO" → "hello" |
.upper() | Convert to uppercase | "hello" → "HELLO" |
Best Practices
1. Always trim user input
// Good - handles copy-paste errors
email: string.trim().lower().email()
// Bad - may fail validation due to whitespace
email: string.lower().email()2. Normalize identifiers
// Good - consistent lookup regardless of input case
username: string.trim().lower().unique()
email: string.trim().lower().unique()
// Bad - "User" and "user" could be different records
username: string.unique()3. Use uppercase for codes
// Good - standard format for codes
sku: string.trim().upper()
countryCode: string.trim().upper().length(2, 2)
// Consistent with industry standards4. Consider the full chain
// Complete chain for email
email: string
.trim() // 1. Clean whitespace
.lower() // 2. Normalize case
.email() // 3. Validate format
.unique() // 4. Database constraint
// Complete chain for username
username: string
.trim() // 1. Clean whitespace
.lower() // 2. Normalize case
.min(3) // 3. Min length
.max(20) // 4. Max length
.regex(/^[a-z0-9_]+$/) // 5. Valid chars
.unique() // 6. Database constraintNext Steps
- Security - Password hashing and encryption
- Access Policies - Field-level permissions
- Validation - Input validation rules
