<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Viacheslav Kabanov</title>
    <description>The latest articles on DEV Community by Viacheslav Kabanov (@maxrendel).</description>
    <link>https://dev.to/maxrendel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3910852%2Ffb6b5d17-e34c-4190-a4bb-2b9686cc4cc9.jpg</url>
      <title>DEV Community: Viacheslav Kabanov</title>
      <link>https://dev.to/maxrendel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maxrendel"/>
    <language>en</language>
    <item>
      <title>The Dependency Graph Is the Type: Meet InferDI</title>
      <dc:creator>Viacheslav Kabanov</dc:creator>
      <pubDate>Tue, 25 Aug 2026 10:37:34 +0000</pubDate>
      <link>https://dev.to/maxrendel/the-dependency-graph-is-the-type-meet-inferdi-266g</link>
      <guid>https://dev.to/maxrendel/the-dependency-graph-is-the-type-meet-inferdi-266g</guid>
      <description>&lt;p&gt;Most TypeScript dependency injection containers type &lt;strong&gt;services&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;InferDI goes further: it types the &lt;strong&gt;dependency graph itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As you register services, the container's type evolves with the graph. TypeScript knows which services exist, what their dependencies resolve to, their lifetimes, whether they belong to the synchronous or asynchronous graph, and whether required scope inputs are available.&lt;/p&gt;

&lt;p&gt;The result is a different failure model for DI.&lt;/p&gt;

&lt;p&gt;A missing dependency, an invalid lifetime relationship, or an unavailable request-scoped value doesn't have to wait until application startup to fail. In many cases, TypeScript rejects the graph while you're building it.&lt;/p&gt;

&lt;p&gt;And none of this requires decorators, reflection metadata, code generation, runtime dependencies, or a large runtime.&lt;/p&gt;

&lt;p&gt;The core stays below &lt;strong&gt;3 KiB gzipped&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @inferdi/inferdi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with JSR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno add jsr:@inferdi/inferdi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Graph Is the Type
&lt;/h2&gt;

&lt;p&gt;Start with ordinary TypeScript classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@inferdi/inferdi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is nothing DI-specific in them. No &lt;code&gt;@Injectable()&lt;/code&gt;, no &lt;code&gt;@Inject()&lt;/code&gt;, no &lt;code&gt;reflect-metadata&lt;/code&gt;, and no special compiler configuration.&lt;/p&gt;

&lt;p&gt;The graph is registered explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dsn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres://localhost/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userRepository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dsn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's interesting here isn't simply that &lt;code&gt;container.get()&lt;/code&gt; returns the right type.&lt;/p&gt;

&lt;p&gt;The registration itself is checked.&lt;/p&gt;

&lt;p&gt;Reference a dependency that doesn't exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userRepository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;databaseUrl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript rejects it.&lt;/p&gt;

&lt;p&gt;Put dependencies in an incompatible order and TypeScript rejects that too.&lt;/p&gt;

&lt;p&gt;InferDI isn't maintaining an untyped bag of registrations and adding types only at the point of resolution. Each registration changes the container's type, so the graph accumulates as TypeScript type-state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userRepository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;//    ^ UserRepository&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unknown keys never make it as far as runtime in a correctly typed graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lifetime Is Part of the Graph
&lt;/h2&gt;

&lt;p&gt;Missing services are only one class of DI bug.&lt;/p&gt;

&lt;p&gt;A nastier one is the captive dependency: a long-lived singleton accidentally retaining request-scoped state.&lt;/p&gt;

&lt;p&gt;With InferDI, lifetime information is carried by the graph itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RequestContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestContext&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;requestContext&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scoped&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;AppService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;requestContext&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;singleton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final registration is invalid.&lt;/p&gt;

&lt;p&gt;A singleton cannot directly depend on a scoped service, so the compiler rejects the relationship.&lt;/p&gt;

