Skip to content

Releases

v4.0.0

Choose a tag to compare

@github-actions github-actions released this 18 Sep 21:41
26c232e

Add registration policies, typed registration metadata and public operations. Factory resolvers now
keep their lifetime checks after await, which changes some runtime behavior; review the breaking
changes before upgrading.

Breaking changes:

  • Pass factories a resolver bound to the requesting registration instead of the scope itself. It exposes
    only resolve, tryResolve, resolveAll and tryResolveAll.
  • Keep the singleton restriction on that resolver after the factory returns. A resolver stored by a
    singleton factory, or by a factory resolved while building a singleton, and a lazy() view created from
    such a resolver, now throw a captive-dependency ContainerError when they resolve a scoped token later.
  • Return Katagami's own Promise when a factory returns a native Promise, including async factories,
    instead of the Promise the factory returned. Its identity and custom properties are not preserved.
    Without a beforeReturn policy, repeated resolutions of a cached result still return the same Promise.
  • Detect cycles along each resolution chain, including while an async creation is pending. The factories
    still building a pending creation, through their resolvers or by calling the same scope synchronously,
    can no longer resolve its token again, even without waiting for it: this throws a circular-dependency
    ContainerError. In 3.x it recursed without end, rejected with a TypeError, never settled or returned
    the pending Promise, depending on the lifetime and on whether it was awaited. A resolver kept by a
    dependency that has finished building still returns the pending Promise to later callers; a creation
    that waits for its own Promise that way never settles, as in 3.x. Synchronous cycles are reported
    whichever resolver or scope they pass through.
  • Skip a cached async creation that rejected when disposing, instead of reporting it in the disposal
    AggregateError. The failure stays with the caller that resolved the token.
  • Throw ContainerError when registering on, or composing into, a disposed container.
  • Use new shared runtime keys. Containers, scopes and disposable views from 3.x cannot be mixed with 4.x
    in the same process.
  • Require TypeScript 5.0 or later: the published declarations use const type parameters.

Additions:

  • Share a policy object across modules with createContainer({ policy }): requiredMetadata is checked
    at compile time and at runtime, including .use(), and modules with a different policy are rejected.
  • Define typed metadata keys with createMetadataKey, attach them through the { metadata } registration
    option and read them with container.getMetadata(token).
  • Mark function-valued factories with entrypoint() and call them through
    createScope(container, { access: 'operations' }). Operations resolve on each call, always return a
    Promise, and are awaited by disposal. Unknown access values are rejected.
  • Check each resolution synchronously with the beforeResolve scope option, which receives the
    requesting registration and the resolution path.
  • Check values leaving top-level resolution and operation results with policy.beforeReturn, using the
    registrations each value was observed through. The records keep neither containers nor factories alive.
  • Export RegisteredTokens and the policy, metadata, resolution-event and operations-scope types, so
    consumers can emit declarations for code that builds operations scopes.
  • Give the new type parameters defaults; the 3.0.3 documentation examples and type tests compile unchanged.

See CHANGELOG.md and the registration policies guide.

What's Changed

  • feat!: add registration policies and public operations for 4.0.0 by @hiroiku in #4

Full Changelog: v3.0.3...v4.0.0

v3.0.3

Choose a tag to compare

@github-actions github-actions released this 11 Sep 15:00
d6ba131

What's Changed

  • docs: restore detailed comparison matrix for 3.0.3 by @hiroiku in #3

Full Changelog: v3.0.2...v3.0.3

v3.0.2

Choose a tag to compare

@github-actions github-actions released this 11 Sep 11:38
8061eed

What's Changed

  • Restore and refresh README library comparisons by @hiroiku in #2

Full Changelog: v3.0.1...v3.0.2

v3.0.1

Choose a tag to compare

@github-actions github-actions released this 11 Sep 11:04
768528f

What's Changed

  • Make type-safe DI usable in AI-assisted development by @hiroiku in #1

New Contributors

Full Changelog: v3.0.0...v3.0.1

v3.0.0

Choose a tag to compare

@hiroiku hiroiku released this 09 Feb 11:30

Breaking Changes

Container is now registration-only

Container no longer has resolve(), tryResolve(), resolveAll(), or tryResolveAll() methods. All resolution must go through a Scope created via createScope().

// Before (v2.x)
const container = createContainer()
  .registerSingleton(Logger, () => new Logger());
container.resolve(Logger);

// After (v3.x)
import { createContainer, createScope } from 'katagami';
const container = createContainer()
  .registerSingleton(Logger, () => new Logger());
const scope = createScope(container);
scope.resolve(Logger);

This separation ensures that:

  • Container is responsible only for registration and composition
  • Scope is responsible for resolution, caching, circular dependency detection, and captive dependency enforcement
  • The roles are cleanly separated with no duplication of resolution logic

lazy() no longer accepts Container or DisposableContainer

Since Container no longer has resolution methods, lazy() only accepts Scope or DisposableScope:

// Before (v2.x)
const service = lazy(container, HeavyService);

// After (v3.x)
const scope = createScope(container);
const service = lazy(scope, HeavyService);

DisposableContainer no longer extends Resolver

DisposableContainer no longer exposes resolve / tryResolve methods. Use createScope() on a disposable container to resolve tokens.

Migration Guide

  1. Replace all container.resolve(...) calls with createScope(container).resolve(...)
  2. Update lazy(container, ...) to lazy(createScope(container), ...)

