Matchbox Factories
The matchboxFactory
function creates factories for working with tagged unions in TypeScript, providing type-safe pattern matching, exhaustiveness checking, and comprehensive type inference.
Why MatchboxFactory?
Section titled “Why MatchboxFactory?”While TypeScript has built-in support for discriminated unions, the matchboxFactory
system adds powerful capabilities:
- Factory Functions - Create tagged union instances with proper typing
- Pattern Matching - Match against union variants with exhaustiveness checking
- Type Predicates - Built-in type guards with the
is
method - Type Assertion - Convert between variants with the
as
method - Complete Type Inference - Full inference with minimal type annotations
Creating a Factory
Section titled “Creating a Factory”You can create a factory using the matchboxFactory
function. This factory then provides functions to create individual tagged union instances:
import { function matchboxFactory<Config extends TaggedTypes | string, TagProp extends string = "tag", R = MatchboxFactory<Config extends readonly string[] ? { [K in Config[number]]: (data: any) => any; } : Config, TagProp>>(config: Config, tagProp?: TagProp): R
Create a tagged union from a record mapping tags to value types, along with associated
variant constructors, type predicates and `match` function.matchboxFactory } from "matchina";
export const const Light: MatchboxFactory<{
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
Light = matchboxFactory<{
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag", MatchboxFactory<{
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">>(config: {
...;
}, tagProp?: "tag" | undefined): MatchboxFactory<...>
Create a tagged union from a record mapping tags to value types, along with associated
variant constructors, type predicates and `match` function.matchboxFactory({
// Off state with no parameters
type Off: undefined
Off: var undefined
undefined,
// On state with optional brightness percentage
type On: (percentage?: number) => {
percentage: number;
}
On: (percentage: number
percentage = 100) => ({ percentage: number
percentage }),
});
// Create light instances
const const off: MatchboxMember<"Off", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
off = const Light: MatchboxFactory<{
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
Light.type Off: () => MatchboxMember<"Off", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
Off();
const const onFull: MatchboxMember<"On", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
onFull = const Light: MatchboxFactory<{
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
Light.type On: (percentage?: number | undefined) => MatchboxMember<"On", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
On(); // Uses default 100%
const const dimmed: MatchboxMember<"On", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
dimmed = const Light: MatchboxFactory<{
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
Light.type On: (percentage?: number | undefined) => MatchboxMember<"On", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
On(50); // 50% brightness
const { const percentage: number
percentage } = const dimmed: MatchboxMember<"On", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
dimmed.data: {
percentage: number;
}
data;
export { const off: MatchboxMember<"Off", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
export off
off, const onFull: MatchboxMember<"On", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
export onFull
onFull, const dimmed: MatchboxMember<"On", {
Off: undefined;
On: (percentage?: number) => {
percentage: number;
};
}, "tag">
export dimmed
dimmed, const percentage: number
export percentage
percentage };
Custom Tag Property
Section titled “Custom Tag Property”By default, the tag property is named tag
, but you can customize it:
const const states: MatchboxFactory<{
Idle: () => {};
Done: (x: number) => {
result: number;
};
}, "key">
states = matchboxFactory<{
Idle: () => {};
Done: (x: number) => {
result: number;
};
}, "key", MatchboxFactory<{
Idle: () => {};
Done: (x: number) => {
result: number;
};
}, "key">>(config: {
Idle: () => {};
Done: (x: number) => {
result: number;
};
}, tagProp?: "key" | undefined): MatchboxFactory<...>
Create a tagged union from a record mapping tags to value types, along with associated
variant constructors, type predicates and `match` function.matchboxFactory(
{
type Idle: () => {}
Idle: () => ({}),
type Done: (x: number) => {
result: number;
}
Done: (x: number
x: number) => ({ result: number
result: x: number
x }),
},
"key"
);
const const events: MatchboxFactory<{
add: (x: number, y: number) => {
x: number;
y: number;
};
square: (x: number) => number;
}, "type">
events = matchboxFactory<{
add: (x: number, y: number) => {
x: number;
y: number;
};
square: (x: number) => number;
}, "type", MatchboxFactory<{
add: (x: number, y: number) => {
x: number;
y: number;
};
square: (x: number) => number;
}, "type">>(config: {
...;
}, tagProp?: "type" | undefined): MatchboxFactory<...>
Create a tagged union from a record mapping tags to value types, along with associated
variant constructors, type predicates and `match` function.matchboxFactory(
{
add: (x: number, y: number) => {
x: number;
y: number;
}
add: (x: number
x: number, y: number
y: number) => ({ x: number
x, y: number
y }),
square: (x: number) => number
square: (x: number
x: number) => x: number
x,
},
"type"
);
const states: MatchboxFactory<{
Idle: () => {};
Done: (x: number) => {
result: number;
};
}, "key">
states.type Idle: () => MatchboxMember<"Idle", {
Idle: () => {};
Done: (x: number) => {
result: number;
};
}, "key">
Idle().key: "Idle"
key; // "Idle"
const events: MatchboxFactory<{
add: (x: number, y: number) => {
x: number;
y: number;
};
square: (x: number) => number;
}, "type">
events.add: (x: number, y: number) => MatchboxMember<"add", {
add: (x: number, y: number) => {
x: number;
y: number;
};
square: (x: number) => number;
}, "type">
add(1, 2).type: "add"
type; // "add"
Conclusion
Section titled “Conclusion”Matchbox provides a powerful way to work with tagged unions in TypeScript, offering better type safety, pattern matching, and developer experience than plain TypeScript discriminated unions.
The key advantages are:
- Factory functions for creating properly typed variants
- Exhaustive pattern matching
- Built-in type guards and assertions
- Excellent type inference
This makes complex state modeling and data handling more robust and maintainable, with TypeScript helping to catch errors at compile time rather than runtime.