A TypeScript library for pattern matching with full type safety and exhaustiveness checking.
This library provides a declarative, fluent interface for implementing pattern matching in TypeScript, offering type-safe value matching, type guards, predicate combinators, and compile-time exhaustiveness checking.
npm install switch-tsimport { when, eq, then } from 'switch-ts';
const result = when(2)
.is(eq(1), then('one'))
.is(eq(2), then('two'))
.otherwise(then('other'));
console.log(result); // 'two'import { when } from 'switch-ts';
const result = when(2)
.isValue(1, 'one')
.isValue(2, 'two')
.otherwise(() => 'other');
console.log(result); // 'two'import { when, isString, isNumber } from 'switch-ts';
const value: string | number = 'hello';
const result = when(value)
.isType(isString, (v) => v.toUpperCase())
.isType(isNumber, (v) => v.toFixed(2))
.otherwise(() => 'unknown');
console.log(result); // 'HELLO'import { when, between, betweenExclusive, then } from 'switch-ts';
const score = 85;
const grade = when(score)
.is(between(90, 100), then('A'))
.is(between(80, 89), then('B'))
.is(between(70, 79), then('C'))
.is(between(60, 69), then('D'))
.otherwise(then('F'));
console.log(grade); // 'B'import { when, oneOf, noneOf, then } from 'switch-ts';
type Status = 'active' | 'pending' | 'approved' | 'deleted' | 'archived';
const status: Status = 'active';
const result = when(status)
.is(oneOf(['active', 'pending', 'approved']), then('valid'))
.is(oneOf(['deleted', 'archived']), then('invalid'))
.otherwise(then('unknown'));
console.log(result); // 'valid'
// Using noneOf for exclusion
const isActive = when(status)
.is(noneOf(['deleted', 'archived']), then('active status'))
.otherwise(then('inactive status'));
console.log(isActive); // 'active status'import { when, all, any, not, gt, lt, eq, then } from 'switch-ts';
const value = 5;
// Logical AND - all predicates must pass
const result1 = when(value)
.is(all([gt(0), lt(10)]), then('in range'))
.otherwise(then('out of range'));
console.log(result1); // 'in range'
// Logical OR - any predicate must pass
const result2 = when(value)
.is(any([eq(2), eq(3), eq(5)]), then('prime'))
.otherwise(then('not prime'));
console.log(result2); // 'prime'
// Logical NOT - negate a predicate
const result3 = when(value)
.is(not(eq(0)), then('not zero'))
.otherwise(then('zero'));
console.log(result3); // 'not zero'import { when, all, ge, lt, then } from 'switch-ts';
const getStatusCategory = (status: number) =>
when(status)
.is(all([ge(200), lt(300)]), then('Success'))
.is(all([ge(300), lt(400)]), then('Redirect'))
.is(all([ge(400), lt(500)]), then('Client Error'))
.is(all([ge(500), lt(600)]), then('Server Error'))
.otherwise(then('Unknown'));
console.log(getStatusCategory(200)); // 'Success'
console.log(getStatusCategory(404)); // 'Client Error'
console.log(getStatusCategory(500)); // 'Server Error'import { when } from 'switch-ts';
type State = 'idle' | 'loading' | 'success' | 'error';
type Action = 'start' | 'resolve' | 'reject' | 'reset';
const transition = (state: State, action: Action): State =>
when(state)
.isValue(
'idle',
when(action).isValue('start', 'loading' as State).otherwise(() => state)
)
.isValue(
'loading',
when(action)
.isValue('resolve', 'success' as State)
.isValue('reject', 'error' as State)
.otherwise(() => state)
)
.isValue(
'success',
when(action).isValue('reset', 'idle' as State).otherwise(() => state)
)
.isValue(
'error',
when(action).isValue('reset', 'idle' as State).otherwise(() => state)
)
.otherwise(() => state);
console.log(transition('idle', 'start')); // 'loading'
console.log(transition('loading', 'resolve')); // 'success'
console.log(transition('success', 'reset')); // 'idle'import { when, exhaustive } from 'switch-ts';
type Status = 'pending' | 'approved' | 'rejected';
const getMessage = (status: Status): string =>
when(status)
.isValue('pending', 'Waiting for approval')
.isValue('approved', 'Request approved')
.isValue('rejected', 'Request rejected')
.otherwise(() => exhaustive(status)); // TypeScript error if any case is missing
console.log(getMessage('pending')); // 'Waiting for approval'
console.log(getMessage('approved')); // 'Request approved'
// If you add a new status without handling it, TypeScript will show a compile errorInitiates a pattern matching expression with the provided value.
when(value)
.is(predicate, producer)
.isValue(expectedValue, result)
.isType(guard, producer)
.isAny(predicates, producer)
.isAll(predicates, producer)
.otherwise(producer);Matches when the predicate returns true.
when(x).is((v) => v > 0, then('positive'))Matches when the value strictly equals the expected value using ===.
when(x).isValue(42, 'the answer')Matches when the type guard returns true, providing type narrowing.
when(value).isType(isString, (v) => v.toUpperCase())Matches when any predicate in the array returns true (logical OR).
when(x).isAny([eq(1), eq(2), eq(3)], then('one, two, or three'))Matches when all predicates in the array return true (logical AND).
when(x).isAll([gt(0), lt(10)], then('between 0 and 10'))Provides a default value when no predicates match. This is required to complete the pattern matching chain.
when(x).is(eq(1), then('one')).otherwise(then('other'))Creates a constant producer function that always returns the given value.
when(x).is(eq(1), then('one'))Creates an equality comparison predicate (===).
when(x).is(eq(42), then('matched'))Creates an inequality comparison predicate (!==).
when(x).is(ne(0), then('not zero'))Creates a greater-than comparison predicate (>).
when(x).is(gt(0), then('positive'))Creates a less-than comparison predicate (<).
when(x).is(lt(0), then('negative'))Creates a greater-than-or-equal comparison predicate (>=).
when(x).is(ge(0), then('non-negative'))Creates a less-than-or-equal comparison predicate (<=).
when(x).is(le(10), then('at most ten'))Creates a predicate that checks if a value is within a range (inclusive).
when(score).is(between(0, 100), then('valid score'))Creates a predicate that checks if a value is within a range (exclusive).
when(age).is(betweenExclusive(18, 65), then('working age'))Creates a predicate that checks if a value is included in an array.
when(status).is(oneOf(['active', 'pending', 'approved']), then('valid status'))Creates a predicate that checks if a value is not included in an array.
when(status).is(noneOf(['deleted', 'archived']), then('active status'))Type guard for string values.
when(value).isType(isString, (v) => v.toUpperCase())Type guard for number values.
when(value).isType(isNumber, (v) => v.toFixed(2))Type guard for boolean values.
when(value).isType(isBoolean, (v) => v ? 'yes' : 'no')Type guard for null values.
when(value).isType(isNull, () => 'is null')Type guard for undefined values.
when(value).isType(isUndefined, () => 'is undefined')Combines multiple predicates with logical AND. All predicates must return true.
when(x).is(all([gt(0), lt(10)]), then('between 0 and 10'))Combines multiple predicates with logical OR. At least one predicate must return true.
when(x).is(any([eq(1), eq(2), eq(3)]), then('one, two, or three'))Negates a predicate, returning the opposite boolean value.
when(x).is(not(eq(0)), then('not zero'))Ensures exhaustive checking of all cases in pattern matching. This function should never be reached if all cases are handled. Throws an error if called, indicating a missing case in the pattern match.
type Status = 'pending' | 'approved' | 'rejected';
const getMessage = (status: Status): string =>
when(status)
.isValue('pending', 'Waiting')
.isValue('approved', 'Approved')
.isValue('rejected', 'Rejected')
.otherwise(() => exhaustive(status)); // TypeScript error if any case is missingThis library provides full TypeScript type safety with:
- Type narrowing through type guards
- Exhaustiveness checking for union types
- Compile-time validation of pattern completeness
Pattern matching evaluation is short-circuit: once a predicate matches, subsequent predicates are not evaluated. Order your predicates from most specific to most general for optimal performance.
Unlike native JavaScript switch statements, switch-ts provides:
- Type-safe pattern matching with type narrowing
- First-class support for complex predicates
- Exhaustiveness checking at compile time
- Functional composition through predicate combinators
- Immutable, expression-based syntax
This project is licensed under the MIT License - see the LICENSE file for details.