Quick Start
Define a union, construct values, narrow types, pattern-match, and fold over collections — in five short steps.
This walk-through introduces the main APIs through a single running example.
Each step builds on the previous one — by the end you will have used
createUnion, is, match, matchWithDefault, map, mapAll, and fold.
1. Define a union
createUnion has two forms:
createUnion(schema)uses the default discriminant key"type".createUnion(discriminant, schema)lets you pick a different key.
The discriminant is the property that holds the variant tag. In
{ type: "ok", data: "..." } the discriminant key is type and the active
variant is "ok". Constructors return only the data fields — createUnion
injects the discriminant automatically.
import { createUnion, type InferUnion } from "dismatch";
const Result = createUnion({
ok: (data: string) => ({ data }),
error: (message: string) => ({ message }),
loading: () => ({}),
});
type Result = InferUnion<typeof Result>;
// { type: 'ok'; data: string }
// | { type: 'error'; message: string }
// | { type: 'loading' }If your data already uses a different key, pass it explicitly:
const Event = createUnion("kind", {
click: (x: number) => ({ x }),
key: (code: string) => ({ code }),
});Re-exporting constructors
createUnion returns a plain object holding constructors, matchers, and
metadata, so you can destructure and re-export anything you want. Two
idioms cover the common cases.
Namespace style — keep the factory public, destructure for ergonomics:
// shape.ts
export const Shape = createUnion({
circle: (radius: number) => ({ radius }),
rectangle: (width: number, height: number) => ({ width, height }),
});
export type Shape = InferUnion<typeof Shape>;
export const { circle, rectangle } = Shape;
// consumer.ts
import { Shape, circle } from "./shape";
const c = circle(5);
const area = Shape.match({ /* ... */ });Exit-friendly style — keep the factory file-private; only
constructors and the type leave the module. The type alias derives from
ReturnType of the constructors, so it has no dependency on the factory:
// shape.ts
const _Shape = createUnion({
circle: (radius: number) => ({ radius }),
rectangle: (width: number, height: number) => ({ width, height }),
});
export const { circle, rectangle, match, matchWithDefault, is } = _Shape;
export type Shape = ReturnType<typeof circle> | ReturnType<typeof rectangle>;
// consumer.ts
import { circle, match, type Shape } from "./shape";The exit-friendly style is what makes Removing dismatch mechanical: the type alias survives even after the factory is gone, because it's a TS-native discriminated union.
2. Construct values
const r = Result.ok("hello"); // { type: 'ok', data: 'hello' }
const e = Result.error("fail"); // { type: 'error', message: 'fail' }
const l = Result.loading(); // { type: 'loading' }3. Narrow with is
Use is(value, variant) inside if blocks for branch narrowing, and the
bound Result.is(variant) form as a .filter() predicate factory. Both work
with a single variant or an array of variants.
import { is } from "dismatch";
if (is(r, "ok")) {
r.data; // narrowed to { type: 'ok'; data: string }
}
const errors = results.filter(Result.is("error"));
// ^? { type: 'error'; message: string }[]
const settled = results.filter(Result.is(["ok", "error"]));Result.isKnown(value) is the runtime equivalent — true if the value is any
declared variant. Use it at system boundaries (network responses, message
queues) to validate incoming data.
4. Pattern-match
Every matcher is handlers-first and curried — pass the handlers and you get back a reusable function.
const label = Result.match({
ok: ({ data }) => `Data: ${data}`,
error: ({ message }) => `Error: ${message}`,
loading: () => "Loading...",
});
label(r); // 'Data: hello'
results.map(label); // string[]Need to handle only some variants? Use matchWithDefault:
const banner = Result.matchWithDefault({
error: ({ message }) => `Something went wrong: ${message}`,
Default: () => "All good",
});To transform variants in place (preserving the discriminant), use map for
partial and mapAll for exhaustive transforms:
const cleared = Result.map({
error: ({ message }) => ({ message: "" }),
});
const normalized = Result.mapAll({
ok: ({ data }) => ({ data: data.trim() }),
error: ({ message }) => ({ message: message.trim() }),
loading: () => ({}),
});5. Fold over a collection
fold aggregates a collection in a single exhaustive pass — no reduce
wrapper, no switch.
const stats = Result.fold(results, { oks: 0, errors: 0, loadings: 0 })({
ok: (acc) => ({ ...acc, oks: acc.oks + 1 }),
error: (acc) => ({ ...acc, errors: acc.errors + 1 }),
loading: (acc) => ({ ...acc, loadings: acc.loadings + 1 }),
});Where next
- The Discriminated Unions primer explains the problem dismatch solves and when to reach for it.
- The Standalone API covers
match,map,foldand friends — usable on any union, no factory required. - For async work, see the Async APIs.