Dependencies stay explicit
Read the contract at the function boundary. TypeScript verifies every connection.
v3 · TypeScript-first DI
Explicit dependencies. Replaceable boundaries. No decorators, reflection, or container ceremony.
npm install @favy/di import { Module, type Live } from '@favy/di';
const Clock = Module()('Clock', () => ({
now: () => new Date(),
}));
type ClockLive = Live<typeof Clock>;
const Greeting = Module<ClockLive>()(
'Greeting',
({ Clock }) => ({
message: () => 'Hello at ' + Clock.now().toISOString(),
}),
);
Greeting({ Clock }).message(); Clock is resolved when Greeting uses it. Types
verify the connection before the graph runs.
One mental model
Each edge is an ordinary TypeScript contract. Follow it in the code, replace it in a test, or preconfigure it for the next caller.
A module is a named function with a typed input and output.
ClockLive names Clock's output for reuse as a typed dependency.
Supply implementations at the boundary and call the resulting graph.
Small API, deliberate control
Start with typed functions. Add lifetime and transformation rules at the boundary where they matter.
Read the contract at the function boundary. TypeScript verifies every connection.
Choose run, module, or no caching without changing the consumer.
Dependencies resolve lazily by default, including deep inside the graph.
Provide part of a graph once, then expose only what the caller should supply.
Pass a small fake at the same typed boundary—there is no container to reset.
Transform input and output while preserving inference through the graph.
Documentation paths
The core stays small. Pick a path when you need the next layer of control.
Use transformations and exact signatures when the graph gets advanced.
Build the first edge