&lt;p&gt;In InferDI, lifetime isn't merely runtime configuration. It's part of the type-level graph.&lt;/p&gt;

&lt;p&gt;The same model covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;singleton&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scoped&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transient&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default runtime still performs cycle and lifetime checks as defense-in-depth for dynamic code, casts, and other ways around the type system. But for a normally typed graph, many of these mistakes are caught much earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Async Is Graph State Too
&lt;/h2&gt;

&lt;p&gt;Asynchronous initialization often turns DI APIs into a second, parallel abstraction.&lt;/p&gt;

&lt;p&gt;InferDI keeps async services in the same graph.&lt;/p&gt;

&lt;p&gt;Suppose a database requires asynchronous initialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dsn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres://localhost/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerAsyncFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dsn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph stores the final service type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;//    ^ Database&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registration represents an asynchronously initialized &lt;code&gt;Database&lt;/code&gt;, not a &lt;code&gt;Promise&amp;lt;Database&amp;gt;&lt;/code&gt; service.&lt;/p&gt;

&lt;p&gt;The more useful part appears when something depends on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dsn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres://localhost/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerAsyncFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dsn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userRepository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;userRepository&lt;/code&gt; depends on an async service, so InferDI propagates that state through the graph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userRepository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't have to rewrite every dependent registration as another async factory just because something deeper in the graph needs asynchronous initialization.&lt;/p&gt;

&lt;p&gt;Singleton and scoped async services also use single-flight initialization. Concurrent resolutions join the same initialization instead of starting duplicate work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Data Can Be Type-State
&lt;/h2&gt;

&lt;p&gt;Some dependencies don't exist until a request, job, transaction, or similar execution boundary begins.&lt;/p&gt;

&lt;p&gt;InferDI can model those values without introducing ambient global context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RequestContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;declareScopeInputs&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestContext&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This declaration is type-only. It doesn't create a request object, store one globally, or read from &lt;code&gt;AsyncLocalStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The application provides the actual value when it creates a scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createScope&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A service that depends on &lt;code&gt;request&lt;/code&gt; isn't considered ready until that input exists.&lt;/p&gt;

&lt;p&gt;So readiness itself becomes part of the graph's type-state.&lt;/p&gt;

&lt;p&gt;This fits naturally with HTTP servers, serverless functions, background jobs, multi-tenant applications, transactions, and other systems where some dependencies only exist inside an execution boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  No DI-Specific Toolchain
&lt;/h2&gt;

&lt;p&gt;InferDI doesn't need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;decorators&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reflect-metadata&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;experimentalDecorators&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;emitDecoratorMetadata&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;TypeScript transformers&lt;/li&gt;
&lt;li&gt;compiler plugins&lt;/li&gt;
&lt;li&gt;code generation&lt;/li&gt;
&lt;li&gt;runtime scanning&lt;/li&gt;
&lt;li&gt;parameter-name parsing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The constructor signature defines the dependency types. The dependency tuple defines their order.&lt;/p&gt;

&lt;p&gt;That gives TypeScript enough information to verify the relationship without making the domain class aware of the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CheckoutService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Payments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Orders&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CheckoutService&lt;/code&gt; doesn't know InferDI exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Core Across Modern Runtimes
&lt;/h2&gt;

&lt;p&gt;InferDI's core is framework-agnostic and has &lt;strong&gt;zero runtime dependencies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It relies on ordinary modern JavaScript rather than a platform-specific DI mechanism, so the same core can be used across environments such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Bun&lt;/li&gt;
&lt;li&gt;Deno&lt;/li&gt;
&lt;li&gt;browsers&lt;/li&gt;
&lt;li&gt;Cloudflare Workers&lt;/li&gt;
&lt;li&gt;Vercel Edge&lt;/li&gt;
&lt;li&gt;serverless functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moving a service from a backend process to a worker or edge runtime doesn't require changing the DI architecture or introducing a different metadata pipeline.&lt;/p&gt;

