@macroforge/mcp-server
Version:
MCP server for Macroforge documentation and code analysis
71 lines (49 loc) • 2.31 kB
Markdown
# Cycles & Forward References
`JSON.stringify` throws on a cycle. Macroforge's generated encoders handle cyclic graphs by
assigning identities and emitting back-references.
```typescript
/** @derive(Encode, Decode) */
class Node {
name: string;
parent?: Node;
children: Node[];
}
const root = new Node();
const child = new Node();
root.children = [child];
child.parent = root; // cycle
const json = Node.encode(root, true); // keepMetadata
```
## Metadata
Cycle detection works by tagging objects with `__type` and `__id` as they're visited; a repeat visit
emits a reference to the existing id instead of recursing.
Those fields are **stripped from the output by default**. Pass `keepMetadata: true` to retain them:
which you must do if you intend to decode a cyclic graph back, since the ids are what reconnect it.
```typescript
Node.encode(root); // clean payload, cycles flattened
Node.encode(root, true); // round-trippable, includes __type/__id
```
For API responses you usually want the default. For persistence or transferring an object graph,
keep the metadata.
## The context object
`encodeWithContext` / `decodeWithContext` take a context that carries the identity map. The
top-level `encode` / `decode` create one for you; you only need the context form when composing
encoders by hand.
## Forward references
While decoding, a reference may point at an object that hasn't been constructed yet. Rather than
failing, the decoder records a `PendingRef` placeholder and patches it once the target exists.
Two consequences worth knowing:
- The field is temporarily `null` between the placeholder being recorded and patches being applied.
If you read the graph mid-decoding you may observe that.
- `ctx.applyPatches()` performs the fix-up. The top-level `decode` calls it for you.
## `freeze`
Pass `freeze: true` to deep-freeze every object produced:
```typescript
const r = Node.decode(input, { freeze: true });
```
Freezing happens after patches are applied, so cyclic graphs freeze correctly rather than being
frozen mid-construction.
## Errors
Structural failures inside `decodeWithContext` throw `DecodeError`. The top-level `decode` catches
these and returns `{ success: false, errors }` instead: so the entry point never throws, while the
internal context form does.