Skip to main content
Skip to main content

HexDI Documentation

AI writes code faster than architects can review it. HexDI solves the review problem structurally — the compiler is the architect.

When AI tools generate code, they produce architecturally plausible output that may be silently wrong: a circular dependency that fails at runtime, a scoped service resolved without a scope, a port that was supposed to be abstract now accessed directly. These errors don't show up in a linter, a formatter, or a standard code review. They show up in production.

HexDI catches them before the code builds.

// This fails to compile — not at runtime
const graph = GraphBuilder.create()
.provide(UserServiceAdapter) // requires Logger, Database
.build();
// TypeScript: Type '"ERROR[HEX008]: Missing adapters for Logger | Database. Call .provide() first."'
// is not assignable to type 'Graph<...>'

The compiler reviews the architecture. You review the logic.


How It Works

HexDI represents your application's dependency graph as a live TypeScript object. Every service declares what it provides and what it requires. The compiler validates that every declared requirement is satisfied before a container can be created.

Not a convention — a constraint.

Port → Adapter → Graph → Container
  1. Port — A contract. The what, not the how.
  2. Adapter — An implementation that declares its dependencies explicitly.
  3. Graph — A compile-time-validated collection of adapters. If it builds, it's wired correctly.
  4. Container — A runtime resolver that creates instances from the validated graph.
// 1. Define a contract
const LoggerPort = port<Logger>()({ name: 'Logger' });

// 2. Declare an implementation with explicit dependencies
const LoggerAdapter = createAdapter({
provides: LoggerPort,
requires: [],
lifetime: 'singleton',
factory: () => ({ log: console.log })
});

// 3. Build a structurally validated graph
const graph = GraphBuilder.create()
.provide(LoggerAdapter)
.build();

// 4. Resolve services at runtime
const container = createContainer({ graph, name: "App" });
const logger = container.tryResolve(LoggerPort); // Result<Logger, ContainerError>

The 4 Core Packages

Everything you need in one install:

pnpm add hex-di

The hex-di umbrella includes:

@hex-di/core      Port token system — the contracts
@hex-di/graph GraphBuilder — structural validation at compile time
@hex-di/runtime Container and scopes — deterministic resolution

For testing (devDependency):

pnpm add -D @hex-di/testing

For React applications:

pnpm add @hex-di/react

Structural Guarantees

Conventions enforced by hope. Constraints enforced by the compiler.

Most architectural rules in software are conventions: agreed upon, documented, checked in code review. They can be forgotten, bypassed, or simply missed under deadline pressure — especially when AI is generating the code.

HexDI makes architectural rules into structural facts:

ProblemHexDI makes it a compile error
Missing dependency"ERROR[HEX008]: Missing adapters for Logger. Call .provide() first."
Duplicate provider"ERROR[HEX001]: Duplicate adapter for 'Logger'. Fix: Remove one .provide() call."
Circular dependency"ERROR[HEX002]: Circular dependency: A -> B -> A. Fix: ..."
Resolving unknown portTypeScript error at call site
Wrong lifetime scopeScopeRequiredError at resolution

Architecture as a living object, not a diagram that drifts.

The dependency graph isn't in a wiki. It's in the code. At runtime, the graph is a queryable TypeScript object that reflects the actual wiring — not a stale Confluence page.

Structurally correct by construction.

If the graph compiles, all dependencies are satisfied. If the container builds, all adapters are correctly composed. The guarantee isn't "someone reviewed it" — it's "the type system verified it."


Getting Started

New to HexDI? Follow this path:

  1. Installation — One command. Done.
  2. Core Concepts — Ports, adapters, graphs, containers
  3. First Application — Build something complete
  4. Lifetimes — Singleton, scoped, transient

Guides

Patterns

API Reference


Package Architecture

                    ┌─────────────────┐
│ Your App │
└────────┬────────┘

┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│@hex-di/react │ │@hex-di/testing │(your adapters)│
│ (optional) │ │ (dev only) │ │ │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
└─────────────────┼─────────────────┘


┌─────────────────┐
│ @hex-di/runtime │
└────────┬────────┘


┌─────────────────┐
│ @hex-di/graph │
└────────┬────────┘


┌─────────────────┐
│ @hex-di/core │
│ (zero deps) │
└─────────────────┘

The Ecosystem

HexDI is more than a DI container. Every library in the ecosystem exposes its functionality as ports — contracts wired through the same container. Your application becomes a self-describing system: the container knows what it provides, how it's connected, and what each part is doing at runtime.

LibraryPurpose
@hex-di/loggerStructured logging with swappable backends (pino, winston, bunyan)
@hex-di/tracingDistributed tracing with W3C Trace Context (OTel, Datadog, Jaeger, Zipkin)
@hex-di/queryPort-based data fetching and caching
@hex-di/storeSignal-based reactive state as a DI port
@hex-di/flowState machines as dependency-injected services
@hex-di/sagaDistributed workflow orchestration
@hex-di/guardRole/permission/policy evaluation as a typed port
@hex-di/clockTestable time — injectable clock port with virtual time support
@hex-di/http-clientTyped HTTP client port with interceptors and retry

All libraries are designed to be composed together through the graph. See the repository README for the full package list.

Getting Help