&lt;p&gt;The container remains the same.&lt;/p&gt;

&lt;p&gt;Framework-specific integration stays outside the core. InferDI provides adapter packages for frameworks including Hono, Fastify, Elysia, Koa, and Express without making any of them a dependency of the container itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under 3 KiB Gzipped
&lt;/h2&gt;

&lt;p&gt;Strong static guarantees often arrive together with more runtime machinery.&lt;/p&gt;

&lt;p&gt;InferDI deliberately avoids that trade-off.&lt;/p&gt;

&lt;p&gt;The core has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;zero runtime dependencies&lt;/li&gt;
&lt;li&gt;no reflection metadata&lt;/li&gt;
&lt;li&gt;no proxy-based resolution&lt;/li&gt;
&lt;li&gt;no decorator runtime&lt;/li&gt;
&lt;li&gt;no graph-analysis engine in production&lt;/li&gt;
&lt;li&gt;no framework machinery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The published core stays below &lt;strong&gt;3 KiB gzipped&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's useful anywhere, but particularly in serverless, edge, browser, CLI, and worker environments where startup cost and bundle size are part of the architecture rather than an afterthought.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Is Part of the Design
&lt;/h2&gt;

&lt;p&gt;The small runtime isn't the result of stripping features out of a conventional container. The resolution path itself is designed to stay small.&lt;/p&gt;

&lt;p&gt;For an already-created service, the first operation in &lt;code&gt;get()&lt;/code&gt; is the cache lookup.&lt;/p&gt;

&lt;p&gt;No metadata traversal happens before it. There is no decorator lookup, proxy trap, or resolve middleware pipeline.&lt;/p&gt;

&lt;p&gt;Class construction is optimized as well. Common constructor arities use direct calls rather than sending every instantiation through one generic reflection path.&lt;/p&gt;

&lt;p&gt;InferDI exposes two runtime contracts.&lt;/p&gt;

&lt;p&gt;The default container keeps runtime cycle and lifetime protection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For applications whose graphs are fixed and controlled at compile time, fast mode removes that bookkeeping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fast mode isn't a way to make an invalid graph valid.&lt;/p&gt;

&lt;p&gt;It's a fixed-graph contract: the application relies on the compile-time model instead of paying for the same runtime safety checks during resolution.&lt;/p&gt;

&lt;p&gt;Performance here isn't an optimization applied after the API was designed. The resolve path is part of the architecture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7cbtalnl240ra6m97mw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7cbtalnl240ra6m97mw.jpg" alt="Benchmarks results" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ Source data&lt;/strong&gt;&lt;br&gt;
The summary table is based on benchmark results available in the project repository. You can find the full benchmark source code, environment details, and execution workloads in the benchmarks directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lazy Dependencies Without Proxy Magic
&lt;/h2&gt;

&lt;p&gt;Sometimes a dependency genuinely should be resolved later.&lt;/p&gt;

&lt;p&gt;InferDI supports explicit lazy companions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;singleton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;databaseLazy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The companion is a tiny wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lazy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;databaseLazy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resolving the wrapper does not instantiate the database.&lt;/p&gt;

&lt;p&gt;Async graphs use the corresponding &lt;code&gt;AsyncLazy&amp;lt;T&amp;gt;&lt;/code&gt; behavior.&lt;/p&gt;

&lt;p&gt;The important part is that laziness stays visible. Resolution isn't hidden behind a transparent proxy, and the target's lifetime semantics are preserved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native Resource Management
&lt;/h2&gt;

&lt;p&gt;Scopes often own real resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database transactions&lt;/li&gt;
&lt;li&gt;request-local clients&lt;/li&gt;
&lt;li&gt;temporary files&lt;/li&gt;
&lt;li&gt;streams&lt;/li&gt;
&lt;li&gt;sockets&lt;/li&gt;
&lt;li&gt;other disposable objects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;InferDI integrates with JavaScript Explicit Resource Management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createScope&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the scope ends, resources owned by that container are disposed in reverse creation order.&lt;/p&gt;

