# Configuration

Macroforge can be configured with a `macroforge.config.ts` (or `.js`) file in your project root.

## Configuration File

Macroforge searches for config files in the following order, walking up from the input file's
directory:

- `macroforge.config.ts`
- `macroforge.config.mts`
- `macroforge.config.js`
- `macroforge.config.mjs`
- `macroforge.config.cjs`

Create a `macroforge.config.ts` file:

macroforge.config.ts

```
export default {
  keepDecorators: false,
  generateConvenienceConst: true,
};
```

## Sharing a Base Config

The config is read statically, never run, and the reader follows what a config inherits. Name a base
with `extends`, a path or an array of them with later ones winning:

apps/web/macroforge.config.ts

```
export default {
  extends: "../../macroforge.base.ts",
  foreignTypes: {
    "Option.Option": { from: ["effect"] },
  },
};
```

Fields the config sets replace the base's, except `foreignTypes`, which merge by type name: an entry
the config names again replaces the base's, and the rest are added.

A config can also export, spread or wrap a config it imports, with JavaScript's spread semantics, so
a field set after a spread replaces the spread one:

macroforge.config.ts

```
import base from "@acme/macroforge-config";

export default defineConfig({
  ...base,
  keepDecorators: true,
  foreignTypes: { ...base.foreignTypes, "Option.Option": { from: ["effect"] } },
});
```

Relative specifiers resolve against the importing file, and package specifiers from the nearest
`node_modules`, honouring its `exports` and `main`. A reference the reader cannot follow, such as a
config a function builds, is an error rather than an empty config. Editing a base config reaches
every config built on it.

## Options Reference

### keepDecorators

Type

`boolean`

Default

`false`

Whether to preserve `@derive` decorators in the output code after macro expansion. When `false`,
decorators are removed after expansion since they serve only as build-time directives. When `true`,
decorators are kept in the output, which can be useful for debugging or when using runtime
reflection.

### generateConvenienceConst

Type

`boolean`

Default

`true`

Whether to generate a convenience const for non-class types. When `true`, generates an
`export const TypeName = { ... } as const;` that groups all generated functions for a type into a
single namespace-like object. For example:
`export const User = { clone: userClone, encode: userEncode } as const;`.

### foreignTypes

Type

`Record<string, ForeignTypeHandler>`

Configuration files can define foreign type handlers for external types like Effect's `DateTime`.
When a matching type is found during expansion, the configured handlers are used automatically.

macroforge.config.ts

```
// macroforge.config.ts
import { DateTime } from "effect";
export default {
  foreignTypes: {
    "DateTime.DateTime": {
      from: ["effect"],
      aliases: [
        { name: "DateTime", from: "effect/DateTime" }
      ],
      encode: (v) => DateTime.formatIso(v),
      decode: (raw) => DateTime.unsafeFromDate(new Date(raw)),
      default: () => DateTime.unsafeNow(),
      // Optional shape check for union variant matching
      hasShape: (v) => v instanceof Date || typeof v === "string"
    }
  }
};
```

Each foreign type handler supports the following properties:

- `from`: Array of module paths this type can be imported from.
- `encode`: Function `(value) => unknown` for encoding.
- `decode`: Function `(raw) => T` for decoding.
- `default`: Function `() => T` for default value generation.
- `hasShape`: Optional function `(value) => boolean` used for shape-check predicate expression in
  union variant matching.
- `aliases`: Array of `{ name: string, from: string }` objects for alternative type-package pairs.

### vite

Type

`VitePluginConfig`

These options configure the `@macroforge/vite-plugin` behavior.

macroforge.config.ts

```
// macroforge.config.ts
export default {
  vite: {
    // Whether to generate .d.ts type definition files from expanded code
    generateTypes: true,
    typesOutputDir: ".macroforge/types",

    // Whether to emit macro IR metadata as JSON files
    emitMetadata: true,
    metadataOutputDir: ".macroforge/meta",

    // Enable disk-based expansion cache in dev mode (vite dev)
    devCache: true
  }
};
```

- `generateTypes`: Whether to generate `.d.ts` type definition files from expanded code (default:
  `true`).
- `typesOutputDir`: Output directory for generated type definitions, relative to project root
  (default: `".macroforge/types"`).
- `emitMetadata`: Whether to emit macro IR metadata as JSON files (default: `true`).
- `metadataOutputDir`: Output directory for metadata JSON files, relative to project root (default:
  `".macroforge/meta"`).
- `devCache`: Enable disk-based expansion cache in dev mode (`vite dev`) (default: `true`).

### cfg

Build flags consumed by the `@cfg` attribute macro. See [Attribute Macros](/docs/attributes) for the
annotation syntax.

macroforge.config.ts

```
export default {
  cfg: {
    features: ["beta", "experimental"],
    target: "node",
    debugAssertions: false,
    custom: { tier: "pro" }
  }
};
```

- `features`: Active feature flags; `@cfg(&lbrace; feature: "beta" &rbrace;)` passes when the value
  is a member (default: `[]`).
- `target`: Matched exactly against `@cfg(&lbrace; target: … &rbrace;)` (default: unset).
- `debugAssertions`: Boolean matched against `@cfg(&lbrace; debugAssertions: … &rbrace;)` (default:
  `false`).
- `custom`: Arbitrary keys matched exactly by any other annotation key (default:
  `&lbrace;&rbrace;`).

### deprecated

Behavior of the `@deprecated` attribute macro.

macroforge.config.ts

```
export default {
  deprecated: {
    runtimeWarn: true,
    failOnUse: false
  }
};
```

- `failOnUse`: Promote use of a deprecated symbol from an editor hint to a hard expansion error
  (default: `false`).
- `runtimeWarn`: Reserved. Defaults to `true` but currently has no effect: no runtime warning is
  injected.

### mustUse

Behavior of the `@mustUse` attribute macro.

macroforge.config.ts

```
export default {
  mustUse: { mode: "lint" }
};
```

- `mode`: Currently only `"lint"` is recognised, which emits a diagnostic when a return value is
  discarded (default: `"lint"`).

### nonExhaustive

Behavior of the `@nonExhaustive` attribute macro.

macroforge.config.ts

```
export default {
  nonExhaustive: { brand: "__nonExhaustive" }
};
```

- `brand`: Property name used in the branding intersection. Keep it stable across a project
  (default: `"__nonExhaustive"`).

### buildtime

Sandbox settings for `@buildtime` evaluation. See [Buildtime Evaluation](/docs/buildtime) for the
API.

macroforge.config.ts

```
export default {
  buildtime: {
    capabilities: {
      timeout: 5000,
      maxHeap: 256,
      filesystem: { read: ["src/**"] },
      env: ["NODE_ENV"]
    },
    flags: { CHANNEL: "beta" }
  }
};
```

- `capabilities.timeout`: Evaluation budget in milliseconds, enforced (default: `5000`).
- `capabilities.maxHeap`: Heap ceiling in MiB. Advisory: not currently enforced (default: `256`).
- `capabilities.filesystem.read`: Globs readable via `buildtime.fs` (default: `["**"]`).
- `capabilities.env`: Environment variable names exposed as `buildtime.env.NAME`. Deny-by-default
  (default: `[]`).
- `flags`: Values returned by `buildtime.flags.has()` / `.get()` (default: `&lbrace;&rbrace;`).

Capability keys may also be written flat (`buildtime.timeout`); the nested form is canonical because
it matches the path sandbox diagnostics point at. Build-time code can never write files or reach the
network, so `filesystem.write` and `network` are rejected.
