Macro System
6 macro kinds to extend TypeScript with custom syntax that compiles away completely.
Operators and methods that just work, compiled to exactly what you'd write by hand.

interface User {
id: number;
name: string;
email: string;
}
const alice: User = { id: 1, name: "Alice", email: "alice@example.com" };
const bob: User = { id: 2, name: "Bob", email: "bob@example.com" };
// Operators just work โ auto-derived, auto-specialized
alice === bob; // Compiles to: alice.id === bob.id && alice.name === bob.name && ...
alice < bob; // Lexicographic comparison
// Methods just work too
alice.show(); // "User(id = 1, name = Alice, email = alice@example.com)"
alice.clone(); // Deep copy
alice.toJson(); // JSON serializationHow it works: The compiler sees === on a User, resolves the Eq typeclass, auto-derives an instance from the type's fields, and inlines the comparison directly โ no dictionary lookup, no runtime cost.
Custom language features that compile away
typesugar provides 6 kinds of macros, each triggered differently:
myMacro(...) function calls@myDecorator on classes, methods, properties@derive(Eq, Clone) generates implementations from type structuresql`SELECT * FROM users` with compile-time validationRefined<number, Positive> at the type levellet: { } yield: { } for custom control flow// Define a simple expression macro
defineSyntaxMacro("unless", {
arms: [
{
pattern: "$cond:expr, $body:expr",
expand: "($cond) ? undefined : ($body)",
},
],
});
// Use it
unless(isLoggedIn, redirect("/login"));
// Compiles to: (isLoggedIn) ? undefined : (redirect("/login"))Writing Macros Guide ยท Macro Types Reference
Typeclasses, monads, and proofs โ for the nerds
If you're into FP, typesugar has you covered:
let:/yield: labeled blocks โ works with any monadF<A> syntax โ write generic code over type constructorsPositive, Port, Email, NonEmpty<T> with compile-time validationrequires(), ensures(), @invariant with compile-time proof elimination// HKT with F<A> syntax (the transformer rewrites F<A> to Kind<F, A>)
interface Functor<F> {
map<A, B>(fa: F<A>, f: (a: A) => B): F<B>;
}
// Do-notation for any monad
const result = let: {
user << fetchUser(id);
posts << fetchPosts(user.id);
stats << computeStats(posts);
}
yield: { { user, posts, stats } };
// Contracts with compile-time proof elimination
function sqrt(x: number): number {
requires: { x >= 0 }
ensures: { result => result >= 0 }
return Math.sqrt(x);
}Typeclasses Guide ยท FP Guide ยท Contracts Guide
Run code at build time, not at runtime
Move computation from runtime to compile time:
comptime() โ evaluate any expression at build time@tailrec โ tail-call elimination for stack-safe recursionincludeStr() / includeJson() โ embed file contents at compile timestaticAssert() โ compile-time assertions that disappear in outputcfg() / @cfgAttr โ conditional compilation for feature flagscollectTypes() โ introspect your entire project at compile time"use no typesugar" โ opt-out directives for debugging and interop// Computed at compile time, inlined as a literal
const BUILD_TIME = comptime(new Date().toISOString());
const FIB_50 = comptime(fibonacci(50));
// Stack-safe recursion via loop transformation
@tailrec
function factorial(n: number, acc = 1): number {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
// Compiles to: while(true) { if (n <= 1) return acc; acc = n * acc; n = n - 1; }
// Embed files at compile time
const SCHEMA = includeJson("./schema.json");
const TEMPLATE = includeStr("./email.html");Compile-Time Guide ยท Conditional Compilation
TypeScript's missing standard library
Batteries included for everyday TypeScript:
(42).clamp(0, 100), "hello".capitalize(), [1,2,3].sum()match() with discriminated unions, guards, OR patternstypeInfo<T>(), fieldNames<T>(), validator<T>() at compile timetransformInto() for zero-cost struct-to-struct conversion@derive(Eq, Ord, Clone, Debug, Hash, Json, Builder, TypeGuard)sql, regex, html, fmt with compile-time validation// Pattern matching with exhaustiveness checking
type Result<T, E> = { tag: "Ok"; value: T } | { tag: "Err"; error: E };
const message = match(result, {
Ok: ({ value }) => `Got ${value}`,
Err: ({ error }) => `Failed: ${error}`,
});
// Extension methods on primitives
const clamped = (255).clamp(0, 100); // 100
const words = "hello world".words(); // ["hello", "world"]
const total = [1, 2, 3, 4, 5].sum(); // 15
// Auto-derive common implementations
@derive(Eq, Clone, Debug, Json)
class Point {
constructor(
public x: number,
public y: number
) {}
}Pattern Matching ยท Derive Guide ยท Extension Methods
Powerful abstractions with zero runtime cost
Advanced data structures and algorithms following typesugar's zero-cost philosophy:
// Lazy iterator fusion โ single pass, no intermediate arrays
const result = lazy([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.filter((x) => x % 2 === 0)
.map((x) => x * x)
.take(3)
.toArray();
// โ [4, 16, 36] โ single loop, early termination
// PEG grammar โ recursive descent parser
const csv = grammar`
file = record ("\\n" record)*
record = field ("," field)*
field = quoted | unquoted
quoted = '"' (!'"' .)* '"'
unquoted = (!',' !'\\n' .)*
`;HList Guide ยท Parser Guide ยท Fusion Guide ยท Graph Guide
Supercharge your existing tools
typesugar integrates deeply with popular frameworks:
Reduce Effect boilerplate with macros. The let:/yield: do-notation works seamlessly with Effect.
@service
class UserService {
getUser(id: string) { return Effect.succeed({ id, name: "Alice" }); }
}
@layer
class UserServiceLive implements UserService {
getUser(id: string) { return Effect.succeed({ id, name: "Alice" }); }
}
// Do-notation with Effect
const program = let: {
user << UserService.getUser("123");
posts << PostService.getPosts(user.id);
}
yield: { posts.length };Power assertions and property-based testing:
// Power assertions show expression breakdown on failure
assert(user.age > 18 && user.name.length > 0);
// On failure:
// assert(user.age > 18 && user.name.length > 0)
// | | | | | | |
// | 16 false | "" 0 false
// { age: 16, name: "" }
class Point {
constructor(
public x: number,
public y: number
) {}
}
// forAll auto-derives Arbitrary generators from field types
forAll(Point, (p) => p.x + p.y === p.y + p.x);
forAll(Point, 1000, (p) => p.x * 0 === 0); // custom iteration countEffect Integration ยท Testing Guide
When something goes wrong, you should know exactly what happened and how to fix it.
Every error shows the code, points at the problem, and suggests a fix:
error[TS9101]: Cannot auto-derive Eq<UserProfile>: field `metadata` has type `unknown` which lacks Eq
--> src/user.ts:5:3
|
3 | interface UserProfile {
4 | id: number;
5 | metadata: unknown;
| ^^^^^^^^ this field prevents auto-derivation
|
= note: `unknown` cannot implement Eq โ it could be anything
= help: Use a concrete type instead of `unknown`, or provide @instance Eq<UserProfile>Look up any error: npx typesugar --explain TS9101
Missing an import? typesugar tells you where to find it:
error[TS9062]: Method `clamp` does not exist on type `number`
--> src/math.ts:7:20
|
7 | const safe = value.clamp(0, 100);
| ^^^^^
|
= help: Did you mean to import?
+ import { clamp } from "@typesugar/std";"use no typesugar"; // whole file
function debug() {
"use no typesugar";
} // one function
specialize(add); // @ts-no-typesugar // one line
("use no typesugar extensions"); // just extensionsError Messages Guide ยท Developer Experience Guide ยท Opt-Out Guide ยท Error Reference ยท Performance Architecture
| Package | Description |
|---|---|
| typesugar | Umbrella package |
| @typesugar/core | Macro registration and types |
| @typesugar/transformer | TypeScript transformer (ts-patch) |
| unplugin-typesugar | Bundler plugins (Vite, esbuild, Rollup, Webpack) |
| Package | Description |
|---|---|
| @typesugar/std | Extension methods, pattern matching, do-notation, standard typeclasses |
| Package | Description |
|---|---|
| @typesugar/typeclass | @typeclass, @instance, summon() |
| @typesugar/derive | @derive(Eq, Clone, Debug, Json, ...) |
| @typesugar/specialize | Zero-cost typeclass specialization |
| @typesugar/reflect | typeInfo<T>(), fieldNames<T>(), validator<T>() |
| Package | Description |
|---|---|
| @typesugar/strings | regex, html, raw tagged templates |
| Package | Description |
|---|---|
| @typesugar/type-system | Refined types, newtype, HKT, phantom types |
| @typesugar/contracts | requires:, ensures:, @invariant |
| @typesugar/contracts-refined | Refinement type integration |
| @typesugar/validate | Schema validation macros |
| @typesugar/units | Type-safe physical units |
| Package | Description |
|---|---|
| @typesugar/fp | Option, Either, IO, Result, List |
| @typesugar/hlist | Heterogeneous lists |
| @typesugar/fusion | Iterator fusion, expression templates |
| @typesugar/parser | PEG parser generation |
| @typesugar/graph | Graph algorithms, state machines |
| @typesugar/erased | Type erasure / dyn Trait |
| @typesugar/codec | Versioned codecs, schema evolution |
| @typesugar/math | Math types and typeclasses |
| @typesugar/mapper | Zero-cost object mapping |
| Package | Description |
|---|---|
| @typesugar/effect | Effect-TS adapter |
| @typesugar/sql | Doobie-like SQL |
| Package | Description |
|---|---|
| @typesugar/vscode | VS Code/Cursor extension |
| @typesugar/eslint-plugin | ESLint processor and rules |
| @typesugar/testing | Power assertions, property testing |
typesugar draws from the best ideas across language ecosystems:
| Language | What it brings | Packages |
|---|---|---|
| Scala 3 | Typeclasses, extension methods, do-notation | typeclass, std, fp, effect, operators |
| Rust | Derive macros, zero-cost specialization, serde, dyn Trait | derive, specialize, codec, erased, validate |
| Zig | Compile-time evaluation and reflection | comptime, reflect, preprocessor |
| C++ / Boost | Expression templates, heterogeneous containers, parsers | fusion, hlist, graph, parser, units |
| Haskell / ML | Refinement types, type-level programming, property testing | contracts, contracts-refined, type-system, testing, math |
Long-term vision documents for typesugar's future: