API Stability Notice
Macroforge is under active development. The API is not yet stable and may change between versions. Some documentation sections may be outdated.
Default
The Default macro generates a static defaultValue() factory method that creates
instances with default values. This is analogous to Rust’s Default trait, providing
a standard way to create “zero” or “empty” instances of types.
Generated Output
| Type | Generated Code | Description |
|---|---|---|
| Class | static defaultValue() + classNameDefaultValue() | Static factory method + standalone function |
| Enum | enumNameDefaultValue(): EnumName | Standalone function returning @default variant |
| Interface | ifaceNameDefaultValue(): InterfaceName | Standalone function returning object literal |
| Type Alias | typeNameDefaultValue(): TypeName | Standalone function with type-appropriate default |
Names use camelCase conversion (e.g., UserSettings -> userSettingsDefaultValue).
Default Values by Type
The macro uses Rust-like default semantics:
| Type | Default Value |
|---|---|
string | "" (empty string) |
number | 0 |
boolean | false |
bigint | 0n |
T[] | [] (empty array) |
Array<T> | [] (empty array) |
Map<K,V> | new Map() |
Set<T> | new Set() |
Date | new Date() (current time) |
T \| null | null |
Unions (A \| B) | Default of the first primitive or literal member, else the first member’s default |
CustomType | customTypeDefaultValue() (recursive standalone-function call) |
Field-Level Options
The @default decorator allows specifying explicit default values:
@default(42)- Use 42 as the default@default("hello")- Use “hello” as the default@default([])- Use empty array as the default@default({ value: "test" })- Named form for complex values
Example
/** @derive(Default) */
class UserSettings {
/** @default("light") */
theme: string;
/** @default(10) */
pageSize: number;
notifications: boolean; // Uses type default: false
}class UserSettings {
theme: string;
pageSize: number;
notifications: boolean; // Uses type default: false
static defaultValue(): UserSettings {
const instance: UserSettings = Object.create(UserSettings.prototype);
instance.theme = 'light';
instance.pageSize = 10;
instance.notifications = false;
return instance;
}
}
export function userSettingsDefaultValue(): UserSettings {
return UserSettings.defaultValue();
}Enum Defaults
For enums, mark one variant with @default:
/** @derive(Default) */
enum Status {
/** @default */
Pending,
Active,
Completed
}enum Status {
/** @default */
Pending,
Active,
Completed
}
export function statusDefaultValue(): Status {
return Status.Pending;
}Like Rust’s derive, the class default runs no constructor (so it works beside Decode’s, which takes arguments). Each field gets its @default(value), else
its initializer as written, else its type’s default above. An optional field with
neither @default nor an initializer is left out, and a static property is never
a field.
/** @derive(Default) */
class Retry {
attempts: number = 3; // 3, its initializer
/** @default(500) */
delayMs: number = 100; // 500, @default wins
label?: string; // left out
}Primitive Aliases
A type alias of a primitive, plain or branded with $Newtype, defaults to the
primitive’s default above, or to its @default(value), cast to the alias:
/** @derive(Default) */
type Meters = $Newtype<number>;
export function metersDefaultValue(): Meters {
return 0 as Meters;
}An alias with @endec validators must give @default(value): the primitive’s
default may fail them, just as a Rust newtype with no valid zero derives no Default.
/** @derive(Decode, Default) */
/** @endec(positive) */
/** @default(1) */
type Meters = $Newtype<number>;Error Handling
The macro reports an expansion error only if:
- An enum has no variant marked with
@default - A type alias that is not an object, union or primitive has no
@default(value) - A primitive alias with
@endecvalidators has no@default(value) - A field’s
@defaultexpression fails to parse - A function-typed field has neither
@default(value)nor an initializer, as a Rustfnpointer has noDefault - A field’s initializer reads
this, which a default has no instance for
Missing defaults on custom-typed fields are not expansion errors: every type is
assumed to implement Default (Rust-like philosophy), so the macro emits a typeNameDefaultValue() call for any custom type. If a field’s type is in the
type registry but does not derive Default, a warning is printed to stderr at
expansion time and the call is generated anyway. It may then fail at runtime if
the function does not exist.