@synergyeffect/newtype
v1.0.1
Published
Haskell-style newtypes, typeclasses, and typed functions for JavaScript — powered by crocks
Maintainers
Readme
@synergyeffect/newtype
Haskell-style newtypes, typeclasses, and typed functions for JavaScript — powered by crocks
Why?
Defensive programming — checking and re-checking values at every function boundary — is tedious, error-prone, and easy to forget. This module lifts that burden by encoding validations directly into branded types. A value that passes a NewType predicate is guaranteed valid for its entire lifetime; every downstream use is safe without additional checks.
Because validation happens at construction time, invalid states become unrepresentable — if a value exists, it's correct. Combined with built-in generics that flow through the function chain, you get fully JSDoc-typed code by default, with zero extra annotation effort.
The result: type signatures that serve as executable documentation, and code where "if it compiles, it works" is actually true.
Install
npm install @synergyeffect/newtypeUsage
ESM:
import { NewType, TypeClass, Func } from "@synergyeffect/newtype";CJS:
const { NewType, TypeClass, Func } = require("@synergyeffect/newtype");Examples
import { NewType, TypeClass, Func } from "@synergyeffect/newtype";
import isString from "crocks/predicates/isString.js";
const Positive = NewType("Positive", (n) => n > 0);
const Negative = NewType("Negative", (n) => n < 0);
const Text = NewType("Text", isString);
const Showable = TypeClass("Showable", [Positive, Negative]);
const showBoth = Func(
[Showable, Showable, Text], // Declare input types and finally the output type.
(a, b) => `Show both: ${[a.value, b.value].join(' and ')}.` // You get wrapped inputs, but output wrapping is automatic.
);
console.log(showBoth.bare(Positive(5), Negative(-5))); // Show both: 5 and -5.
console.log(showBoth(Positive(5), Negative(-5))); // { value: 'Show both: 5 and -5.', [Symbol(Text)]: true }API
NewType(name, predicate?, menadicWrapper?)
Creates a new branded type with optional validation.
NewType(value)- wrap a valuerun(x)- unwrap the value from newtypehas(x)- validate against the newtype prodicate
TypeClass(name, methods)
Creates a group of interchangable newtypes.
has(x)- check if x is in TypeClass.
Func(...types, fn, name?)
Creates a curried function with type constraints. Sometimes different functions achieve different results, although type signature is the same. In that case add a name so you are sure you need one more function with exact same type signature.
bare(...)- outputs the resultant without a wrapping NewType.
Type Safety
Branded types prevent accidental mixing — you cannot pass a Positive where a Negative is expected, even though both are numbers:
const dividePositive = Func([Positive, Positive, Positive], (a, b) => a.value / b.value);
// enforces: both args must be Positive, return is Positive
divide(Positive.mk(10), Positive.mk(2)); // OK
divide(Negative.mk(-10), Positive.mk(2)); // Type error!Each function constraint guarantees your values have the expected shape — invaluable for long data transformations with multiple intermediate steps.
License
MIT
