UNPKG

@macroforge/mcp-server

Version:

MCP server for Macroforge documentation and code analysis

83 lines (62 loc) • 3.66 kB
# Foreign Types A foreign type is one you don't own and can't annotate: `DateTime` from `effect`, `Decimal` from a math library, and so on. Instead of adding `encodeWith` to every field that uses it, configure it once in `macroforge.config.ts` and every derive picks it up. ```typescript // macroforge.config.ts import { DateTime } from 'effect'; export default { foreignTypes: { 'DateTime.Utc': { from: ['effect'], encode: (v) => DateTime.formatIso(v), decode: (raw) => DateTime.unsafeFromDate(new Date(raw)), default: () => DateTime.unsafeNow(), hasShape: (v) => typeof v === 'string' } } }; ``` Now any field typed `DateTime.Utc` encodes through your handlers. ## Options | Key | Purpose | | ---------- | ---------------------------------------------------------------------------------------------- | | `from` | Module paths this type may be imported from | | `aliases` | Additional `{ name, from }` pairs naming the same type | | `encode` | Value → JSON-safe representation | | `decode` | Raw JSON → value | | `default` | Value used by `@derive(Default)` and by missing-field defaults | | `hasShape` | Whether a value is this type, raw or decoded: tells it apart from the other members of a union | ## In a union with your own types A field can hold a foreign type or one of your own, as `RecordId | Employee` does, often through a generic alias such as `type RecordLink<T> = /** @default */ RecordId | T` declared in another module. Decoding asks `hasShape` of the raw value and encoding asks it of the live one, so it must recognise both: ```typescript hasShape: ((v): v is RecordId | string => v instanceof RecordId || typeof v === 'string'); ``` Whatever `hasShape` accepts goes through the foreign handlers; everything else goes through your type's own encoder and decoder. Expansion fails when such a union needs a handler the config does not give. ## Import-source validation `from` exists to disambiguate types that share a name across packages. A `Decimal` from `decimal.js` and one from your own module are different types that happen to collide. When a type's name matches a foreign-type entry but its **import source doesn't**, the match falls through to generic handling **silently**: the type may be a different one, and TypeScript reports any mismatch. If a foreign type appears not to be applying, check that the module path in `from` matches how the file actually imports it. When the name and source match but the qualification doesn't, such as `DateTime` for `"DateTime.DateTime"`, expansion warns and names the alias that would match it. ## Namespaced names A dotted key like `"DateTime.Utc"` is interpreted as namespace `DateTime`, type `Utc`. The namespace is derived from the key: there's no separate `namespace` option to set. To match the type under another name or path, list it in `aliases`. ## Setup Generated code imports your handlers from `#macroforge/config`, which your manifest has to declare once: ```bash macroforge init ``` Expansion fails with an error naming the missing entry until it is declared. [Expanded Config](/docs/endec/foreign-types/expanded-config) shows what the handlers become and why, with generated code before and after.