@yohannawf/typeswiftjs
v1.3.4
Published
Runtime type validation and schema enforcement for JavaScript - Better than Zod with Generics, Algebraic Types, and Late Initialization
Downloads
1,259
Maintainers
Readme
TypeSwiftJS 🚀
Runtime type validation and schema enforcement for JavaScript/TypeScript - Better than Zod with Generics, Algebraic Types, and Late Initialization
📖 Table of Contents
- Overview
- Why TypeSwiftJS?
- Installation
- Quick Start
- Importing
- Core Types
- Advanced Types
- Collections
- Algebraic Types
- TypedRef - Late Initialization
- Advanced Features
- Internationalization
- JSON Schema Export
- Use Cases
- API Reference
- Performance
- Migration Guide
- Contributing
- License
🎯 Overview
TypeSwiftJS is a comprehensive runtime type validation library that brings type safety to JavaScript at runtime. Inspired by Zod but enhanced with powerful features from Rust, Haskell, Java, and other modern languages, TypeSwiftJS provides a robust solution for validating data structures with an intuitive, chainable API.
Whether you're building APIs, validating user input, or ensuring configuration integrity, TypeSwiftJS gives you the confidence that your data matches your expectations.
Key Information
- Version: 1.2.3
- License: MIT
- Author: Yohanna WF
- Package Size: Tree-shakeable, modular architecture
- Dependencies: Zero - completely standalone
- TypeScript Support: Full native support with complete type inference
- Node.js: Requires Node.js ≥16.0.0
🌟 Why TypeSwiftJS?
Unique Features That Set Us Apart
TypeSwiftJS isn't just another validation library—it's a complete type system for runtime JavaScript with features you won't find anywhere else:
🔥 Zero Dependencies
Completely standalone with no external packages. This means:
- Smaller bundle sizes
- No dependency conflicts
- Faster installation
- Better security (fewer attack vectors)
⚡ Full TypeScript Integration
Native TypeScript support with complete type inference:
const userSchema = Type.object({
name: Type.string(),
age: Type.number(),
});
type User = Infer<typeof userSchema>;
// Automatically inferred as: { name: string; age: number }🌍 Built-in Internationalization
Support for 3 languages out of the box:
- English (en-US) - Default
- Portuguese (pt-BR) - Full support
- Spanish (es-ES) - Full support
Error messages automatically translated based on your locale setting.
🎨 Chainable, Intuitive API
Fluent API design that reads like natural language:
const emailSchema = Type.string()
.email()
.trim()
.lowercase()
.min(5)
.max(100);🧩 Modular & Tree-Shakeable
Import only what you need. Modern bundlers can eliminate unused code, keeping your bundle size minimal.
🔢 Advanced Mathematical Validators
Unique to TypeSwiftJS! Validators you won't find in any other library:
- Prime numbers - Miller-Rabin primality test
- Fibonacci sequence - Validate Fibonacci numbers
- Even/Odd numbers - Parity validation
- C-style integers - int8, int16, int32 with overflow detection
Type.number().prime().parse(17); // ✅
Type.number().fibonacci().parse(13); // ✅
Type.number().even().parse(42); // ✅
Type.int8().parse(127); // ✅ (-128 to 127)🎭 Algebraic Types (Haskell & Rust Compatible)
Support for both Haskell and Rust/Option naming conventions:
- Maybe/Option types - Handle optional values safely with
just/nothing(Haskell) orsome/none(Rust) - Either types - Sophisticated error handling with left/right semantics
- Result types - Success/failure patterns for robust error handling
- Pattern matching - Functional programming utilities
- Choose your preferred style - both APIs work identically!
// Haskell style
const value = Maybe.just(42);
Maybe.isJust(value); // true
// Rust/Option style (aliases)
const value2 = Maybe.some(42);
Maybe.isSome(value2); // true
// Both work the same way!🏗️ Late Initialization (Java-style TypedRef)
Unique to TypeSwiftJS! Declare typed variables and initialize them later with full type safety:
const config = Type.string().ref(); // Uninitialized
console.log(config.isInitialized); // false
config.value = 'production'; // Initialize later
console.log(config.value); // 'production'
config.lock(); // Make immutablePerfect for:
- Configuration loaded asynchronously
- Dependency injection patterns
- Circular dependencies
- Deferred initialization for performance
🎯 Comprehensive Type System
Complete support for all JavaScript types and beyond:
- Primitives: string, number, boolean, bigint, symbol, null, undefined
- Collections: array, tuple, set, map, object, record
- Advanced: union, intersection, literal, enum
- Special: date, regexp, function, promise
- Exotic: prime, fibonacci, even, odd, int8/16/32
🔐 Immutability Control
Deep freeze objects and nested structures:
const schema = Type.object({
config: Type.object({ enabled: Type.boolean() })
}).immutable();
const data = schema.parse({ config: { enabled: true } });
Object.isFrozen(data.config); // true - deeply frozenFeature Comparison
| Feature | TypeSwiftJS | Zod | Yup | Joi | io-ts | |---------|-------------|-----|-----|-----|-------| | Zero Dependencies | ✅ | ✅ | ❌ | ❌ | ❌ | | TypeScript Native | ✅ | ✅ | ⚠️ Partial | ❌ | ✅ | | Bundle Size | Optimized | Medium | Medium | Large | Small | | Tree-Shakeable | ✅ | ✅ | ✅ | ❌ | ✅ | | Late Initialization (Java-style) | ✅ | ❌ | ❌ | ❌ | ❌ | | Advanced Number Validators | ✅ (prime, fibonacci, even, odd) | ❌ | ❌ | ❌ | ❌ | | Algebraic Types (Dual API) | ✅ (Maybe, Either, Result) | ❌ | ❌ | ❌ | Limited | | Pattern Matching | ✅ | ❌ | ❌ | ❌ | ❌ | | C-style Integer Types | ✅ (int8, int16, int32) | ❌ | ❌ | ❌ | ❌ | | Native i18n | ✅ (3 languages) | ⚠️ Limited | ✅ | ✅ | ❌ | | Immutability Control | ✅ Full (deep freeze) | ⚠️ Partial | ❌ | ❌ | ❌ | | Typed References | ✅ (TypedRef) | ❌ | ❌ | ❌ | ❌ | | Extended String Validators | ✅ (IBAN, ISBN, EAN, etc.) | ⚠️ Basic | ⚠️ Basic | ⚠️ Basic | ❌ | | Generic Types | ✅ | ✅ | ❌ | ❌ | ✅ | | JSON Schema Export | ✅ | ✅ | ✅ | ❌ | ❌ | | Coercion Utilities | ✅ | ✅ | ✅ | ✅ | ❌ | | Async Validation | ✅ | ✅ | ✅ | ✅ | ⚠️ | | Custom Error Messages | ✅ | ✅ | ✅ | ✅ | ⚠️ | | Transformations | ✅ | ✅ | ✅ | ❌ | ⚠️ |
📦 Installation
Install TypeSwiftJS using your preferred package manager:
npm
npm install @yohannawf/typeswiftjsyarn
yarn add @yohannawf/typeswiftjspnpm
pnpm add @yohannawf/typeswiftjsRequirements
- Node.js: ≥16.0.0
- TypeScript: ≥5.0.0 (optional, for TypeScript projects)
🚀 Quick Start
Get started with TypeSwiftJS in under 2 minutes:
Basic Validation
const { Type } = require('@yohannawf/typeswiftjs');
// String validation
const nameSchema = Type.string().min(3).max(50);
const name = nameSchema.parse('John Doe'); // ✅ "John Doe"
// Email validation
const emailSchema = Type.string().email();
const email = emailSchema.parse('[email protected]'); // ✅
// Number with constraints
const ageSchema = Type.number().int().positive().max(120);
const age = ageSchema.parse(25); // ✅ 25Object Validation
const userSchema = Type.object({
name: Type.string().min(3),
email: Type.string().email(),
age: Type.number().int().min(18),
role: Type.enum(['admin', 'user', 'guest']).default('user'),
});
const user = userSchema.parse({
name: 'John Doe',
email: '[email protected]',
age: 25,
}); // ✅ { name: 'John Doe', email: '[email protected]', age: 25, role: 'user' }Safe Parsing (No Exceptions)
const result = ageSchema.safeParse(-5);
if (result.success) {
console.log('Valid:', result.data);
} else {
console.log('Invalid:', result.error.message);
// Access detailed error information
result.error.issues.forEach(issue => {
console.log(`Path: ${issue.path.join('.')}`);
console.log(`Message: ${issue.message}`);
});
}TypeScript Integration
import { Type, Infer } from '@yohannawf/typeswiftjs';
const userSchema = Type.object({
id: Type.number(),
username: Type.string(),
email: Type.string().email(),
createdAt: Type.date(),
});
// Automatically infer TypeScript type
type User = Infer<typeof userSchema>;
// {
// id: number;
// username: string;
// email: string;
// createdAt: Date;
// }
// Type-safe function
function createUser(data: unknown): User {
return userSchema.parse(data); // Validates and returns typed data
}📥 Importing
TypeSwiftJS offers flexible import options to suit your coding style:
1. Type Namespace (Recommended)
The most common and recommended approach:
const { Type } = require('@yohannawf/typeswiftjs');
const stringSchema = Type.string();
const numberSchema = Type.number();
const objectSchema = Type.object({
name: Type.string(),
age: Type.number(),
});Why use this?
- Clean, readable code
- Single import
- All types accessible through one namespace
- Works great with IDE autocomplete
2. Schema Namespace
Alternative to Type, functionally identical:
const { Schema } = require('@yohannawf/typeswiftjs');
const stringSchema = Schema.string();
const numberSchema = Schema.number();3. Direct Factory Imports
Import specific factory functions for a lighter footprint:
const { string, number, object, array } = require('@yohannawf/typeswiftjs');
const nameSchema = string().min(3);
const ageSchema = number().positive();
const userSchema = object({
name: nameSchema,
age: ageSchema,
});When to use:
- When you only need a few types
- Tree-shaking optimization
- Cleaner imports for specific use cases
4. Class Imports
Direct class imports for advanced use cases:
const { StringType, NumberType, ObjectType } = require('@yohannawf/typeswiftjs');
const nameSchema = new StringType().min(3);
const ageSchema = new NumberType().positive();5. Error Handling
Import error classes and utilities:
const {
TypeSwiftError, // Main error class
UninitializedError, // For uninitialized TypedRef
ImmutableError, // For immutable value violations
setLocale, // Set error message language
getLocale // Get current language
} = require('@yohannawf/typeswiftjs');
try {
schema.parse(invalidData);
} catch (error) {
if (TypeSwiftError.isTypeSwiftError(error)) {
console.log(error.issues); // Detailed validation issues
}
}6. Utility Functions
Import validation and utility functions:
const {
isPrime,
isFibonacci,
isValidEmail,
isValidUrl,
isValidUuid,
isValidIp,
deepClone,
deepFreeze
} = require('@yohannawf/typeswiftjs');
console.log(isPrime(7)); // true
console.log(isFibonacci(13)); // true
console.log(isValidEmail('[email protected]')); // true7. Coercion Utilities
Type coercion helpers for loose type conversion:
const {
coerceString,
coerceNumber,
coerceBoolean,
coerceBigInt,
coerceDate
} = require('@yohannawf/typeswiftjs');
coerceBoolean('true'); // true
coerceNumber('42'); // 42
coerceDate('2024-01-01'); // Date object8. Type Inference Helpers (TypeScript)
Utility types for extracting TypeScript types from schemas:
import { Infer, InferInput, InferOutput } from '@yohannawf/typeswiftjs';
const schema = Type.string().transform(s => s.length);
type Input = InferInput<typeof schema>; // string
type Output = InferOutput<typeof schema>; // number
type Default = Infer<typeof schema>; // number (same as Output)9. Algebraic Types
Import algebraic data types and pattern matching with dual API (Haskell + Rust/Option styles):
const {
Maybe,
Either,
Result,
matchMaybe,
matchEither,
matchResult
} = require('@yohannawf/typeswiftjs');
// Haskell style (original)
const value1 = Maybe.just(42);
const result1 = matchMaybe(value1, {
just: (x) => x * 2,
nothing: () => 0,
}); // 84
// Rust/Option style (aliases)
const value2 = Maybe.some(42);
const result2 = matchMaybe(value2, {
some: (x) => x * 2,
none: () => 0,
}); // 84
// Both work identically - choose your preferred style!ES Modules (ESM)
For ES modules, use import instead of require:
import { Type } from '@yohannawf/typeswiftjs';
import { string, number } from '@yohannawf/typeswiftjs';
import type { Infer } from '@yohannawf/typeswiftjs';🔤 Core Types
TypeSwiftJS provides comprehensive support for all JavaScript primitive types with powerful validation and transformation capabilities.
String Types
Strings are one of the most common data types. TypeSwiftJS provides extensive string validation:
Basic String
const schema = Type.string();
schema.parse('hello'); // ✅ "hello"
schema.parse(123); // ❌ TypeSwiftError: Invalid type
schema.parse(null); // ❌ TypeSwiftError: Invalid typeLength Constraints
Control string length with precision:
// Minimum length
Type.string().min(3).parse('hello'); // ✅
Type.string().min(3).parse('hi'); // ❌ Too short
// Maximum length
Type.string().max(10).parse('hello'); // ✅
Type.string().max(10).parse('this is way too long'); // ❌ Too long
// Exact length
Type.string().length(5).parse('hello'); // ✅
Type.string().length(5).parse('hi'); // ❌ Wrong length
// Range (min and max)
Type.string().min(3).max(10).parse('hello'); // ✅
Type.string().min(3).max(10).parse('hi'); // ❌
Type.string().min(3).max(10).parse('this is too long'); // ❌String Format Validations
Built-in validators for common string formats:
// Email (RFC 5322 compliant)
Type.string().email().parse('[email protected]'); // ✅
Type.string().email().parse('invalid-email'); // ❌
// URL (supports http, https, ftp)
Type.string().url().parse('https://example.com'); // ✅
Type.string().url().parse('not-a-url'); // ❌
// UUID (v1, v3, v4, v5)
Type.string().uuid().parse('550e8400-e29b-41d4-a716-446655440000'); // ✅
Type.string().uuid().parse('not-a-uuid'); // ❌
// CUID (Collision-resistant Unique ID)
Type.string().cuid().parse('ck1234567890abcdefghij'); // ✅
// ULID (Universally Unique Lexicographically Sortable ID)
Type.string().ulid().parse('01ARZ3NDEKTSV4RRFFQ69G5FAV'); // ✅
// Custom Regex
Type.string().regex(/^\d{3}-\d{4}$/).parse('123-4567'); // ✅
Type.string().regex(/^\d{3}-\d{4}$/, 'Invalid format').parse('abc'); // ❌
// DateTime ISO 8601
Type.string().datetime().parse('2024-01-01T12:00:00Z'); // ✅
Type.string().datetime().parse('not-a-date'); // ❌
// IP Address
Type.string().ip().parse('192.168.1.1'); // ✅ (IPv4 or IPv6)
Type.string().ipv4().parse('192.168.1.1'); // ✅ (IPv4 only)
Type.string().ipv6().parse('2001:0db8:85a3::8a2e:0370:7334'); // ✅ (IPv6 only)String Transformations
Transform strings during validation:
// Trim whitespace
Type.string().trim().parse(' hello '); // "hello"
// Convert to lowercase
Type.string().lowercase().parse('HELLO'); // "hello"
// Convert to uppercase
Type.string().uppercase().parse('hello'); // "HELLO"
// Chain transformations
const normalizedEmail = Type.string()
.email()
.trim()
.lowercase();
normalizedEmail.parse(' [email protected] '); // "[email protected]"
// Custom transformation
Type.string()
.transform(s => s.replace(/\s+/g, '-'))
.parse('hello world'); // "hello-world"Character Type
For single-character validation:
// Single character only
Type.char().parse('A'); // ✅ 'A'
Type.char().parse('AB'); // ❌ Too many characters
Type.char().parse(''); // ❌ Empty stringCommon String Patterns
// Username (alphanumeric + underscore)
const usernameSchema = Type.string()
.min(3)
.max(20)
.regex(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores allowed');
// Phone number (simple US format)
const phoneSchema = Type.string()
.regex(/^\d{3}-\d{3}-\d{4}$/, 'Format: XXX-XXX-XXXX');
// Hex color
const hexColorSchema = Type.string()
.regex(/^#[0-9A-Fa-f]{6}$/, 'Must be a valid hex color');
// Credit card (basic validation)
const creditCardSchema = Type.string()
.regex(/^\d{4}-\d{4}-\d{4}-\d{4}$/, 'Format: XXXX-XXXX-XXXX-XXXX');Number Types
TypeSwiftJS provides rich number validation with specialized types:
Basic Number
const schema = Type.number();
schema.parse(42); // ✅ 42
schema.parse(3.14); // ✅ 3.14
schema.parse(-10); // ✅ -10
schema.parse('123'); // ❌ String not accepted
schema.parse(NaN); // ❌ NaN not acceptedNumber Constraints
// Minimum value
Type.number().min(0).parse(10); // ✅
Type.number().min(0).parse(-5); // ❌
// Maximum value
Type.number().max(100).parse(50); // ✅
Type.number().max(100).parse(150); // ❌
// Range
Type.number().min(0).max(100).parse(50); // ✅
Type.number().between(0, 100).parse(75); // ✅ (same as min/max)
// Greater than (exclusive)
Type.number().gt(0).parse(1); // ✅
Type.number().gt(0).parse(0); // ❌
// Greater than or equal
Type.number().gte(0).parse(0); // ✅
// Less than (exclusive)
Type.number().lt(100).parse(99); // ✅
Type.number().lt(100).parse(100); // ❌
// Less than or equal
Type.number().lte(100).parse(100); // ✅
// Percentage (0-100)
Type.number().percentage().parse(75); // ✅
Type.number().percentage().parse(150); // ❌Integer Validation
// Integer only (no decimals)
Type.number().int().parse(42); // ✅
Type.number().int().parse(3.14); // ❌
// Or use dedicated int type
Type.int().parse(42); // ✅
Type.int().parse(3.14); // ❌Sign Constraints
// Positive (> 0)
Type.number().positive().parse(10); // ✅
Type.number().positive().parse(-5); // ❌
Type.number().positive().parse(0); // ❌
// Negative (< 0)
Type.number().negative().parse(-10); // ✅
Type.number().negative().parse(5); // ❌
Type.number().negative().parse(0); // ❌
// Non-negative (>= 0)
Type.number().nonnegative().parse(0); // ✅
Type.number().nonnegative().parse(10); // ✅
Type.number().nonnegative().parse(-5); // ❌
// Non-positive (<= 0)
Type.number().nonpositive().parse(0); // ✅
Type.number().nonpositive().parse(-10); // ✅
Type.number().nonpositive().parse(5); // ❌Special Number Validations
// Finite (not Infinity/-Infinity)
Type.number().finite().parse(1000); // ✅
Type.number().finite().parse(Infinity); // ❌
Type.number().finite().parse(-Infinity); // ❌
// Safe integer (within Number.MAX_SAFE_INTEGER)
Type.number().safe().parse(1000); // ✅
Type.number().safe().parse(Number.MAX_SAFE_INTEGER); // ✅
Type.number().safe().parse(Number.MAX_SAFE_INTEGER + 1); // ❌
// Multiple of
Type.number().multipleOf(5).parse(15); // ✅
Type.number().multipleOf(5).parse(17); // ❌
Type.number().multipleOf(0.5).parse(1.5); // ✅Advanced Number Validators
Unique to TypeSwiftJS! Mathematical validators not found in other libraries:
// Even numbers
Type.number().even().parse(8); // ✅
Type.number().even().parse(7); // ❌
Type.even().parse(8); // ✅ (dedicated type)
// Odd numbers
Type.number().odd().parse(7); // ✅
Type.number().odd().parse(8); // ❌
Type.odd().parse(7); // ✅ (dedicated type)
// Prime numbers (uses Miller-Rabin primality test)
Type.number().prime().parse(7); // ✅
Type.number().prime().parse(8); // ❌
Type.prime().parse(17); // ✅ (dedicated type)
// Fibonacci numbers
Type.number().fibonacci().parse(13); // ✅ (1,1,2,3,5,8,13,21...)
Type.number().fibonacci().parse(12); // ❌
Type.fibonacci().parse(21); // ✅ (dedicated type)Specialized Integer Types
C-style integer types with overflow/underflow checking:
// Int8 (-128 to 127)
Type.int8().parse(100); // ✅
Type.int8().parse(200); // ❌ Overflow error
// Int16 (-32768 to 32767)
Type.int16().parse(1000); // ✅
Type.int16().parse(40000); // ❌ Overflow error
// Int32 (-2147483648 to 2147483647)
Type.int32().parse(1000000); // ✅
Type.int32().parse(3000000000); // ❌ Overflow error
// Positive integers
Type.positiveInt().parse(5); // ✅
Type.positiveInt().parse(-5); // ❌
Type.positiveInt().parse(0); // ❌
// Negative integers
Type.negativeInt().parse(-5); // ✅
Type.negativeInt().parse(5); // ❌
Type.negativeInt().parse(0); // ❌Float Type
const floatSchema = Type.float();
floatSchema.parse(3.14159); // ✅
floatSchema.parse(42); // ✅
// With precision (rounds to specified decimal places)
const preciseFloat = Type.float().precision(2);
preciseFloat.parse(3.14159); // 3.14
preciseFloat.parse(2.999); // 3.00BigInt Type
For numbers beyond JavaScript's safe integer range:
const bigIntSchema = Type.bigint();
// From bigint literal
bigIntSchema.parse(9007199254740991n); // ✅
// From safe integer number
bigIntSchema.parse(42); // ✅ Converts to 42n
// From string
bigIntSchema.parse('9007199254740991'); // ✅ Parses to bigint
// With constraints
Type.bigint()
.min(0n)
.max(1000000n)
.parse(500000n); // ✅
// Positive/Negative
Type.bigint().positive().parse(100n); // ✅
Type.bigint().negative().parse(-100n); // ✅Practical Number Examples
// Age validation
const ageSchema = Type.number()
.int()
.min(0)
.max(150);
// Price (cents)
const priceSchema = Type.number()
.int()
.nonnegative()
.multipleOf(1);
// Percentage with decimals
const percentageSchema = Type.number()
.min(0)
.max(100)
.precision(2);
// Rating (1-5 stars)
const ratingSchema = Type.number()
.int()
.min(1)
.max(5);
// Temperature (Celsius)
const temperatureSchema = Type.number()
.min(-273.15) // Absolute zero
.precision(1);Boolean Type
// Basic boolean
Type.boolean().parse(true); // ✅
Type.boolean().parse(false); // ✅
Type.boolean().parse('true'); // ❌ String not accepted by default
// Boolean coercion (loose validation)
const coercedSchema = Type.boolean().coerce();
coercedSchema.parse('true'); // true
coercedSchema.parse('false'); // false
coercedSchema.parse('yes'); // true
coercedSchema.parse('no'); // false
coercedSchema.parse('1'); // true
coercedSchema.parse('0'); // false
coercedSchema.parse(1); // true
coercedSchema.parse(0); // false
coercedSchema.parse(''); // false (falsy)
coercedSchema.parse('anything else'); // true (truthy)
// Specific value validation
Type.boolean().true().parse(true); // ✅
Type.boolean().true().parse(false); // ❌
Type.boolean().false().parse(false); // ✅
Type.boolean().false().parse(true); // ❌Date Type
Comprehensive date validation with timezone support:
// Basic date
Type.date().parse(new Date()); // ✅
Type.date().parse('2024-01-01'); // ✅ Parses to Date
Type.date().parse(1704067200000); // ✅ From timestamp
// Future dates only
Type.date().future().parse(new Date('2030-01-01')); // ✅
Type.date().future().parse(new Date('2020-01-01')); // ❌
// Past dates only
Type.date().past().parse(new Date('2020-01-01')); // ✅
Type.date().past().parse(new Date('2030-01-01')); // ❌
// With custom error message
Type.date()
.past('Birth date must be in the past')
.parse(new Date('2030-01-01')); // ❌ Shows custom message
// Minimum date
Type.date()
.min(new Date('2020-01-01'))
.parse(new Date('2023-06-15')); // ✅
// Maximum date
Type.date()
.max(new Date('2025-12-31'))
.parse(new Date('2023-06-15')); // ✅
// Date range
Type.date()
.between(new Date('2020-01-01'), new Date('2025-12-31'))
.parse(new Date('2023-06-15')); // ✅
// Practical examples
const birthDateSchema = Type.date()
.past()
.min(new Date('1900-01-01'));
const appointmentSchema = Type.date()
.future()
.min(new Date()); // Must be in the futureOther Primitive Types
// Symbol
Type.symbol().parse(Symbol('test')); // ✅
Type.symbol().parse('string'); // ❌
// Null
Type.null().parse(null); // ✅
Type.null().parse(undefined); // ❌
// Undefined
Type.undefined().parse(undefined); // ✅
Type.undefined().parse(null); // ❌
// Any (accepts everything - use sparingly!)
Type.any().parse('anything'); // ✅
Type.any().parse(123); // ✅
Type.any().parse(null); // ✅
Type.any().parse(undefined); // ✅
// Unknown (safer than any - forces type checking)
Type.unknown().parse('anything'); // ✅
const value = Type.unknown().parse(data);
// Must check type before using
// Never (never valid - useful for exhaustive checks)
Type.never().parse('anything'); // ❌ Always throws
// Void (only undefined - for functions that return nothing)
Type.void().parse(undefined); // ✅
Type.void().parse(null); // ❌
// RegExp
Type.regexp().parse(/test/); // ✅
Type.regexp().parse(new RegExp('test')); // ✅
Type.regexp().parse('^test$'); // ✅ Parses string to RegExp🔧 Advanced Types
Literal Types
Validate exact values:
// String literal
const adminRole = Type.literal('admin');
adminRole.parse('admin'); // ✅
adminRole.parse('user'); // ❌
// Number literal
const magicNumber = Type.literal(42);
magicNumber.parse(42); // ✅
magicNumber.parse(43); // ❌
// Boolean literal
const trueOnly = Type.literal(true);
trueOnly.parse(true); // ✅
trueOnly.parse(false); // ❌
// Null literal
const nullOnly = Type.literal(null);
nullOnly.parse(null); // ✅
nullOnly.parse(undefined); // ❌
// Practical use - API versions
const apiVersion = Type.literal('v1');
const endpoint = Type.object({
version: apiVersion,
path: Type.string(),
});Enum Types
String Enum
// Basic enum
const roleSchema = Type.enum(['admin', 'user', 'guest']);
roleSchema.parse('admin'); // ✅
roleSchema.parse('superadmin'); // ❌
// Check if value is in enum
roleSchema.includes('admin'); // true
roleSchema.includes('superadmin'); // false
// Get all enum values
console.log(roleSchema.values); // ['admin', 'user', 'guest']
// Extract specific values (create subset)
const adminOrUser = roleSchema.extract('admin', 'user');
adminOrUser.parse('admin'); // ✅
adminOrUser.parse('guest'); // ❌
// Exclude values (create subset without certain values)
const notGuest = roleSchema.exclude('guest');
notGuest.parse('admin'); // ✅
notGuest.parse('guest'); // ❌Enum with Methods (Java-style)
Unique to TypeSwiftJS!
const statusEnum = Type.enum(['pending', 'approved', 'rejected'])
.withMethods({
canEdit: (status) => status === 'pending',
isComplete: (status) => status !== 'pending',
getColor: (status) => {
const colors = {
pending: 'yellow',
approved: 'green',
rejected: 'red',
};
return colors[status];
},
});
// Use enum methods
statusEnum.methods.canEdit('pending'); // true
statusEnum.methods.canEdit('approved'); // false
statusEnum.methods.getColor('approved'); // 'green'Native TypeScript Enum
enum UserRole {
Admin = 'admin',
User = 'user',
Guest = 'guest',
}
const roleSchema = Type.nativeEnum(UserRole);
roleSchema.parse(UserRole.Admin); // ✅
roleSchema.parse('admin'); // ✅
roleSchema.parse('superadmin'); // ❌
// Also works with numeric enums
enum Status {
Pending, // 0
Approved, // 1
Rejected, // 2
}
const statusSchema = Type.nativeEnum(Status);
statusSchema.parse(Status.Pending); // ✅
statusSchema.parse(0); // ✅
statusSchema.parse(3); // ❌Union Types
Validate against multiple possible types:
// Union of literals
const roleSchema = Type.union(
Type.literal('admin'),
Type.literal('user'),
Type.literal('guest')
);
roleSchema.parse('admin'); // ✅
roleSchema.parse('superadmin'); // ❌
// Union of types
const stringOrNumber = Type.union(
Type.string(),
Type.number()
);
stringOrNumber.parse('hello'); // ✅
stringOrNumber.parse(42); // ✅
stringOrNumber.parse(true); // ❌
// Complex unions
const idSchema = Type.union(
Type.number().int().positive(),
Type.string().uuid()
);
idSchema.parse(123); // ✅ Number ID
idSchema.parse('550e8400-e29b-41d4-a716-446655440000'); // ✅ UUID
// Discriminated Union (tagged union)
const eventSchema = Type.union(
Type.object({
type: Type.literal('click'),
x: Type.number(),
y: Type.number(),
}),
Type.object({
type: Type.literal('keypress'),
key: Type.string(),
}),
Type.object({
type: Type.literal('scroll'),
delta: Type.number(),
})
).discriminatedBy('type');
// More efficient - uses 'type' field to quickly identify which schema to use
eventSchema.parse({ type: 'click', x: 100, y: 200 }); // ✅
eventSchema.parse({ type: 'keypress', key: 'Enter' }); // ✅
eventSchema.parse({ type: 'unknown', data: 'test' }); // ❌Intersection Types
Combine multiple schemas:
const basicUser = Type.object({
id: Type.number(),
name: Type.string(),
});
const timestamps = Type.object({
createdAt: Type.date(),
updatedAt: Type.date(),
});
// Intersection combines both schemas
const userSchema = Type.intersection(basicUser, timestamps);
// Must satisfy both schemas
userSchema.parse({
id: 1,
name: 'John',
createdAt: new Date(),
updatedAt: new Date(),
}); // ✅
// Practical example - adding metadata
const withMetadata = Type.intersection(
Type.object({ data: Type.any() }),
Type.object({
metadata: Type.object({
version: Type.string(),
source: Type.string(),
}),
})
);📚 Collections
Array Type
Validate arrays with typed elements:
// Basic array
const numbersSchema = Type.array(Type.number());
numbersSchema.parse([1, 2, 3]); // ✅
numbersSchema.parse([1, 'two', 3]); // ❌ Mixed types not allowed
// Length constraints
Type.array(Type.string()).min(1).parse(['a']); // ✅
Type.array(Type.string()).min(1).parse([]); // ❌ Too short
Type.array(Type.string()).max(5).parse(['a', 'b']); // ✅
Type.array(Type.string()).max(5).parse(['a', 'b', 'c', 'd', 'e', 'f']); // ❌
Type.array(Type.string()).length(3).parse(['a', 'b', 'c']); // ✅ Exact length
Type.array(Type.string()).length(3).parse(['a', 'b']); // ❌
// Non-empty array
Type.array(Type.number()).nonempty().parse([1, 2]); // ✅
Type.array(Type.number()).nonempty().parse([]); // ❌
// Unique elements
Type.array(Type.number()).unique().parse([1, 2, 3]); // ✅
Type.array(Type.number()).unique().parse([1, 2, 2]); // ❌ Duplicate found
// Array transformations
Type.array(Type.number()).sorted().parse([3, 1, 2]); // [1, 2, 3]
Type.array(Type.number()).reversed().parse([1, 2, 3]); // [3, 2, 1]
Type.array(Type.number().nullable()).compact().parse([1, null, 2, undefined, 3]); // [1, 2, 3]
// Custom sort
Type.array(Type.object({ age: Type.number() }))
.sorted((a, b) => a.age - b.age)
.parse([{ age: 30 }, { age: 20 }, { age: 25 }]);
// [{ age: 20 }, { age: 25 }, { age: 30 }]
// Complex example - validating API response
const postsSchema = Type.array(
Type.object({
id: Type.number(),
title: Type.string().min(1).max(200),
author: Type.string(),
tags: Type.array(Type.string()).optional(),
createdAt: Type.date(),
})
).min(1).max(100);Tuple Type
Fixed-length arrays with different types:
// Basic tuple (x, y coordinates)
const coordinateSchema = Type.tuple([
Type.number(), // x
Type.number(), // y
]);
coordinateSchema.parse([10, 20]); // ✅ [10, 20]
coordinateSchema.parse([10, 20, 30]); // ❌ Too many elements
coordinateSchema.parse([10]); // ❌ Too few elements
// Tuple with different types
const userTuple = Type.tuple([
Type.string(), // name
Type.number(), // age
Type.boolean(), // active
]);
userTuple.parse(['John', 25, true]); // ✅
// Tuple with rest elements (variable length at end)
const csvRow = Type.tuple([
Type.string(), // id (required)
Type.string(), // name (required)
]).rest(Type.string()); // Additional columns (variable)
csvRow.parse(['1', 'John', 'extra', 'data', 'here']); // ✅
// Practical example - RGB color
const rgbSchema = Type.tuple([
Type.number().int().min(0).max(255), // red
Type.number().int().min(0).max(255), // green
Type.number().int().min(0).max(255), // blue
]);
rgbSchema.parse([255, 0, 128]); // ✅
// Function signature as tuple
const signatureSchema = Type.tuple([
Type.string(), // function name
Type.array(Type.string()), // parameters
Type.string(), // return type
]);Set Type
Validate Sets with typed elements:
// Basic set
const tagsSchema = Type.set(Type.string());
tagsSchema.parse(new Set(['tag1', 'tag2'])); // ✅
// Auto-converts arrays to Sets
tagsSchema.parse(['tag1', 'tag2']); // ✅ Becomes Set
// Size constraints
Type.set(Type.string()).min(1).parse(new Set(['tag1'])); // ✅
Type.set(Type.string()).max(5).parse(new Set(['tag1', 'tag2'])); // ✅
Type.set(Type.string()).nonempty().parse(new Set(['tag1'])); // ✅
Type.set(Type.string()).nonempty().parse(new Set()); // ❌
// Sets automatically handle uniqueness
const numberSet = Type.set(Type.number());
numberSet.parse([1, 2, 2, 3]); // Set { 1, 2, 3 } - duplicates removedMap Type
Validate Maps with typed keys and values:
// Basic map
const userMapSchema = Type.map(
Type.string(), // key type
Type.number() // value type
);
userMapSchema.parse(new Map([['age', 25]])); // ✅
// Auto-converts objects to Maps
userMapSchema.parse({ age: 25, score: 100 }); // ✅ Becomes Map
// Auto-converts arrays of entries
userMapSchema.parse([['age', 25], ['score', 100]]); // ✅ Becomes Map
// Size constraints
Type.map(Type.string(), Type.number()).min(1).parse(new Map([['age', 25]])); // ✅
Type.map(Type.string(), Type.number()).max(10).parse(new Map([['a', 1]])); // ✅
Type.map(Type.string(), Type.number()).nonempty().parse(new Map()); // ❌
// Complex types
const cacheSchema = Type.map(
Type.string().uuid(), // UUID keys
Type.object({
data: Type.any(),
expires: Type.date(),
})
);Object Type
The most powerful type for validating complex structures:
// Basic object
const userSchema = Type.object({
name: Type.string(),
age: Type.number(),
email: Type.string().email(),
});
userSchema.parse({
name: 'John',
age: 25,
email: '[email protected]',
}); // ✅
// Nested objects
const addressSchema = Type.object({
street: Type.string(),
city: Type.string(),
country: Type.string(),
zipCode: Type.string().regex(/^\d{5}$/),
});
const personSchema = Type.object({
name: Type.string(),
age: Type.number(),
address: addressSchema, // Nested schema
contacts: Type.array(
Type.object({
type: Type.enum(['email', 'phone']),
value: Type.string(),
})
),
});
// Optional fields
const optionalUserSchema = Type.object({
name: Type.string(),
age: Type.number().optional(),
email: Type.string().email().optional(),
});
// Partial (all fields optional)
const partialUserSchema = userSchema.partial();
// All fields become optional
// Make specific fields optional
const partiallyOptionalSchema = userSchema.partialKeys('age', 'email');
// Only 'age' and 'email' are optional, 'name' is still required
// Pick specific fields
const nameAndEmailSchema = userSchema.pick('name', 'email');
// New schema with only 'name' and 'email' fields
// Omit fields
const withoutAgeSchema = userSchema.omit('age');
// New schema without 'age' field
// Extend with new fields
const userWithTimestamps = userSchema.extend({
createdAt: Type.date(),
updatedAt: Type.date(),
});
// Merge with another schema
const timestampsSchema = Type.object({
createdAt: Type.date(),
updatedAt: Type.date(),
});
const merged = userSchema.merge(timestampsSchema);
// Strict mode (no extra keys allowed)
const strictUserSchema = userSchema.strict();
strictUserSchema.parse({
name: 'John',
age: 25,
email: '[email protected]',
extraKey: 'value', // ❌ Error - unknown key
});
// Passthrough (keep extra keys)
const passthroughSchema = userSchema.passthrough();
passthroughSchema.parse({
name: 'John',
age: 25,
email: '[email protected]',
extraKey: 'value', // ✅ Kept in result
});
// Strip (default - remove extra keys silently)
const stripSchema = userSchema.strip(); // or just userSchema
stripSchema.parse({
name: 'John',
age: 25,
email: '[email protected]',
extraKey: 'value', // Silently removed
});
// Catchall (validate extra keys with a type)
const catchallSchema = userSchema.catchall(Type.string());
catchallSchema.parse({
name: 'John',
age: 25,
email: '[email protected]',
extra1: 'value', // ✅ Must be string
extra2: 'another', // ✅ Must be string
extra3: 123, // ❌ Must be string
});
// Default values
const userWithDefaults = Type.object({
name: Type.string(),
role: Type.string().default('user'),
active: Type.boolean().default(true),
settings: Type.object({
theme: Type.string().default('light'),
notifications: Type.boolean().default(true),
}).default({}),
});
userWithDefaults.parse({ name: 'John' });
// {
// name: 'John',
// role: 'user',
// active: true,
// settings: { theme: 'light', notifications: true }
// }
// Dependent validation (fields depend on each other)
const passwordSchema = Type.object({
password: Type.string().min(8),
confirmPassword: Type.string(),
}).dependent('confirmPassword', (confirmPassword, context) => {
if (confirmPassword !== context.password) {
return 'Passwords must match';
}
return true;
});
// Complex real-world example
const userRegistrationSchema = Type.object({
// Basic info
username: Type.string()
.min(3)
.max(20)
.regex(/^[a-zA-Z0-9_]+$/),
email: Type.string().email().trim().lowercase(),
password: Type.string()
.min(8)
.refine(s => /[A-Z]/.test(s), 'Must have uppercase')
.refine(s => /[0-9]/.test(s), 'Must have number'),
// Profile
profile: Type.object({
firstName: Type.string().min(1),
lastName: Type.string().min(1),
birthDate: Type.date().past(),
avatar: Type.string().url().optional(),
}),
// Preferences
preferences: Type.object({
language: Type.enum(['en', 'es', 'pt']).default('en'),
timezone: Type.string().default('UTC'),
newsletter: Type.boolean().default(false),
}).optional(),
// Terms
acceptTerms: Type.boolean().true(),
acceptPrivacy: Type.boolean().true(),
});Record Type
For objects with typed keys and values:
// Basic record (dictionary/map-like structure)
const scoresSchema = Type.record(
Type.string(), // key type
Type.number() // value type
);
scoresSchema.parse({
math: 95,
english: 88,
science: 92,
}); // ✅
scoresSchema.parse({
math: 95,
english: 'A+', // ❌ Value must be number
});
// With number keys
const arrayLikeSchema = Type.record(
Type.number().int().nonnegative(),
Type.string()
);
arrayLikeSchema.parse({
0: 'first',
1: 'second',
2: 'third',
}); // ✅
// Complex example - user preferences
const userPreferencesSchema = Type.record(
Type.string().regex(/^[a-z_]+$/), // snake_case keys
Type.union(
Type.string(),
Type.number(),
Type.boolean()
)
);
userPreferencesSchema.parse({
theme_color: '#FF0000',
font_size: 14,
auto_save: true,
}); // ✅🎭 Algebraic Types
Unique to TypeSwiftJS! Algebraic data types from functional programming with dual naming conventions supporting both Haskell and Rust/Option styles.
Maybe Type (Optional)
Represents a value that may or may not exist (like Rust's Option or Haskell's Maybe):
const { Maybe, matchMaybe } = require('@yohannawf/typeswiftjs');
// Create Maybe values - BOTH styles work!
// Haskell style (original)
const someValue = Maybe.just(42);
const noValue = Maybe.nothing();
// Rust/Option style (aliases - same behavior)
const someValue2 = Maybe.some(42);
const noValue2 = Maybe.none();
// Check if has value - BOTH styles work!
console.log(Maybe.isJust(someValue)); // true (Haskell style)
console.log(Maybe.isSome(someValue)); // true (Rust style - same result!)
console.log(Maybe.isNothing(noValue)); // true (Haskell style)
console.log(Maybe.isNone(noValue)); // true (Rust style - same result!)
// Get value (throws if None)
console.log(Maybe.unwrap(someValue)); // 42
// Maybe.unwrap(noValue); // ❌ Throws error
// Get value with fallback
console.log(Maybe.getOrElse(someValue, 0)); // 42
console.log(Maybe.getOrElse(noValue, 0)); // 0
// Map over value (only if Some)
const doubled = Maybe.map(someValue, x => x * 2);
console.log(Maybe.unwrap(doubled)); // 84
const stillNone = Maybe.map(noValue, x => x * 2);
console.log(Maybe.isNone(stillNone)); // true
// FlatMap (for chaining Maybe operations)
const result = Maybe.flatMap(someValue, x =>
x > 40 ? Maybe.some(x * 2) : Maybe.none()
);
console.log(Maybe.unwrap(result)); // 84
// Pattern matching - accepts BOTH naming styles!
const message1 = matchMaybe(someValue, {
just: (value) => `Value is ${value}`,
nothing: () => 'No value found',
});
console.log(message1); // "Value is 42"
// OR use Rust/Option style
const message2 = matchMaybe(someValue, {
some: (value) => `Value is ${value}`,
none: () => 'No value found',
});
console.log(message2); // "Value is 42"
// Schema validation
// ⚠️ IMPORTANT: parse() returns MaybeValue<T> (plain object)
const maybeNumberSchema = Type.maybe(Type.number());
maybeNumberSchema.parse(Maybe.just(42)); // ✅
maybeNumberSchema.parse(Maybe.some(42)); // ✅ (alias works too!)
maybeNumberSchema.parse(Maybe.nothing()); // ✅
maybeNumberSchema.parse(Maybe.none()); // ✅ (alias works too!)
maybeNumberSchema.parse(null); // ✅ Converts to Maybe.nothing()
maybeNumberSchema.parse(undefined); // ✅ Converts to Maybe.nothing()
maybeNumberSchema.parse(42); // ❌ Must be wrapped in Maybe
// Practical example - user lookup
function findUser(id) {
const user = database.findById(id);
return user ? Maybe.some(user) : Maybe.none();
}
const userResult = findUser(123);
const userName = matchMaybe(userResult, {
some: (user) => user.name,
none: () => 'Guest User',
});Note: schema.parse() returns a MaybeValue<T> object ({ _tag: 'Just', value: T } or { _tag: 'Nothing' }), not a Maybe with methods. Use static methods like Maybe.unwrap(), Maybe.isJust(), etc. on the parsed result.
💡 Dual API: TypeSwiftJS supports both Haskell and Rust/Option naming conventions:
- Haskell style:
just(),nothing(),isJust(),isNothing() - Rust/Option style:
some(),none(),isSome(),isNone()
Both are aliases and work identically. Choose the style you prefer!
Either Type (Error Handling)
Represents a value that can be one of two types (like Rust's Result for error handling):
const { Either, matchEither } = require('@yohannawf/typeswiftjs');
// Create Either values
const success = Either.right(42); // Success case
const failure = Either.left('Error occurred'); // Error case
// Check which type
console.log(Either.isRight(success)); // true
console.log(Either.isLeft(success)); // false
console.log(Either.isRight(failure)); // false
console.log(Either.isLeft(failure)); // true
// Get values
console.log(Either.unwrap(success)); // 42 (right value)
console.log(Either.unwrapLeft(failure)); // 'Error occurred' (left value)
// Get right value with fallback
console.log(Either.getOrElse(success, 0)); // 42
console.log(Either.getOrElse(failure, 0)); // 0
// Map over right value (success)
const doubled = Either.map(success, x => x * 2);
console.log(Either.unwrap(doubled)); // 84
const stillError = Either.map(failure, x => x * 2);
console.log(Either.isLeft(stillError)); // true - unchanged
// Map over left value (error)
const betterError = Either.mapLeft(failure, err => `Failed: ${err}`);
console.log(Either.unwrapLeft(betterError)); // 'Failed: Error occurred'
// FlatMap (chain Either operations)
const chainResult = Either.flatMap(success, x =>
x > 40 ? Either.right(x * 2) : Either.left('Too small')
);
console.log(Either.unwrap(chainResult)); // 84
// Pattern matching with fold
const message = Either.fold(success,
(error) => `Error: ${error}`,
(value) => `Success: ${value}`
);
console.log(message); // "Success: 42"
// OR use matchEither helper
const message2 = matchEither(failure, {
left: (error) => `Error: ${error}`,
right: (value) => `Success: ${value}`,
});
console.log(message2); // "Error: Error occurred"
// Schema validation
// Schema validation
const eitherSchema = Type.either(
Type.string(), // LEFT type (error) - FIRST parameter
Type.number() // RIGHT type (success) - SECOND parameter
);
// Practical example - safe division
function safeDivide(a, b) {
if (b === 0) {
return Either.left('Division by zero');
}
return Either.right(a / b);
}
const result1 = safeDivide(10, 2);
const result2 = safeDivide(10, 0);
console.log(
matchEither(result1, {
left: (err) => `Error: ${err}`,
right: (val) => `Result: ${val}`,
})
); // "Result: 5"
console.log(
matchEither(result2, {
left: (err) => `Error: ${err}`,
right: (val) => `Result: ${val}`,
})
); // "Error: Division by zero"
// Practical example - validation pipeline
function validateAge(age) {
if (typeof age !== 'number') {
return Either.left('Age must be a number');
}
if (age < 0) {
return Either.left('Age cannot be negative');
}
if (age > 150) {
return Either.left('Age is unrealistic');
}
return Either.right(age);
}
const validAge = validateAge(25);
const invalidAge = validateAge(-5);
Either.fold(validAge,
(err) => console.error('Invalid:', err),
(age) => console.log('Valid age:', age)
); // "Valid age: 25"Parameter Order: Type.either(leftType, rightType)
- First parameter: Left type (usually error)
- Second parameter: Right type (usually success)
Result Type (Success/Failure)
Similar to Either, specifically for operation results (Rust-inspired):
const { Result, matchResult } = require('@yohannawf/typeswiftjs');
// Create Result values
const success = Result.ok(42);
const failure = Result.err('Operation failed');
// Check state
console.log(Result.isOk(success)); // true
console.log(Result.isErr(success)); // false
console.log(Result.isOk(failure)); // false
console.log(Result.isErr(failure)); // true
// Get values
console.log(Result.unwrap(success)); // 42
console.log(Result.unwrapOr(success, 0)); // 42
console.log(Result.unwrapOr(failure, 0)); // 0 (fallback)
// Unwrap with custom error message
try {
Result.expect(failure, 'Failed to get value');
} catch (error) {
console.log(error.message); // "Failed to get value"
}
// Get error value
console.log(Result.unwrapErr(failure)); // 'Operation failed'
// Map operations (only if Ok)
const doubled = Result.map(success, x => x * 2);
console.log(Result.unwrap(doubled)); // 84
// Map error (only if Err)
const betterError = Result.mapErr(failure, err => `Error: ${err}`);
console.log(Result.unwrapErr(betterError)); // 'Error: Operation failed'
// AndThen / FlatMap (chain Result operations)
const chainResult = Result.andThen(success, x =>
x > 40 ? Result.ok(x * 2) : Result.err('Value too small')
);
console.log(Result.unwrap(chainResult)); // 84
// Pattern matching
const message = Result.match(success, {
ok: (value) => `Got: ${value}`,
err: (error) => `Failed: ${error}`,
});
console.log(message); // "Got: 42"
// OR use matchResult helper
const message2 = matchResult(failure, {
ok: (value) => `Got: ${value}`,
err: (error) => `Failed: ${error}`,
});
console.log(message2); // "Failed: Operation failed"
// Schema validation
// ⚠️ BREAKING CHANGE (v1.2.2+): errType comes FIRST!
const resultSchema = Type.result(
Type.object({ code: Type.number(), msg: Type.string() }), // error type
Type.string() // success type
);
// Create Result values
const success = Result.ok('Operation successful');
const failure = Result.err({ code: 500, msg: 'Server error' });
// Parse with schema
const parsedOk = resultSchema.parse(success);
console.log(Result.unwrap(parsedOk)); // 'Operation successful'
const parsedErr = resultSchema.parse(failure);
console.log(Result.unwrapErr(parsedErr)); // { code: 500, msg: 'Server error' }
// Convert to/from Promise
const promise = Result.toPromise(success);
promise.then(value => console.log('Success:', value)); // "Success: 42"
const resultFromPromise = await Result.fromPromise(
fetch('https://api.example.com/data')
);
if (Result.isOk(resultFromPromise)) {
console.log('Fetched successfully');
} else {
console.log('Fetch failed:', Result.unwrapErr(resultFromPromise));
}
// Combine multiple Results
const results = [
Result.ok(1),
Result.ok(2),
Result.ok(3),
];
const combined = Result.all(results);
console.log(Result.unwrap(combined)); // [1, 2, 3]
const resultsWithError = [
Result.ok(1),
Result.err('Failed'),
Result.ok(3),
];
const combinedError = Result.all(resultsWithError);
console.log(Result.isErr(combinedError)); // true
// Practical example - async operation with error handling
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
return Result.err(`HTTP ${response.status}: ${response.statusText}`);
}
const user = await response.json();
return Result.ok(user);
} catch (error) {
return Result.err(error.message);
}
}
const userResult = await fetchUser(123);
Result.match(userResult, {
ok: (user) => console.log('User:', user.name),
err: (error) => console.error('Failed to fetch user:', error),
});
// Practical example - validation pipeline
function parseAndDouble(str) {
const num = parseInt(str);
if (isNaN(num)) {
return Result.err('Not a number');
}
if (num < 0) {
return Result.err('Number must be positive');
}
return Result.ok(num * 2);
}
const result1 = parseAndDouble('42');
const result2 = parseAndDouble('abc');
const result3 = parseAndDouble('-5');
console.log(Result.unwrapOr(result1, 0)); // 84
console.log(Result.unwrapOr(result2, 0)); // 0
console.log(Result.unwrapOr(result3, 0)); // 0
// Chain multiple operations
const processValue = (str) =>
Result.andThen(
parseAndDouble(str),
doubled => doubled > 100
? Result.ok(`Large: ${doubled}`)
: Result.err('Value too small')
);
const processed = processValue('60');
console.log(Result.unwrapOr(processed, 'Failed')); // "Large: 120"Algebraic Types - Complete API Reference
TypeSwiftJS provides dual naming conventions for algebraic types, supporting both Haskell and Rust/Option styles:
Maybe / Option Type
ALL methods support BOTH Haskell and Rust/Option naming conventions:
| Operation | Haskell Style | Rust/Option Style | Description |
|-----------|---------------|-------------------|-------------|
| Create value | Maybe.just(value) | Maybe.some(value) | Create a value that exists |
| Create empty | Maybe.nothing() | Maybe.none() | Create an empty value |
| Check if exists | Maybe.isJust(maybe) | Maybe.isSome(maybe) | Check if value exists |
| Check if empty | Maybe.isNothing(maybe) | Maybe.isNone(maybe) | Check if value is empty |
| Extract value | Maybe.unwrap(maybe) | Maybe.unwrap(maybe) | Get value (throws if empty) |
| Get or default | Maybe.getOrElse(maybe, default) | Maybe.getOrElse(maybe, default) | Get value or fallback |
| Transform | Maybe.map(maybe, fn) | Maybe.map(maybe, fn) | Transform value if exists |
| Chain | Maybe.flatMap(maybe, fn) | Maybe.flatMap(maybe, fn) | Chain Maybe operations |
Pattern Matching - accepts BOTH styles:
// Haskell style
matchMaybe(value, {
just: (x) => `Has value: ${x}`,
nothing: () => 'No value'
});
// Rust/Option style (exact same behavior!)
matchMaybe(value, {
some: (x) => `Has value: ${x}`,
none: () => 'No value'
});// Both work identically!
#### Either Type
| Method | Description |
|--------|-------------|
| `Either.left(error)` | Create error case |
| `Either.right(value)` | Create success case |
| `Either.isLeft(either)` | Check if error |
| `Either.isRight(either)` | Check if success |
| `Either.unwrap(either)` | Extract success value (throws if error) |
| `Either.unwrapLeft(either)` | Extract error value (throws if success) |
| `Either.getOrElse(either, default)` | Get success or fallback |
| `Either.map(either, fn)` | Transform success value |
| `Either.mapLeft(either, fn)` | Transform error value |
| `Either.flatMap(either, fn)` | Chain Either operations |
| `Either.fold(either, onLeft, onRight)` | Pattern match both cases |
**Pattern Matching:**
```javascript
matchEither(value, {
left: (error) => `Error: ${error}`,
right: (value) => `Success: ${value}`
});Result Type
| Method | Description |
|--------|-------------|
| Result.ok(value) | Create success |
| Result.err(error) | Create error |
| Result.isOk(result) | Check if success |
| Result.isErr(result) | Check if error |
| Result.unwrap(result) | Extract value (throws if error) |
| Result.unwrapErr(result) | Extract error (throws if success) |
| Result.expect(result, message) | Unwrap with custom error message |
| Result.unwrapOr(result, default) | Get value or fallback |
| Result.map(result, fn) | Transform success value |
| Result.mapErr(result, fn) | Transform error value |
| Result.andThen(result, fn) | Chain Result operations (flatMap) |
| Result.match(result, handlers) | Pattern match both cases |
| Result.toPromise(result) | Convert to Promise |
| Result.fromPromise(promise) | Create from Promise (async) |
| Result.all(results) | Combine multiple Results (fails if any error) |
Pattern Matching:
matchResult(value, {
ok: (value) => `Success: ${value}`,
err: (error) => `Error: ${error}`
});
// OR use Result.match directly
Result.match(value, {
ok: (value) => `Success: ${value}`,
err: (error) => `Error: ${error}`
});When to Use Each Type
Use Maybe/Option when:
- A value might not exist (null/undefined scenarios)
- You want to avoid null/undefined checks
- Representing optional configuration or user input
- Database queries that might return no results
Use Either when:
- You need to distinguish between two different types
- You want to represent success/error with different error types
- Building validation pipelines with detailed error messages
- Domain modeling where you need left/right semantics
Use Result when:
- Handling operation outcomes (success/failure)
- Working with async operations and promises
- You prefer Rust-style error handling
- Building robust error handling in critical code paths
All three types support pattern matching and provide type-safe error handling without exceptions!
🔗 TypedRef - Late Initialization
Unique to TypeSwiftJS! Java-style late initialization with full type safety:
Why Use TypedRef?
In many scenarios, you need to declare a variable but initialize it later:
- Configuration that's loaded asynchronously
- Dependency injection
- Circular dependencies
- Deferred initialization for performance
TypedRef provides a type-safe way to handle these cases:
// Create uninitialized reference
const configRef = Type.string().ref();
console.log(configRef.isInitialized); // false
// Trying to access throws a descriptive error
try {
console.log(configRef.value);
} catch (error) {
console.log(error.message);
// "Variable is not initialized. Assign a value before accessing."
}
// Initialize later
configRef.value = 'production';
console.log(configRef.value); // 'production'
console.log(configRef.isInitialized); // true
// Validation still applies
try {
configRef.value = ''; // Fails string validation
} catch (error) {
console.log(error.message); // Validation error
}With Initial Value
const portRef = Type.number().int().positive().ref(3000);
console.log(portRef.value); // 3000
console.log(portRef.isInitialized); // trueNamed Fields (Better Error Messages)
const apiKeyRef = Type.string().min(32).ref(undefined, 'API_KEY');
try {
console.log(apiKeyRef.value);
} catch (error) {
console.log(error.message);
// "Variable 'API_KEY' is not initialized. Assign a value before accessing."
}Immutability with Lock
Understanding the difference between TypedRef and Schema immutability:
| Feature | TypedRef .lock() | Schema .immutable() |
|---------|-------------------|----------------------|
| What it locks | The reference (prevents reassignment) | The parsed object (deep freeze) |
| Where it works | On TypedRef instances only | On schema definitions only |
| Can be combined? | ✅ Yes - for dual immutability | ✅ Yes - for dual immutability |
TypedRef Immutability (.lock())
What .lock() does: Prevents reassignment of the TypedRef's value - the reference becomes read-only.
// ✅ CORRECT - TypedRef immutability
const envRef = Type.string().ref('production');
console.log(envRef.isLocked); // false
envRef.value = 'staging'; // ✅ OK - not locked yet
console.log(envRef.value); // 'staging'
// Lock the reference
envRef.lock();
console.log(envRef.isLocked); // true
// Cannot modify after locking
try {
envRef.value = 'development'; // ❌ Throws error
} catch (error) {
console.log(error.message); // "Cannot modify an immutable value"
}
// ❌ WRONG - .lock() doesn't exist on schemas
const schema = Type.object({ name: Type.string() }).lock();
// TypeError: schema.lock is not a functionSchema Immutability (.immutable())
What .immutable() does: Deep freezes the parsed object - all properties become read-only.
// ✅ CORRECT - Schema immutability
const userSchema = Type.object({
name: Type.string(),
age: Type.number(),
}).immutable();
const user = userSchema.parse({ name: 'John', age: 25 });
// Object is frozen
console.log(Object.isFrozen(user)); // true
try {
user.name = 'Jane'; // ❌ Cannot modify (throws in strict mode)
user.age = 30; // ❌ Cannot modify (throws in strict mode)
} catch (error) {
console.log('Cannot modify immutable object');
}
// ❌ WRONG - .immutable() doesn't exist on TypedRef
const ref = Type.string().ref('test').immutable();
// TypeError: ref.immutable is not a functionCombining Both for Dual Immutability
You can create a TypedRef that holds immutable objects by combining both approaches:
// ✅ BEST OF BOTH WORLDS
const configSchema = Type.object({
port: Type.number(),
host: Type.string(),
}).immutable(); // Schema freezes the object
// Create TypedRef from immutable schema
const configRef = configSchema.ref({
port: 3000,
host: 'localhost'
});
// Level 1: Object is frozen (from .immutable())
console.log(Object.isFrozen(configRef.value)); // true
try {
configRef.value.port = 4000; // ❌ Cannot modify object properties
} catch (error) {
console.log('Object is frozen');
}
// Level 2: But you can still replace the entire reference
configRef.value = { port: 8080, host: '0.0.0.0' }; // ✅ OK - reference not locked
// Lock the reference to prevent replacement
configRef.lock();
try {
configRef.value = { port: 9000, host: 'example.com' }; // ❌ Cannot replace
} catch (error) {
console.log('Reference is locked');
}This provides dual immutability:
- Object-level immutability:
.immutable()deep freezes the parsed object - Reference-level immutability:
.lock()prevents reassignment of the TypedRef
Quick Reference
// TypedRef methods (for references)
const ref = Type.string().ref('value');
ref.lock(); // ✅ Locks the reference
ref.isLocked; // ✅ Check if locked
ref.value = 'new'; // ❌ After lock - throws error
// Schema methods (for objects)
const schema = Type.object({ ... }).immutable(); // ✅ Freezes parsed objects
const data = schema.parse({ ... });
Object.isFrozen(data); // ✅ true
data.property = 'new'; // ❌ Cannot modify
// Combined approach
const ref = Type.object({ ... }).immutable().ref({ ... });
ref.lock(); // ✅ Both object AND reference are now immutableReal-World Example
// Application configuration with maximum immutability
class AppConfig {
constructor() {
// Immutable schema prevents object modification
const immutableConfigSchema = Type.object({
apiKey: Type.string().min(32),
environment: Type.enum(['development', 'production', 'test']),
port: Type.number().int().positive(),
}).immutable();
// TypedRef allows late initialization
this.config = immutableConfigSchema.ref(undefined, 'APP_CONFIG');
}
async load() {
const envConfig = await loadFromEnvironment();
// Initialize the config
this.config.value = {
apiKey: envConfig.API_KEY,
environment: envConfig.NODE_ENV,
port: parseInt(envConfig.PORT),
};
// Lock the reference to prevent accidental replacement
this.config.lock();
console.log('Config loaded and locked!');
}
get apiKey() {
return this.config.value.apiKey;
}
get isProduction() {
return this.config.value.environment === 'production';
}
}
// Usage
const appConfig = new AppConfig();
await appConfig.load();
// ✅ Can read values
console.log(appConfig.apiKey);
console.log(appConfig.isProduction);
// ❌ Cannot modify object properties (immutable schema)
appConfig.config.value.apiKey = 'hacked'; // Fails silently or throws
// ❌ Cannot replace entire config (locked reference)