dismatchdismatch
Standalone API

Collections

find, some, every, groupBy, and filterMap — variant-aware collection operations over discriminated unions in a single pass.

Five purpose-built collection operations for discriminated unions: search, test, group, and filter-transform — all narrowed by variant, all in a single pass.

import { find, some, every, groupBy, filterMap } from "dismatch";

Shared semantics

These rules apply to every operation on this page:

  • Optional discriminant. Each function accepts an optional trailing discriminant key (default "type"), e.g. find(items, "circle", "kind").
  • Non-union items are silently skipped. Items that don't structurally look like a discriminated union (missing or non-string discriminant) are ignored — no throws, no errors.
  • Bound forms. Every op is also available on createUnion factories (Shape.find(...), Shape.some(...), …) and on createPipeHandlers bindings, pre-bound to the union type and discriminant.

find

Returns the first item matching the given variant(s), narrowed to that type, or undefined. Accepts a single variant or an array of variants.

import { find } from "dismatch";

type Shape =
  | { type: "circle"; radius: number }
  | { type: "rectangle"; width: number; height: number }
  | { type: "triangle"; base: number; height: number };

const shapes: Shape[] = [
  { type: "rectangle", width: 4, height: 6 },
  { type: "circle", radius: 5 },
  { type: "triangle", base: 3, height: 8 },
];

const firstCircle = find(shapes, "circle");
//    ^? { type: "circle"; radius: number } | undefined

const firstRound = find(shapes, ["circle", "triangle"]);
//    ^? Circle | Triangle | undefined
Try in Playground

find is to Array.prototype.find what partition is to a two-pass filter — the variant-aware version narrows the return type for free.

some

Returns true if any item matches the given variant(s).

import { some } from "dismatch";

if (some(shapes, "circle")) {
  // there is at least one circle in the collection
}

const hasRound = some(shapes, ["circle", "triangle"]);
Try in Playground

every

Returns true if every item matches the given variant(s).

import { every } from "dismatch";

const allCircles = every(shapes, "circle");
const allRound = every(shapes, ["circle", "triangle"]);
Try in Playground

Non-union items are skipped. A collection of only non-union items is treated as empty and returns true — vacuous truth, consistent with Array.prototype.every.

groupBy

Groups a collection by variant in a single pass. Each present group is narrowed to its specific variant type.

import { groupBy } from "dismatch";

const groups = groupBy(shapes);
//    ^? {
//         circle?:    Circle[];
//         rectangle?: Rectangle[];
//         triangle?:  Triangle[];
//       }

groups.circle?.forEach((c) => console.log(c.radius));
Try in Playground

Result keys are optional: absent variant groups are absent at runtime, and the type now reflects that. Access groups with groups.circle?.…, groups.circle ?? [], or an existence check.

filterMap

Filters and transforms in one pass. Each handler returns either a value to keep or undefined to skip. Unhandled variants are silently skipped.

import { filterMap } from "dismatch";

const areas = filterMap(shapes, {
  circle: ({ radius }) => Math.PI * radius ** 2,
  rectangle: ({ width, height }) => width * height,
  // triangle: skipped — no handler
});
//    ^? number[]
Try in Playground

null is a valid kept value — only undefined skips. This lets you produce nullable results from a filterMap without losing them to the filter.

Bound forms

The same operations are pre-bound on createUnion factories and createPipeHandlers bindings — no discriminant argument needed.

import { createUnion } from "dismatch";

const Shape = createUnion({
  circle: (radius: number) => ({ radius }),
  rectangle: (w: number, h: number) => ({ w, h }),
});

Shape.find(items, "circle");
Shape.some(items, ["circle", "rectangle"]);
Shape.every(items, "circle");
Shape.groupBy(items);
Shape.filterMap(items, {
  circle: ({ radius }) => radius * 2,
});

The five names find, some, every, groupBy, filterMap are reserved factory keys — see createUnion.

Reach for what next

  • Need exhaustive aggregation instead of optional grouping? See fold.
  • Need a count or a two-way partition? See count and partition.
  • Need an exhaustive transform that keeps the discriminant? See mapAll.
  • Composing into a pipeline? Every op is available on createPipeHandlers.

On this page