&lt;p&gt;Async resources can use &lt;code&gt;Symbol.asyncDispose&lt;/code&gt;; synchronous ones can use &lt;code&gt;Symbol.dispose&lt;/code&gt; or a conventional &lt;code&gt;dispose()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Ownership is explicit. InferDI doesn't simply dispose every object that happens to pass through the container.&lt;/p&gt;

&lt;p&gt;Application-provided values, transient results, overrides, and scope inputs remain externally owned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modules Are Checked Graph Contracts
&lt;/h2&gt;

&lt;p&gt;Real applications rarely build an entire dependency graph in one long function.&lt;/p&gt;

&lt;p&gt;InferDI modules can describe both what a graph fragment &lt;strong&gt;requires&lt;/strong&gt; and what it &lt;strong&gt;provides&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That keeps composition type-safe too.&lt;/p&gt;

&lt;p&gt;A module can't quietly assume that the caller contains a dependency with the wrong lifetime or async state. It also can't silently overwrite an existing output key.&lt;/p&gt;

&lt;p&gt;In other words, a module is a typed graph transformation rather than a bag of registrations executed at runtime.&lt;/p&gt;

&lt;p&gt;This follows from the same underlying idea: the dependency graph is something TypeScript can reason about while the application is being composed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What InferDI Is Not
&lt;/h2&gt;

&lt;p&gt;InferDI deliberately leaves some things out.&lt;/p&gt;

&lt;p&gt;It isn't trying to become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a decorator framework&lt;/li&gt;
&lt;li&gt;a reflection container&lt;/li&gt;
&lt;li&gt;an auto-wiring scanner&lt;/li&gt;
&lt;li&gt;an AOP/interceptor engine&lt;/li&gt;
&lt;li&gt;a runtime module-discovery system&lt;/li&gt;
&lt;li&gt;an ambient request-context system&lt;/li&gt;
&lt;li&gt;a universal IoC framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those features can make sense in other architectures.&lt;/p&gt;

&lt;p&gt;They don't belong in a DI core built around static graph guarantees, a tiny runtime, portability, and a minimal resolution path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;TypeScript DI has traditionally involved trade-offs.&lt;/p&gt;

&lt;p&gt;Runtime convenience often means more metadata or magic. Stronger typing tends to require a more explicit API. Performance-focused containers may give up architectural checks. Portable containers can end up with fewer features.&lt;/p&gt;

&lt;p&gt;InferDI takes a different route.&lt;/p&gt;

&lt;p&gt;Its graph carries architectural information directly through the type system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service types&lt;/li&gt;
&lt;li&gt;dependency compatibility&lt;/li&gt;
&lt;li&gt;lifetimes&lt;/li&gt;
&lt;li&gt;async propagation&lt;/li&gt;
&lt;li&gt;lazy relationships&lt;/li&gt;
&lt;li&gt;scope readiness&lt;/li&gt;
&lt;li&gt;module requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At runtime, the core remains dependency-free and below 3 KiB gzipped.&lt;/p&gt;

&lt;p&gt;There is no decorator metadata to reconstruct, no DI-specific build step, and no separate runtime graph model that has to rediscover information TypeScript already knew.&lt;/p&gt;

&lt;p&gt;That's the real shift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InferDI doesn't just add types to a dependency container. The dependency graph itself becomes part of the program's type-state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And once the graph is part of the type system, TypeScript can prove considerably more about the application's architecture before that application ever starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try InferDI
&lt;/h2&gt;

&lt;p&gt;Check out the &lt;a href="https://inferdi.com" rel="noopener noreferrer"&gt;InferDI Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want dependency injection without reflection, decorators, runtime dependencies, or unnecessary work on the resolution path, InferDI is built around exactly that model.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>dependencyinjection</category>
    </item>
  </channel>
</rss>
