npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

katagami

v3.0.3

Published

Type-safe dependency injection for TypeScript, with inferred types and scope checks for AI-assisted development. No decorators or reflect-metadata.

Readme

English | 日本語 | 한국어 | 繁體中文 | 简体中文 | Español | Deutsch | Français

Katagami

Type-safe dependency injection for TypeScript.

Make dependency wiring explicit and type-checked—even when AI coding agents write the code. Katagami accumulates types as you register dependencies, checks which tokens a factory can access, and tracks asynchronous results. No decorators, no reflect-metadata, no runtime dependencies.

npm version CI license

npm install katagami

Quick start

import { createContainer, createScope } from 'katagami';

const container = createContainer()
  .registerSingleton('logger', () => ({ log: (message: string) => console.log(message) }))
  .registerScoped('greeting', r => {
    const logger = r.resolve('logger'); // Inferred from the previous registration
    return (name: string) => logger.log(`Hello, ${name}!`);
  });

const scope = createScope(container);
scope.resolve('greeting')('world');
// scope.resolve('missing'); // Type error: this token has not been registered

No service interface or explicit generic argument is needed here. Literal string keys, unique symbols and class tokens can all be used. See type guarantees for the difference between accumulated registrations and a predeclared type map.

Why Katagami

Katagami combines registration-derived types, compile-time scope restrictions and zero runtime dependencies in an ordinary TypeScript factory API.

  • Types grow with your registrations. Literal keys and unique symbols carry their inferred service types into subsequent factories; required tokens outside that set are compile-time errors.
  • Request state stays explicit. Singleton and transient factories cannot resolve scoped tokens through their supplied typed resolver. You can find this mistake before starting the application.
  • No decorator setup. No experimentalDecorators, emitDecoratorMetadata or Reflect polyfill is needed for DI. Constructors and factories stay ordinary TypeScript.
  • Import the capabilities you use. Core DI, katagami/disposable and katagami/lazy are separate entry points. ESM exports and sideEffects: false support tree shaking; optional cleanup integrates with await using and the host's disposal symbols.

These checks assume narrow tokens and preserved registration types; see the class-token, mutation and predeclared-map boundaries.

Library comparison

27 features across eight libraries. Reviewed 2026-09-11, against the npm latest versions shown below and official documentation. Versions, sources and detailed notes.

✅ Built-in support · ⚠️ Conditions, a different model or application composition · ➖ No built-in support for this specific capability. Short labels identify the actual API or limitation.

Type safety and setup

| Feature | Katagami3.0.3 | InversifyJS8.2.3 | tsyringe4.10.0 | TypeDI0.10.0 | Awilix13.0.5 | NestJS12.0.1 | Effect3.22.2 | typed-inject5.0.0 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Runtime requirements | ✅ Standard TypeScript | ⚠️ Reflect metadata for class DI | ⚠️ Reflect metadata for class DI | ⚠️ Reflect metadata setup | ✅ No DI metadata | ⚠️ Nest modules / metadata | ✅ Effect / Layer APIs | ✅ Standard TypeScript | | Injection style | Explicit factories / constructors | Constructor / property / factory | Constructor / factory | Constructor / property / factory | Proxy / classic / factory | Constructor / property / factory | Functional services / layers | Constructor / factory + inject | | Token types | Class / string / number / symbol | Class / string / symbol | Class / string / symbol | Class / string / Token<T> | String / symbol | Class / string / symbol | Context.Tag | String literals | | Type safety | ✅ Inferred services + scope checks | ✅ Typed identifiers / bindings | ✅ Class / generic types | ✅ Class / Token<T> | ✅ Inferred cradle | ✅ Typed providers | ✅ Typed service requirements | ✅ Tokens + inject tuples | | Registration-derived types | ✅ Accumulated tokens | ➖ | ➖ | ➖ | ✅ register → cradle | ➖ | ⚠️ Layer requirements | ✅ Accumulated tokens | | Missing required tokens: compile-time check¹ | ✅ Literal / unique-symbol keys | ➖ Runtime check | ➖ Runtime check | ➖ Runtime check | ⚠️ Cradle only | ➖ Runtime graph | ✅ Unsatisfied requirements | ✅ Literal keys | | Scoped access from singleton/transient factories: compile-time check¹ | ✅ Scoped tokens excluded | ➖ | ➖ | ➖ | ⚠️ Runtime strict mode | ⚠️ Request-scope propagation | ⚠️ Different Scope model | ➖ No scoped lifetime | | Zero runtime dependency packages² | | ➖ | ➖ | ⚠️ Reflect polyfill installed separately | ⚠️ Browser entry differs | ➖ | ➖ | ✅ | | Tree-shaking support² | ✅ ESM / subpaths / sideEffects: false | ⚠️ ESM; sideEffects: true | ⚠️ ESM build | ✅ ESM / sideEffects: false | ⚠️ ESM / browser builds | ⚠️ ESM / framework setup | ✅ ESM / subpaths / side-effect declaration | ⚠️ ESM build |

Lifetimes, async services and cleanup

