npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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

Readme

TypeSwiftJS 🚀

Runtime type validation and schema enforcement for JavaScript/TypeScript - Better than Zod with Generics, Algebraic Types, and Late Initialization

npm version npm downloads License: MIT TypeScript Node.js Version


📖 Table of Contents


🎯 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) or some/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 immutable

Perfect 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 frozen

Feature 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/typeswiftjs

yarn

yarn add @yohannawf/typeswiftjs

pnpm

pnpm add @yohannawf/typeswiftjs

Requirements

  • 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); // ✅ 25

Object 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]')); // true

7. 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 object

8. 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 type

Length 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 string

Common 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 accepted

Number 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.00

BigInt 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 future

Other 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 removed

Map 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); // true

Named 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 function

Schema 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 function

Combining 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 immutable

Real-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)