Custom Type Handlers
Learn to register custom type validation handlers with JSTC.
Creating Custom Handlers
Register custom type checkers:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
const checker = new JSTypeChecker("boundary");
// Register a custom handler
checker.addCustomHandler("positiveNumber", (value) => {
return typeof value === "number" && value > 0;
});
// Use the custom type
const result = checker.for([42]).check(["positiveNumber"]);
console.log(result); // true
const result2 = checker.for([-5]).check(["positiveNumber"]);
console.log(result2); // falseLive demo of custom handlers:
Console
No logs yet.
Multiple Custom Validators
Create specialized validators for domain logic:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
const appChecker = new JSTypeChecker("hardened");
// Email validator
appChecker.addCustomHandler("email", (value) => {
if (typeof value !== "string") return false;
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
});
// UUID validator
appChecker.addCustomHandler("uuid", (value) => {
if (typeof value !== "string") return false;
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return uuidRegex.test(value);
});
// Integer validator
appChecker.addCustomHandler("integer", (value) => {
return typeof value === "number" && Number.isInteger(value);
});
// Non-empty string validator
appChecker.addCustomHandler("nonEmptyString", (value) => {
return typeof value === "string" && value.trim().length > 0;
});
// Usage
console.log(appChecker.for(["user@example.com"]).check(["email"])); // true
console.log(appChecker.for(["123e4567-e89b-12d3-a456-426614174000"]).check(["uuid"])); // true
console.log(appChecker.for([42]).check(["integer"])); // true
console.log(appChecker.for(["hello"]).check(["nonEmptyString"])); // trueValidating Objects with Custom Types
Check object properties:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
const checker = new JSTypeChecker("hardened");
checker.addCustomHandler("email", (value) => {
if (typeof value !== "string") return false;
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
});
interface User {
id: number;
email: string;
active: boolean;
}
function validateUser(data: any): data is User {
if (typeof data !== "object" || data === null) return false;
return (
checker.for([data.id]).check(["number"]) &&
checker.for([data.email]).check(["email"]) &&
checker.for([data.active]).check(["boolean"])
);
}
const user = { id: 1, email: "john@example.com", active: true };
console.log(validateUser(user)); // trueConditional Custom Handlers
Create context-dependent validators:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
class ValidationContext {
private checker: JSTypeChecker;
constructor(hardenedMode: boolean = false) {
this.checker = new JSTypeChecker(hardenedMode ? "hardened" : "boundary");
}
setupValidators(minPasswordLength: number = 8) {
// Password validator with configurable length
this.checker.addCustomHandler("password", (value) => {
if (typeof value !== "string") return false;
return (
value.length >= minPasswordLength &&
/[A-Z]/.test(value) &&
/[a-z]/.test(value) &&
/\d/.test(value)
);
});
// Age validator
this.checker.addCustomHandler("age", (value) => {
return typeof value === "number" && value >= 18 && value <= 120;
});
// Phone validator (simplified)
this.checker.addCustomHandler("phone", (value) => {
if (typeof value !== "string") return false;
return /^\d{10}$/.test(value.replace(/\D/g, ""));
});
}
validate(values: unknown[], types: string[]): boolean {
return this.checker.for(values).check(types);
}
}
// Usage
const validator = new ValidationContext(true);
validator.setupValidators(12);
console.log(validator.validate(["MyPassword1"], ["password"])); // true
console.log(validator.validate([25], ["age"])); // true
console.log(validator.validate(["5551234567"], ["phone"])); // trueComposite Custom Types
Chain validators for complex validation:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
class CompositeValidators {
static setup(checker: JSTypeChecker) {
checker.addCustomHandler("positiveInteger", (value) => {
return typeof value === "number" && Number.isInteger(value) && value > 0;
});
checker.addCustomHandler("percentage", (value) => {
return typeof value === "number" && value >= 0 && value <= 100;
});
checker.addCustomHandler("hexColor", (value) => {
if (typeof value !== "string") return false;
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value);
});
checker.addCustomHandler("url", (value) => {
if (typeof value !== "string") return false;
try {
new URL(value);
return true;
} catch {
return false;
}
});
}
}
const checker = new JSTypeChecker("hardened");
CompositeValidators.setup(checker);
console.log(checker.for([42]).check(["positiveInteger"])); // true
console.log(checker.for([75]).check(["percentage"])); // true
console.log(checker.for(["#FF6B6B"]).check(["hexColor"])); // true
console.log(checker.for(["https://example.com"]).check(["url"])); // truePattern and Format Validation
Use custom handlers for format checks such as phone numbers, colors, and IP addresses.
Range and Bound Validation
Validate numeric ranges:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
class BoundTypes {
static checker = new JSTypeChecker("hardened");
static isInRange(value: any, min: number, max: number): boolean {
if (this.checker.for([value]).check(["number"]) !== true) return false;
return value >= min && value <= max;
}
static isPercentage(value: any): boolean {
return this.isInRange(value, 0, 100);
}
static isAge(value: any): boolean {
return this.isInRange(value, 0, 150);
}
static isScaledValue(value: any, scale: number = 10): boolean {
if (this.checker.for([value]).check(["number"]) !== true) return false;
return Number.isInteger(value / scale);
}
static isWithinStandardDeviation(
value: number,
mean: number,
stdDev: number,
deviations: number = 2
): boolean {
const lower = mean - stdDev * deviations;
const upper = mean + stdDev * deviations;
return this.isInRange(value, lower, upper);
}
}
// Usage
console.log(BoundTypes.isInRange(50, 0, 100)); // true
console.log(BoundTypes.isPercentage(75)); // true
console.log(BoundTypes.isAge(25)); // true
console.log(BoundTypes.isAge(200)); // falseCollection Type Validation
Validate collection contents:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
class CollectionTypes {
static checker = new JSTypeChecker("hardened");
static isArrayOf(arr: any, type: string | string[]): boolean {
if (this.checker.for([arr]).check(["array"]) !== true) return false;
return arr.every((item) => this.checker.for([item]).check([type]));
}
static isStringArray(arr: any): boolean {
return this.isArrayOf(arr, "string");
}
static isNumberArray(arr: any): boolean {
return this.isArrayOf(arr, "number");
}
static isMixed(arr: any): boolean {
if (this.checker.for([arr]).check(["array"]) !== true) return false;
return arr.some((item) => this.checker.for([item]).check(["string"])) &&
arr.some((item) => this.checker.for([item]).check(["number"]));
}
static isObjectArray(arr: any): boolean {
return this.isArrayOf(arr, "object");
}
static hasUniqueElements(arr: any[]): boolean {
if (this.checker.for([arr]).check(["array"]) !== true) return false;
return new Set(arr).size === arr.length;
}
}
// Usage
console.log(CollectionTypes.isStringArray(["a", "b", "c"])); // true
console.log(CollectionTypes.isNumberArray([1, 2, 3])); // true
console.log(CollectionTypes.isMixed(["a", 1, "b", 2])); // true
console.log(CollectionTypes.hasUniqueElements([1, 2, 2, 3])); // falseSpecialized Domain Types
Create domain-specific type checkers:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
class DomainTypes {
static checker = new JSTypeChecker("hardened");
// E-commerce types
static isSKU(value: any): boolean {
if (this.checker.for([value]).check(["string"]) !== true) return false;
return /^[A-Z0-9]{6,12}$/.test(value);
}
static isPrice(value: any): boolean {
if (this.checker.for([value]).check(["number"]) !== true) return false;
return value >= 0 && Number(value.toFixed(2)) === value;
}
// User types
static isUsername(value: any): boolean {
if (this.checker.for([value]).check(["string"]) !== true) return false;
return /^[a-zA-Z0-9_]{3,20}$/.test(value);
}
static isTag(value: any): boolean {
if (this.checker.for([value]).check(["string"]) !== true) return false;
return /^#?[a-zA-Z0-9_-]{1,30}$/.test(value);
}
// URL types
static isSlug(value: any): boolean {
if (this.checker.for([value]).check(["string"]) !== true) return false;
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
}
static isAbsoluteURL(value: any): boolean {
if (this.checker.for([value]).check(["string"]) !== true) return false;
try {
new URL(value);
return true;
} catch {
return false;
}
}
}
// Usage
console.log(DomainTypes.isSKU("ABC123456")); // true
console.log(DomainTypes.isPrice(19.99)); // true
console.log(DomainTypes.isUsername("john_doe")); // true
console.log(DomainTypes.isSlug("my-slug")); // trueComposed Type Validators
Combine multiple type checks:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
interface ValidationRule {
type: string | string[];
custom?: (value: any) => boolean;
}
class ComposedTypes {
static checker = new JSTypeChecker("hardened");
static validate(value: any, rules: ValidationRule[]): boolean {
return rules.every((rule) => {
// First check base type
if (this.checker.for([value]).check([rule.type]) !== true) {
return false;
}
// Then apply custom validator if provided
if (rule.custom) {
return rule.custom(value);
}
return true;
});
}
static createValidator(rules: ValidationRule[]) {
return (value: any) => this.validate(value, rules);
}
}
// Usage
const emailValidator = ComposedTypes.createValidator([
{ type: "string" },
{
custom: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)
}
]);
console.log(emailValidator("user@example.com")); // true
console.log(emailValidator("invalid")); // falseConditional Custom Types
Type validations with conditions:
typescript
import { JSTypeChecker } from "@briklab/lib/jstc";
class ConditionalTypes {
static checker = new JSTypeChecker();
static isPasswordField(value: any, options?: { minLength: number }): boolean {
if (this.checker.for([value]).check(["string"]) !== true) return false;
const minLength = options?.minLength || 8;
return (
value.length >= minLength &&
/[A-Z]/.test(value) &&
/[a-z]/.test(value) &&
/\d/.test(value) &&
/[!@#$%^&*]/.test(value)
);
}
static isOptionalField(value: any, type: string, required: boolean = false): boolean {
if (!required && (value === null || value === undefined)) {
return true;
}
return this.checker.for([value]).check([type]) === true;
}
static isConditionalField(
value: any,
primaryType: string,
condition: (v: any) => boolean
): boolean {
if (this.checker.for([value]).check([primaryType]) !== true) return false;
return condition(value);
}
}
// Usage
const strongPassword = "MyPassword123!";
console.log(ConditionalTypes.isPasswordField(strongPassword)); // true
console.log(ConditionalTypes.isOptionalField(undefined, "string", false)); // true
console.log(ConditionalTypes.isOptionalField(undefined, "string", true)); // falseNext Steps
- Validation Rules: Standard patterns
- Examples: Real-world scenarios
- API Reference: Complete documentation