| Feature | Katagami3.0.3 | InversifyJS8.2.3 | tsyringe4.10.0 | TypeDI0.10.0 | Awilix13.0.5 | NestJS12.0.1 | Effect3.22.2 | typed-inject5.0.0 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Lifetimes | ✅ Singleton / Transient / Scoped | ✅ Singleton / Transient / Request | ✅ Singleton / Transient / Resolution / Container | ✅ Shared / Transient | ✅ Singleton / Transient / Scoped | ✅ Singleton / Transient / Request | ⚠️ Memoized / fresh layers + scopes | ✅ Singleton / Transient | | Request / scoped lifetime³ | ✅ Explicit per-request scope | ⚠️ One resolution graph | ✅ Container / resolution scoped | ⚠️ Named containers | ✅ Explicit per-request scope | ✅ HTTP request scope | ⚠️ Resource scopes | ⚠️ Child injectors; no Scoped provider | | Child containers / nested scopes³ | ✅ Nested scopes | ✅ Container hierarchy | ✅ Child containers | ⚠️ Named containers | ✅ Child scopes | ⚠️ Module / request contexts | ⚠️ Nested resource scopes | ✅ Child injectors | | Async factories | ✅ Promise-valued factories | ✅ Async bindings | ✅ Promise-valued factories | ✅ Promise-valued services | ✅ Promise-valued factories | ✅ Async providers | ✅ Effectful acquisition | ✅ Promise-valued factories | | Async result type tracking | ✅ Inferred Promise<T> | ✅ getAsync<T> | ⚠️ Promise-valued service type | ⚠️ Promise-valued service type | ✅ Inferred Promise<T> | ⚠️ Provider / consumer types | ✅ Effect result / error / requirements | ✅ Inferred Promise<T> | | Automatically await async dependencies⁴ | ➖ Explicit await | ✅ getAsync / getAllAsync | ➖ Consumer awaits | ➖ Consumer awaits | ➖ Consumer awaits | ✅ Before consumer construction | ✅ Effect composition | ➖ Consumer awaits | | Resource cleanup⁵ | ✅ Disposal symbols / await using | ⚠️ Singleton deactivation | ✅ Constructed disposables | ⚠️ destroy() on reset / removal | ⚠️ Cached values + disposer | ⚠️ App lifecycle hooks | ✅ Scope finalizers | ✅ Owned disposable instances | | Await asynchronous cleanup⁵ | Symbol.asyncDispose | ✅ Async deactivation | ✅ container.dispose() | ➖ destroy() is not awaited | ✅ container.dispose() | ⚠️ App hooks; not request-scoped classes | ✅ Effect finalizers | ✅ injector.dispose() |

Composition and advanced features

| Feature | Katagami3.0.3 | InversifyJS8.2.3 | tsyringe4.10.0 | TypeDI0.10.0 | Awilix13.0.5 | NestJS12.0.1 | Effect3.22.2 | typed-inject5.0.0 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Optional resolution | tryResolve / tryResolveAll | ✅ Optional get / inject | ✅ Optional injection | ⚠️ has then get | ✅ allowUnregistered | ✅ Optional injection | ✅ serviceOption | ⚠️ Compose optional values | | Multi-binding | resolveAll | ✅ getAll / getAllAsync | ✅ injectAll / resolveAll | ✅ getMany | ⚠️ Collection-valued service | ⚠️ Array provider | ⚠️ Collection-valued service | ⚠️ Collection-valued service | | Lazy resolution⁶ | lazy(); sync class tokens | ⚠️ Deferred identifiers / factories | ✅ delay() proxy | ⚠️ Deferred type reference | ⚠️ Cradle property access | ⚠️ LazyModuleLoader | ⚠️ Lazy effect execution | ⚠️ Inject a factory | | Conditional bindings | ⚠️ Tokens / factory logic | ✅ Contextual constraints | ✅ Predicate-aware factory | ⚠️ Factory logic | ⚠️ Local injection / factory logic | ⚠️ Dynamic modules / factories | ⚠️ Select / compose layers | ⚠️ Factory logic | | Auto-loading / discovery⁶ | ➖ Explicit use() | ⚠️ Class autobinding | ➖ Explicit registrations | ➖ Explicit imports | ✅ loadModules (Node) | ⚠️ DiscoveryService | ➖ Explicit layers | ➖ Explicit providers | | Module system / composition | use() | ✅ Container modules | ✅ @registry | ⚠️ Group registrations | ✅ loadModules / register | ✅ Modules / dynamic modules | ✅ Layer composition | ⚠️ Compose provider chains | | Circular dependency detection⁷ | ✅ Runtime cycle path | ✅ Runtime detection | ⚠️ Constructor error / delay | ⚠️ Deferred type references | ✅ Runtime cycle path | ⚠️ Cycle errors / forwardRef | ⚠️ Typed Layer requirements | ⚠️ Registration order constrains dependencies | | Middleware / interceptors⁶ | ⚠️ Higher-order factories | ✅ Activation / deactivation hooks | ✅ Before / after resolution | ⚠️ Factory wrappers | ⚠️ Factory wrappers | ⚠️ Request interceptors, not DI hooks | ⚠️ Effect composition | ⚠️ Provider decoration | | Snapshot / restore⁶ | | ✅ snapshot / restore | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | | Test substitution / isolation | ✅ Fresh scopes / containers + use() | ✅ Rebind / snapshots | ✅ Child container overrides | ✅ Named containers / reset | ✅ Child scopes / overrides | ✅ overrideProvider | ✅ Substitute test layers | ✅ Child injector overrides |

