@reso/validation
@reso/validation
Isomorphic TypeScript validation library for RESO Data Dictionary records. Validates field types, lengths, and ranges against metadata, plus resource-specific business rules with cross-field constraints. Zero external dependencies.
Usage
import { validateRecord, validateBusinessRules, getBusinessRules } from '@reso/validation';
import type { ResoField, ValidationFailure } from '@reso/validation';
// Validate a record against field metadata
const failures = validateRecord(record, fields);
// Validate resource-specific business rules
const ruleFailures = validateBusinessRules('Property', record);
// Get the rule definitions for a resource
const rules = getBusinessRules('Property');
API
validateRecord(body, fields): ValidationFailure[]
Validates a record payload against RESO field metadata. Checks:
- Unknown fields — fields not in metadata
- Type mismatches — Edm.String, Edm.Boolean, Edm.Int32, Edm.Decimal, Edm.Date, Edm.DateTimeOffset, etc.
- Negative numerics — rejects negative values for numeric types
- MaxLength — strings exceeding
field.maxLength - Integer enforcement — Int types must be whole numbers
- Collection fields — must be arrays
- Enum fields — must be string or number
- Business rules — delegates to
validateBusinessRules()for range/relationship checks
Null, undefined, and empty string values are silently skipped unless the field is required. Fields starting with @ (OData annotations) are ignored.
validateBusinessRules(resourceName, body): ValidationFailure[]
Validates resource-specific constraints:
Property:
- Required: City, StateOrProvince, PostalCode, Country
- Price fields (> 0 to $1B): ListPrice, OriginalListPrice, PreviousListPrice, ClosePrice, ListPriceLow
- Room counts (0 to 100): BedroomsTotal, BathroomsFull, BathroomsHalf, etc.
- Expense/fee/amount fields ($0 to $10K): matched via
fieldPatternregex/(?:Expense|Amount|Fee\d?)$/ - Latitude and Longitude are exempt from the non-negative rule (negative coordinates are valid)
- Cross-field:
ListPrice >= ListPriceLow - Cross-field:
BathroomsTotalInteger = sum(BathroomsFull + BathroomsHalf + ...)
Member: Required MemberCity, MemberStateOrProvince, MemberPostalCode, MemberCountry
Office: Required OfficeCity, OfficeStateOrProvince, OfficePostalCode, OfficeCountry
getBusinessRules(resourceName): FieldRule[]
Returns the per-field constraint definitions for a resource.
Type Helpers
isEnumType(type)— true if type is an enum reference (not a primitive Edm type)isNumericEdmType(type)— true for Edm.Decimal, Edm.Int32, Edm.Double, etc.isIntegerEdmType(type)— true for Edm.Int16, Edm.Int32, Edm.Int64, Edm.Byte
Types
interface ValidationFailure {
readonly field: string;
readonly reason: string;
}
interface FieldRule {
readonly fieldName: string;
readonly fieldPattern?: RegExp; // regex alternative to fieldName for matching multiple fields
readonly required?: boolean;
readonly min?: number;
readonly max?: number;
readonly message?: string;
}
interface CrossFieldRule {
readonly name: string;
readonly validate: (body: Record<string, unknown>) => ValidationFailure | null;
}
Integration
Used by both the reference server (request body validation on POST/PATCH) and the React UI (client-side form validation). Also used by @reso/certification-test-runner for payload validation in compliance testing.