@macroforge/mcp-server
Version:
MCP server for Macroforge documentation and code analysis
56 lines (40 loc) • 1.96 kB
Markdown
# Newtype
`$Newtype<T>` is a type-position macro that gives `T` a nominal brand. Two newtypes over the same
base are distinct types, and neither accepts a bare `T` without going through a constructor or
guard.
## Generated Output
```typescript
export type Meters = $Newtype<number>;
```
expands to
```typescript
export type Meters = number & { readonly [__mf_newtype_42]: true };
declare const __mf_newtype_42: unique symbol;
```
Every expansion declares its own `unique symbol`, named from the expansion's id, so the brand needs
no name and cannot collide, even for a `$Newtype` that another macro emits. The `declare const` is
type-only and erases at runtime. A base with a top-level `|` or `&`, or a function or conditional
type, is parenthesized.
`$Newtype` is built in and needs no import. It nests, so `$Newtype<$Newtype<number>>` carries both
brands.
The brand does not involve the alias's own type parameters: with `type Id<T> = $Newtype<string>`,
`Id<User>` and `Id<Order>` are the same type. A brand that should differ per parameter names it in
the type, as in `type Id<T> = string & { readonly [IdOf]: T }` with a
`declare const IdOf: unique symbol`.
## Constructing Values
A newtype has no constructor of its own. Derive `Decode` on the alias: for a primitive base,
`decode` checks the `typeof` and runs any validators written on the alias, which makes it the
validated constructor, and `is` the narrowing guard.
```typescript
/** @derive(Encode, Decode) */
/** @endec(nonNegative, finite) */
export type Meters = $Newtype<number>;
const parsed = Meters.decode(input); // { success, value } | { success, errors }
if (Meters.is(distance)) {
travel(distance); // narrowed to Meters
}
```
`Encode` writes the bare primitive, and `Default` returns the primitive's default (or the alias's
`(value)`) cast to the newtype.
## Error Handling
The macro reports an expansion error when it does not receive exactly one type argument.