Full Changelog: v2.3.0...v3.0.0

v2.3.0

Choose a tag to compare

@github-actions github-actions released this 09 Feb 07:49

New Features

Runtime captive dependency detection

Katagami now detects captive dependencies at runtime within scopes, in addition to existing compile-time prevention. If a singleton factory attempts to resolve a scoped token — directly or through intermediaries — a ContainerError is thrown with a clear message.

const container = createContainer()
  .registerScoped(RequestContext, () => new RequestContext())
  .registerSingleton(DbPool, r => new DbPool(r.resolve(RequestContext)));

const scope = createScope(container);
scope.resolve(DbPool);
// ContainerError: Captive dependency detected: scoped token "RequestContext"
//   cannot be resolved inside a singleton factory.

This uses a depth-tracking mechanism (singletonDepth) to detect when a scoped token is being resolved inside a singleton factory chain, catching issues that type-level checks alone cannot prevent (e.g., @ts-expect-error or any casts).

Both resolve() and tryResolve() / resolveAll() / tryResolveAll() enforce this rule.

Documentation

  • Added DI container comparison table to all multilingual README files

Full Changelog: v2.2.0...v2.3.0

v2.2.0

Choose a tag to compare

@github-actions github-actions released this 09 Feb 06:10

New Features

Multi-binding with resolveAll() / tryResolveAll()

Multiple factories can now be registered for the same token. resolve() returns the instance from the last registered factory, while resolveAll() returns instances from all factories in registration order.

const container = createContainer()
  .registerSingleton(Logger, () => new ConsoleLogger())
  .registerSingleton(Logger, () => new FileLogger());

container.resolve(Logger);    // FileLogger (last registered)
container.resolveAll(Logger); // [ConsoleLogger, FileLogger]

tryResolveAll() returns undefined instead of throwing when the token is not registered.

Per-registration singleton cache

Singleton caching is now per-registration rather than per-token. Each registered factory maintains its own independent cache, enabling correct behavior with multi-binding.

Lazy resolution with lazy()

The new katagami/lazy subpath export provides lazy() — a Proxy-based deferred instantiation function. The instance is not created until the first property access, then cached for subsequent accesses.

import { lazy } from 'katagami/lazy';

const service = lazy(container, HeavyService);
// HeavyService is NOT instantiated yet

service.process(); // instance created here, then cached

Only sync class tokens are supported — async tokens and PropertyKey tokens are rejected at the type level.

Bug Fixes

  • Removed unused ServiceA class from multi.test.ts files

Full Changelog: v2.1.0...v2.2.0

v2.1.0

Choose a tag to compare

@github-actions github-actions released this 09 Feb 03:05

New Features

Module composition with use()

Containers can now be composed via use() to group and reuse registrations. Define reusable modules as containers and apply them to other containers — only registration entries are copied, singleton instance caches are not shared.

const authModule = createContainer()
  .registerSingleton(AuthService, () => new AuthService())
  .registerSingleton(TokenService, () => new TokenService());

const container = createContainer()
  .use(authModule)
  .registerSingleton(UserService, r => new UserService(r.resolve(AuthService)));

Optional resolution with tryResolve()

tryResolve() returns undefined for unregistered tokens instead of throwing ContainerError. Useful for optional dependencies in factories.

const analytics = container.tryResolve(Analytics);
//    ^? Analytics | undefined

DisposableContainer / DisposableScope types

The disposable() wrapper now returns narrowed types (DisposableContainer or DisposableScope) that only expose resolve and tryResolve — registration methods are excluded at the type level, preventing accidental registration on a potentially-disposed container.

createScope() accepts disposable sources

createScope() now accepts DisposableContainer and DisposableScope in addition to Container and Scope.

Documentation

  • Updated all multilingual README files with new API documentation

Full Changelog: v2.0.0...v2.1.0

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 09 Feb 01:59

Breaking Changes

Tree-shakeable architecture with subpath exports

The library has been restructured into independent subpath exports for optimal tree-shaking:

  • katagami — Core container (createContainer, Container, ContainerError)
  • katagami/scope — Scoped child containers (createScope, Scope)
  • katagami/disposable — TC39 Explicit Resource Management (disposable())

Import only what you use — unused entry points are completely eliminated from the bundle. The package now declares sideEffects: false.

Disposal extracted to katagami/disposable

Container no longer implements AsyncDisposable directly. Instead, wrap a container or scope with the disposable() function from katagami/disposable to attach [Symbol.asyncDispose].

// Before (v1.x)
await using container = createContainer().registerSingleton(DB, () => new Database());

// After (v2.x)
import { disposable } from 'katagami/disposable';
await using container = disposable(
  createContainer().registerSingleton(DB, () => new Database())
);

createScope() moved to katagami/scope

container.createScope() method has been removed. Use the standalone createScope(container) function from katagami/scope instead.

// Before (v1.x)
const scope = container.createScope();

// After (v2.x)
import { createScope } from 'katagami/scope';
const scope = createScope(container);

Internal state accessor

Container internals are now exposed via a Symbol-keyed [INTERNALS] property, enabling extension modules (scope, disposable) to access internal state without coupling to class internals.

Other

  • Added multilingual README (Japanese, Korean, Chinese, Spanish, German, French)

Full Changelog: v1.1.0...v2.0.0

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 09 Feb 01:29

Maintenance

  • Updated version to 1.1.0
  • Fixed repository URL in package.json

Full Changelog: v1.0.0...v1.1.0