
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
@practical-fp/union-types
Advanced tools
A Typescript library for creating discriminating union types.
A Typescript library for creating discriminating union types. Requires Typescript 3.5 or higher.
Typescript Handbook on discriminating union types
import { impl, matchExhaustive, Variant } from "@practical-fp/union-types"
type Shape =
| Variant<"Circle", { radius: number }>
| Variant<"Square", { sideLength: number }>
const { Circle, Square } = impl<Shape>()
function getArea(shape: Shape) {
return matchExhaustive(shape, {
Circle: ({ radius }) => Math.PI * radius ** 2,
Square: ({ sideLength }) => sideLength ** 2,
})
}
const circle = Circle({ radius: 5 })
const area = getArea(circle)
$ npm install @practical-fp/union-types
import { Variant } from "@practical-fp/union-types"
type Shape =
| Variant<"Circle", { radius: number }>
| Variant<"Square", { sideLength: number }>
This is equivalent to the following type:
type Shape =
| { tag: "Circle", value: { radius: number } }
| { tag: "Square", value: { sideLength: number } }
import { impl } from "@practical-fp/union-types"
const { Circle, Square } = impl<Shape>()
impl<>() can only be used if your environment has full support
for Proxies. Alternatively, use the constructor<>() function.
import { constructor } from "@practical-fp/union-types"
const Circle = constructor<Shape, "Circle">("Circle")
const Square = constructor<Shape, "Square">("Square")
Circle and Square can then be used to wrap values as a Shape.
const circle: Shape = Circle({ radius: 5 })
const square: Shape = Square({ sideLength: 3 })
Circle.is and Square.is can be used to check if a shape is a circle or a square.
They also act as a type guard.
const shapes: Shape[] = [circle, square]
const sideLengths = shapes.filter(Square.is).map(square => square.value.sideLength)
You can also create custom implementations using the tag() and predicate() helper functions.
import { predicate, tag } from "@practical-fp/union-types"
const Circle = (radius: number) => tag("Circle", { radius })
const isCircle = predicate("Circle")
const Square = (sideLength: number) => tag("Square", { sideLength })
const isSquare = predicate("Square")
import { matchExhaustive } from "@practical-fp/union-types"
function getArea(shape: Shape) {
return matchExhaustive(shape, {
Circle: ({ radius }) => Math.PI * radius ** 2,
Square: ({ sideLength }) => sideLength ** 2,
})
}
matchExhaustive() is exhaustive, i.e., you need to match against every variant of the union.
Cases can be omitted when using a wildcard case with matchWildcard().
import { matchWildcard, WILDCARD } from "@practical-fp/union-types"
function getDiameter(shape: Shape) {
return matchWildcard(shape, {
Circle: ({ radius }) => radius * 2,
[WILDCARD]: () => undefined,
})
}
switch-statements can also be used to match against a union.
import { assertNever } from "@practical-fp/union-types"
function getArea(shape: Shape) {
switch (shape.tag) {
case "Circle":
return Math.PI * shape.value.radius ** 2
case "Square":
return shape.value.sideLength ** 2
default:
// exhaustiveness check
// compile-time error if a case is missing
assertNever(shape)
}
}
impl<>() and constructor<>() also support generic union types.
In case the variant type uses unconstrained generics,
unknown needs to be passed as its type arguments.
import { impl, Variant } from "@practical-fp/union-types"
type Result<T, E> =
| Variant<"Ok", T>
| Variant<"Err", E>
const { Ok, Err } = impl<Result<unknown, unknown>>()
In case the variant type uses constrained generics, the constraint type needs to be passed as its type arguments.
import { impl, Variant } from "@practical-fp/union-types"
type Result<T extends object, E> =
| Variant<"Ok", T>
| Variant<"Err", E>
const { Ok, Err } = impl<Result<object, unknown>>()
strictImpl<>() and strictConstructor<>()impl<>() and constructor<>() generate generic constructor functions.
This may not always be desirable.
import { impl } from "@practical-fp/union-types"
const { Circle } = impl<Shape>()
const circle = Circle({
radius: 5,
color: "red",
})
Since Circle is generic, it's perfectly fine to pass extra properties other than radius.
To prevent that, we can use strictImpl<>() or strictConstructor<>() to create a strict
implementation which is not generic.
import { strictImpl } from "@practical-fp/union-types"
const { Circle } = strictImpl<Shape>()
const circle = Circle({
radius: 5,
color: "red", // compile error
})
FAQs
A Typescript library for creating discriminating union types.
The npm package @practical-fp/union-types receives a total of 1,343 weekly downloads. As such, @practical-fp/union-types popularity was classified as popular.
We found that @practical-fp/union-types demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.