Dependency graphs, just typed functions.
@favy/di v3 turns ordinary TypeScript functions into named, composable dependency providers. Dependency objects stay explicit, results stay inferred, and concrete implementations are selected at the composition root—without decorators or container configuration.
- Create named modules with ordinary functions, without decorators or container setup
- Carry transitive requirements through
typeof Foo.Livewhile TypeScript infers module results - Replace direct or transitive dependencies at the application boundary
- Bind known values incrementally with
.provide() - Choose per-run, factory-wide, or disabled caching
- Initialize dependencies lazily by default
- Extend input and output types with higher-kinded types
npm install @favy/diRequires TypeScript 5+.
import { Module } from '@favy/di';
const Clock = Module()('Clock', () => ({
now: () => new Date('2026-01-01T09:00:00.000Z'),
}));
type ClockLive = typeof Clock.Live;
const Greeting = Module<ClockLive>()('Greeting', ({ Clock }) =>
(name: string) => `Hello, ${name}! It is ${Clock.now().toISOString()}.`,
);
type GreetingLive = typeof Greeting.Live;
const greet = Greeting({ Clock });
console.log(greet('Ada')); // Hello, Ada! It is 2026-01-01T09:00:00.000Z.Module<Deps>()(name, factory)creates a named callable with explicit dependency types and an inferred result.typeof Clock.Livecombines the dependencies required byClockwith the value it provides under theClockkey. Declare the alias immediately below the module and compose these types to carry transitive requirements through the graph..Liveis a type-only marker: use it in a TypeScript type query; readingClock.Liveat runtime throws. The exportedLive<typeof Clock>helper remains equivalent.- The composition root is the top-level call, here
Greeting({ Clock }). Supply module implementations and dependency values there; @favy/di resolves the graph from that boundary.
Supply a matching value to replace a module implementation. This complete example fixes the clock for a test without changing Greeting:
import { Module } from '@favy/di';
const Clock = Module()('Clock', () => ({
now: () => new Date('2026-01-01T09:00:00.000Z'),
}));
type ClockLive = typeof Clock.Live;
const Greeting = Module<ClockLive>()('Greeting', ({ Clock }) =>
(name: string) => `Hello, ${name}! It is ${Clock.now().toISOString()}.`,
);
type GreetingLive = typeof Greeting.Live;
const greet = Greeting({
Clock: { now: () => new Date('2026-06-01T12:00:00.000Z') },
});
console.log(greet('Ada')); // Hello, Ada! It is 2026-06-01T12:00:00.000Z.See Testing for direct and transitive dependency replacement.
import { Module } from '@favy/di';
const Add = Module<{ left: number; right: number }>()(
'Add',
({ left, right }) => left + right,
);
type AddLive = typeof Add.Live;
const AddTen = Add.provide({ left: 10 });
type AddTenLive = typeof AddTen.Live;
console.log(AddTen({ right: 5 })); // 15.provide() returns a new module whose call signature requires only the remaining dependencies. The original module is unchanged. See Partial Application.
| Default | Behavior |
|---|---|
lazy: true |
A supplied provider runs only when its key is first read. |
cache: 'run' |
Its resolved value is reused for the current top-level call; if its key is read in a later run, the provider resolves again. |
Keep the dependency object intact when access needs to remain conditional; destructuring reads the selected keys immediately. Use makeModule to configure eager resolution, factory-wide caching, or disabled caching. See Lazy Initialization and Caching.
- Playground lets you edit and run focused examples.
- Introduction and Module explain the core model.
- Testing and Best Practices cover application boundaries and lifecycle choices.
- Transform Input and Transform Output cover custom factories.
- HKT support describes advanced type transformations.
- API Reference documents exports and signatures.
See the documentation contributor guide for local commands. Bug reports and pull requests are welcome in the GitHub repository.
@favy/di is distributed under the MIT license. See the LICENSE file.