effx
Concepts

The IR

The semantic IR is a graph of nodes and edges defined with Effect Schema. Node kinds, edge kinds, normalization and the graph index.

The IR is the durable data model of effx. It is graph-shaped and defined with Effect Schema (ADR 0002). Analyses and generators read it. Nothing else is the source of truth.

Source: packages/ir/src/{ApplicationIR,Node,Edge,normalize,graph,migrate}.ts, docs/decisions/0002-ir-is-graph-shaped-and-schema-defined.md, docs/specs/0001-compiler-kernel.md.

ApplicationIR (Effect Schema: nodes + edges)       the durable data model
      │  toGraph / fromGraph
      ▼         law: fromGraph(toGraph(x)) == normalize(x)
GraphIndex { graph, byId, indices, ids (Trie), dangling, duplicates }   working indexes only

Envelope

FieldValue
format"effx-ir"
version2
nodesarray of Node
edgesarray of Edge

The current version is 2 (VERSION in ApplicationIR.ts). decode and decodeString accept version 1 and version 2 input and migrate version 1 to 2 without normalizing or dropping nodes (migrate.ts). ADR 0003 describes the envelope as version 1. That text predates the v2 migration (spec 0005).

Nodes

Node is a closed tagged union on _tag. Every node has an id (a StableId).

_tagFields besides id
Schemaref: SchemaRef
Modelname, schema: SchemaRef, table?, views: { name, schema: SchemaRef }[]
Servicename, symbol: SymbolRef
Operationname, kind (Query or Command), input, success, errors: { values: SchemaRef[], inferred }, requirements: { values: StableId[], inferred }, binding?: "external", handler?: SymbolRef
HttpGrouproot, rootSymbol?: SymbolRef, group, title?, description?, displayName?
Capabilityname, resource: StableId, focus?: StableId
Focusroot: StableId, path: string[]
Exposureoperation: StableId, transport
Extensionextension: string, tag: string, data: Json

Exposure.transport is a closed union:

_tagFields
httpmethod (GET, POST, PUT, PATCH, DELETE), path
rpcname
clicommand: string[]

An Operation is either local or external. An external operation has binding: "external" and no handler. A local operation has a handler. The node schema rejects any other combination. See External bindings.

Why transports are closed and extensions are open

Core analyses must pattern-match transports exhaustively. A third-party transport or any new concept uses an Extension node (ext:<extension>/<name> ids). Extension nodes survive normalization, canonical JSON and graph projection without changing the core schema. Promoting a concept into the core union is a versioned IR migration (spec 0001).

Edges

An Edge is { kind, from, to, qualifier? }.

KindFrom toQualifier
InputOf, SuccessOf, ErrorOfschema: to operation:none
Requiresoperation: to service:none
AuthorizedByoperation: to capability:none
Focusescapability: to focus:none
ExposedAsoperation: to exposure:transport tag
PersistsAsmodel: to schema:table name
ViewOfschema: to model:view name
ExtensionOfextension node to the node it extendsowner-defined (the access contract uses AccessContract)

The first nine rows are the spec 0001 table. ExtensionOf is in Edge.ts and is used by the HTTP contract, access, problem, Foldkit and client extensions (packages/compiler/src/extensions/). An Extension node with no ExtensionOf owner edge is reported by a core analysis.

An edge may point at a node that does not exist. That is data, not a decode failure. The graph index keeps such edges as dangling, and a core analysis reports missing targets.

Normalization

normalize puts the IR in one order so equal programs produce equal bytes.

WhatRule
Nodessorted by id, then by canonical encoding. Exact duplicates removed.
Edgessorted by (kind, from, to, qualifier or ""), then by canonical encoding. Exact duplicates removed.
Set fieldsModel.views, Operation.errors.values, Operation.requirements.values sorted and deduplicated
Ordered fieldsFocus.path and Exposure.transport.command are left as written
Idempotentnormalize(normalize(x)) equals normalize(x)

Two nodes with the same id but different content are both kept, and an analysis reports the duplicate id.

Graph index

IRGraph.toGraph builds a GraphIndex from a normalized IR. It is never written to disk.

FunctionResult
nodeOf(index, id)the node, if present
outgoing(index, id, kind?), incoming(index, id, kind?)edges, including dangling ones
reachable(index, id)ids reachable by following edge direction, excluding id
cycles(index)strongly connected components of size above one, plus self-loops
missingTargets(index)edges that point at absent nodes
withPrefix(index, prefix)ids with a given prefix
fromGraph(index)the normalized IR back

effx graph and effx inspect are built on these functions. See effx graph.

What is not in the IR

Not in the IRWhere it lives
ts.* objectsnowhere. Nothing from the TypeScript API crosses SourceFrontend.
Source locationsthe manifest (effx build)
Emit mode, target profile, output directoryGenerationContext (Emit modes and targets)
Schema valuesreferenced by SchemaRef (Schema refs)

On this page