What stands out: Katagami combines accumulated registration types, scope-filtered factory resolvers, three lifetimes and zero runtime dependencies with direct r.resolve(token) calls. Optional/multiple resolution, module composition, lazy class resolution and standards-based cleanup stay available without decorator setup. Awilix, Effect and typed-inject also provide meaningful compile-time checks, as shown above.

  1. Type guarantees: Katagami's missing-token guarantee applies to accumulated literal keys and unique symbols with their types preserved. Class tokens, predeclared maps and mutable aliases have documented boundaries. Awilix rejects unknown cradle properties, but its broad resolve overload accepts unknown names. Effect checks service requirements in its own model. Scope checks here mean excluding scoped tokens from singleton/transient resolvers.
  2. Setup and bundles: Metadata notes describe the documented class-injection path; explicit value/factory bindings can avoid decorating individual services. Core Katagami needs no polyfills; disposal has host/compiler requirements. ESM and side-effect declarations help tree shaking, but these are packaging comparisons, not measured bundle sizes.
  3. Scopes: InversifyJS Request means one resolution graph, not an HTTP request. Named containers, module contexts, child injectors and Effect resource scopes are not identical lifetime policies.
  4. Async: Returning a Promise is distinct from awaiting dependencies before injection. Katagami keeps the Promise in the inferred type and leaves await explicit.
  5. Cleanup: Katagami's opt-in disposable() integrates Symbol.dispose, Symbol.asyncDispose and await using. Ownership varies by library; InversifyJS deactivation is for singletons, Awilix disposers are for cached values, and Nest hooks exclude request-scoped classes.
  6. Composition versus dedicated APIs: A service proxy, deferred token and lazy module are different features. Autobinding/discovery is not filesystem loading. Katagami's use() copies registrations; containers are mutable. Factory wrappers are not interceptor APIs, and fresh containers are not snapshots. Composition details.
  7. Cycles: Runtime cycle detection, deferred references and static dependency requirements are different mechanisms. A ⚠️ entry does not promise a general cycle detector; runtime checks do not imply detection of every asynchronous deadlock.

Why Katagami for AI-assisted development?

Give coding agents a concrete feedback loop: edit dependency wiring, run the TypeScript checker, and use the diagnostics to fix invalid dependencies. Explicit factories keep dependency edges in ordinary TypeScript code that both people and agents can read.

For example, a singleton must not capture state that belongs to one request:

import { createContainer } from 'katagami';

createContainer()
  .registerScoped('request', () => ({ id: crypto.randomUUID() }))
  // @ts-expect-error — a singleton factory cannot resolve this scoped token
  .registerSingleton('handler', r => r.resolve('request'));

The compiler reports No overload matches this call at r.resolve('request'). The factory's resolver does not include scoped tokens. Give the handler the same lifetime as the request:

import { createContainer } from 'katagami';

const container = createContainer()
  .registerScoped('request', () => ({ id: crypto.randomUUID() }))
  .registerScoped('handler', r => r.resolve('request'));

The @ts-expect-error above makes the deliberately invalid example a checked regression test. In application code, fix the lifetime and run npx tsc --noEmit; do not add a suppression. These examples prove specific compiler checks. Improvements in agent success rate or token usage have not been measured; the evaluation protocol describes how to test them.

Start here: Guide for AI coding agents · Runnable request-scope starter · Type safety and its boundaries

What you get

| Capability | Practical use | | --- | --- | | Accumulated registration types | Resolve registered literal tokens with inferred return types | | Scope-aware factory resolvers | Detect direct access to scoped dependencies from singleton/transient factories | | Async type tracking | Keep Promise results visible to the compiler | | Singleton, transient and scoped lifetimes | Share infrastructure and isolate request state | | Explicit factories and use() composition | Group registrations and replace infrastructure in tests | | Class, string, number and symbol tokens | Choose the token style that fits your application | | Optional and multiple resolution | Use tryResolve, resolveAll and tryResolveAll | | Resource cleanup and lazy resolution | Opt in through katagami/disposable and katagami/lazy | | ESM, CommonJS and zero runtime dependencies | Use standard tooling without decorator metadata setup |

Use Katagami when dependency wiring, test substitution or request lifetimes need structure. For a few dependencies, ordinary constructor/function parameters may be enough. See choosing a DI approach for trade-offs and links to alternatives.

Documentation

TypeScript examples use strict type checking. Core DI needs no polyfills. Optional resource cleanup requires the host's disposal symbols; await using also requires suitable TypeScript compiler/lib settings. See the compatibility notes.

About the name

型紙 (katagami) is stencil paper used in traditional Japanese dyeing. Layered stencils compose a pattern, just as registrations accumulate types in a method chain.

License

MIT