katagami 2.2.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,21 +12,21 @@ Lightweight TypeScript DI container with full type inference.
12
12
 
13
13
  ## Features
14
14
 
15
- | Feature | Description |
16
- | ----------------------------- | ---------------------------------------------------------------------------------------------------------- |
17
- | Zero dependencies | No decorators, no reflect-metadata, no polyfills — works with any bundler out of the box |
18
- | Full type inference | Types accumulate through method chaining; unregistered tokens are compile-time errors |
19
- | Tree-shakeable | Subpath exports (`katagami/scope`, `katagami/disposable`) and `sideEffects: false` for minimal bundle size |
20
- | Captive dependency prevention | Singleton/Transient factories cannot access scoped tokens; caught at compile time |
21
- | Hybrid token strategy | Class tokens for strict type safety, PropertyKey tokens for flexibility |
22
- | Interface type map | Pass an interface to `createContainer<T>()` for order-independent registration |
23
- | Three lifetimes | Singleton, Transient, and Scoped with child containers |
24
- | Disposable support | TC39 Explicit Resource Management (`Symbol.dispose` / `Symbol.asyncDispose` / `await using`) |
25
- | Module composition | Containers can be composed via `use()` to group and reuse registrations |
26
- | Async factories | Promise-returning factories are automatically tracked by the type system |
27
- | Circular dependency detection | Clear error messages with the full cycle path |
28
- | Optional resolution | `tryResolve` returns `undefined` for unregistered tokens instead of throwing |
29
- | Lazy resolution | Proxy-based deferred instantiation via `lazy()` from `katagami/lazy`; instance created on first access |
15
+ | Feature | Description |
16
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------- |
17
+ | Zero dependencies | No decorators, no reflect-metadata, no polyfills — works with any bundler out of the box |
18
+ | Full type inference | Types accumulate through method chaining; unregistered tokens are compile-time errors |
19
+ | Tree-shakeable | Subpath exports (`katagami/disposable`, `katagami/lazy`) and `sideEffects: false` for minimal bundle size |
20
+ | Captive dependency prevention | Singleton/Transient factories cannot access scoped tokens; caught at compile time and runtime in scopes |
21
+ | Hybrid token strategy | Class tokens for strict type safety, PropertyKey tokens for flexibility |
22
+ | Interface type map | Pass an interface to `createContainer<T>()` for order-independent registration |
23
+ | Three lifetimes | Singleton, Transient, and Scoped with child containers |
24
+ | Disposable support | TC39 Explicit Resource Management (`Symbol.dispose` / `Symbol.asyncDispose` / `await using`) |
25
+ | Module composition | Containers can be composed via `use()` to group and reuse registrations |
26
+ | Async factories | Promise-returning factories are automatically tracked by the type system |
27
+ | Circular dependency detection | Clear error messages with the full cycle path |
28
+ | Optional resolution | `tryResolve` returns `undefined` for unregistered tokens instead of throwing |
29
+ | Lazy resolution | Proxy-based deferred instantiation via `lazy()` from `katagami/lazy`; instance created on first access |
30
30
 
31
31
  ## Install
32
32
 
@@ -37,7 +37,7 @@ npm install katagami
37
37
  ## Quick Start
38
38
 
39
39
  ```ts
40
- import { createContainer } from 'katagami';
40
+ import { createContainer, createScope } from 'katagami';
41
41
 
42
42
  class Logger {
43
43
  log(msg: string) {
@@ -56,7 +56,7 @@ const container = createContainer()
56
56
  .registerSingleton(Logger, () => new Logger())
57
57
  .registerSingleton(UserService, r => new UserService(r.resolve(Logger)));
58
58
 
59
- const userService = container.resolve(UserService);
59
+ const userService = createScope(container).resolve(UserService);
60
60
  // ^? UserService (fully inferred)
61
61
  userService.greet('world');
62
62
  ```
@@ -65,20 +65,42 @@ userService.greet('world');
65
65
 
66
66
  Most TypeScript DI containers rely on decorators, reflect-metadata, or string-based tokens — each bringing trade-offs in tooling compatibility, type safety, or bundle size. Katagami takes a different approach.
67
67
 
68
+ > **Note:** This comparison was researched on 2026-02-09. Features may have changed since then.
69
+
70
+ | Aspect | Katagami | InversifyJS | tsyringe | TypeDI | Awilix | NestJS | Effect | typed-inject |
71
+ | --------------------------------- | ---------------------------------------------------------------- | -------------------------------- | ----------------------------------------------- | --------------------------------------- | ----------------------------------- | ------------------------------------- | --------------------------------- | ------------------------------ |
72
+ | **Runtime requirements** | ✅ None | ❌ reflect-metadata, decorators | ❌ reflect-metadata, decorators | ❌ reflect-metadata, decorators | ✅ None | ❌ reflect-metadata, decorators | ✅ None | ✅ None |
73
+ | **Lifetimes** | ✅ Singleton, Transient, Scoped | ✅ Singleton, Transient, Request | ✅ Singleton, Transient, Resolution / Container | Singleton, Transient (named containers) | ✅ Singleton, Transient, Scoped | ✅ Singleton, Transient, Request | Shared (memoized), Scoped | ❌ Singleton, Transient |
74
+ | **Injection style** | Constructor (explicit factory) | Constructor, Property | Constructor | Constructor, Property | Constructor (proxy / classic) | Constructor | Functional (Tag + Layer) | Constructor (static inject) |
75
+ | **Token types** | Class, PropertyKey, Interface map | Class, String, Symbol | Class, String, Symbol | Class, String, Token\<T\> | String | Class, String, Symbol, InjectionToken | Context.Tag | String literal |
76
+ | **Type safety** | ✅ Compile-time; full inference, captive-dep guard (+ runtime) | ❌ Generic binding types | ❌ Generic types | ❌ Generic types, Token\<T\> | ❌ Cradle interface typing | ❌ Generic types | ✅ Compile-time; R type parameter | ✅ Compile-time; static inject |
77
+ | **Resource cleanup** | ✅ TC39 Symbol.dispose / asyncDispose; await using | Deactivation handlers | container.dispose() | Container.reset() | Disposer functions | Lifecycle hooks (onModuleDestroy) | Scope finalizers; acquireRelease | injector.dispose() |
78
+ | **Tree-shaking** | ✅ Subpath exports; sideEffects: false | ❌ | ❌ | ❌ | ✅ No decorator / metadata overhead | ❌ | ✅ Subpath exports; ESM | ✅ Zero deps; small bundle |
79
+ | **Async factories** | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ |
80
+ | **Optional resolution** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
81
+ | **Multi-binding** | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
82
+ | **Lazy resolution** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ LazyModuleLoader | ✅ Lazy by design | ❌ |
83
+ | **Conditional bindings** | ✅ Token separation + factory logic + scopes | ✅ Named, tagged, contextual | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
84
+ | **Auto-loading** | ✅ use() module composition (explicit, decorator-free by design) | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
85
+ | **Child containers** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
86
+ | **Module system** | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ |
87
+ | **Circular dependency detection** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ |
88
+ | **Middleware / Interceptors** | ✅ Higher-order factory wrappers | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
89
+ | **Snapshot / Restore** | ✅ Immutable containers; use() for test isolation | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
90
+
68
91
  ### No decorators, no reflect-metadata
69
92
 
70
93
  Decorator-based DI requires `experimentalDecorators` and `emitDecoratorMetadata` compiler options. Modern build tools such as esbuild and Vite (default configuration) do not support `emitDecoratorMetadata`, and the TC39 standard decorators proposal does not include an equivalent for automatic type metadata emission. Katagami depends on none of these — it works with any build tool out of the box.
71
94
 
72
95
  ### Tree-shakeable
73
96
 
74
- Katagami is split into subpath exports. Import only what you use — `katagami/scope`, `katagami/disposable`, and `katagami/lazy` are completely eliminated from the bundle if not imported. Combined with `sideEffects: false`, bundlers can remove every unused byte.
97
+ Katagami is split into subpath exports. Import only what you use — `katagami/disposable` and `katagami/lazy` are completely eliminated from the bundle if not imported. Combined with `sideEffects: false`, bundlers can remove every unused byte.
75
98
 
76
99
  ```ts
77
- // Core only — scope, disposable, and lazy are not included in the bundle
78
- import { createContainer } from 'katagami';
100
+ // Core only — disposable and lazy are not included in the bundle
101
+ import { createContainer, createScope } from 'katagami';
79
102
 
80
103
  // Import only what you need
81
- import { createScope } from 'katagami/scope';
82
104
  import { disposable } from 'katagami/disposable';
83
105
  import { lazy } from 'katagami/lazy';
84
106
  ```
@@ -106,7 +128,7 @@ No runtime dependencies, no polyfills. No need to add reflect-metadata (~50 KB u
106
128
  Singleton creates the instance on the first `resolve` and caches it. Transient creates a new instance every time.
107
129
 
108
130
  ```ts
109
- import { createContainer } from 'katagami';
131
+ import { createContainer, createScope } from 'katagami';
110
132
 
111
133
  class Database {
112
134
  constructor(public id = Math.random()) {}
@@ -120,20 +142,21 @@ const container = createContainer()
120
142
  .registerSingleton(Database, () => new Database())
121
143
  .registerTransient(RequestHandler, () => new RequestHandler());
122
144
 
145
+ const scope = createScope(container);
146
+
123
147
  // Singleton — same instance every time
124
- container.resolve(Database) === container.resolve(Database); // true
148
+ scope.resolve(Database) === scope.resolve(Database); // true
125
149
 
126
150
  // Transient — new instance every time
127
- container.resolve(RequestHandler) === container.resolve(RequestHandler); // false
151
+ scope.resolve(RequestHandler) === scope.resolve(RequestHandler); // false
128
152
  ```
129
153
 
130
154
  ### Scoped Lifetime & Child Containers
131
155
 
132
- Scoped registrations behave like singletons within a scope but produce a fresh instance in each new scope. Import `createScope` from `katagami/scope` to create a child container. Scoped tokens cannot be resolved from the root container.
156
+ Scoped registrations behave like singletons within a scope but produce a fresh instance in each new scope. Use `createScope` to create a child container. Scoped tokens cannot be resolved from the root container.
133
157
 
134
158
  ```ts
135
- import { createContainer } from 'katagami';
136
- import { createScope } from 'katagami/scope';
159
+ import { createContainer, createScope } from 'katagami';
137
160
 
138
161
  class DbPool {
139
162
  constructor(public name = 'main') {}
@@ -224,7 +247,7 @@ const container = createContainer().use(appModule);
224
247
  Factories that return a `Promise` are automatically tracked by the type system. When you `resolve` an async token, the return type is `Promise<V>` instead of `V`:
225
248
 
226
249
  ```ts
227
- import { createContainer } from 'katagami';
250
+ import { createContainer, createScope } from 'katagami';
228
251
 
229
252
  class Database {
230
253
  constructor(public connected: boolean) {}
@@ -243,10 +266,12 @@ const container = createContainer()
243
266
  return new Database(true);
244
267
  });
245
268
 
246
- const logger = container.resolve(Logger);
269
+ const scope = createScope(container);
270
+
271
+ const logger = scope.resolve(Logger);
247
272
  // ^? Logger
248
273
 
249
- const db = await container.resolve(Database);
274
+ const db = await scope.resolve(Database);
250
275
  // ^? Promise<Database> (awaited → Database)
251
276
  db.connected; // true
252
277
  ```
@@ -268,7 +293,7 @@ const container = createContainer()
268
293
  Katagami tracks which tokens are currently being resolved. If a circular dependency is found, a `ContainerError` is thrown with a clear message showing the full cycle path:
269
294
 
270
295
  ```ts
271
- import { createContainer } from 'katagami';
296
+ import { createContainer, createScope } from 'katagami';
272
297
 
273
298
  class ServiceA {
274
299
  constructor(public b: ServiceB) {}
@@ -282,7 +307,7 @@ const container = createContainer()
282
307
  .registerSingleton(ServiceA, r => new ServiceA(r.resolve(ServiceB)))
283
308
  .registerSingleton(ServiceB, r => new ServiceB(r.resolve(ServiceA)));
284
309
 
285
- container.resolve(ServiceA);
310
+ createScope(container).resolve(ServiceA);
286
311
  // ContainerError: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
287
312
  ```
288
313
 
@@ -297,7 +322,7 @@ ContainerError: Circular dependency detected: ServiceX -> ServiceY -> ServiceZ -
297
322
  Disposal is provided by the `disposable()` wrapper from `katagami/disposable`. Wrapping a container or scope attaches `[Symbol.asyncDispose]`, enabling `await using` syntax. When disposed, owned instances are iterated in reverse creation order (LIFO) and their `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` methods are called automatically.
298
323
 
299
324
  ```ts
300
- import { createContainer } from 'katagami';
325
+ import { createContainer, createScope } from 'katagami';
301
326
  import { disposable } from 'katagami/disposable';
302
327
 
303
328
  class Connection {
@@ -307,17 +332,18 @@ class Connection {
307
332
  }
308
333
 
309
334
  // Manual disposal
310
- const container = disposable(createContainer().registerSingleton(Connection, () => new Connection()));
335
+ const container = createContainer().registerSingleton(Connection, () => new Connection());
336
+ const dc = disposable(container);
311
337
 
312
- container.resolve(Connection);
313
- await container[Symbol.asyncDispose]();
338
+ createScope(container).resolve(Connection);
339
+ await dc[Symbol.asyncDispose]();
314
340
  // => "Connection closed"
315
341
  ```
316
342
 
317
343
  With `await using`, scopes are automatically disposed at the end of the block:
318
344
 
319
345
  ```ts
320
- import { createScope } from 'katagami/scope';
346
+ import { createContainer, createScope } from 'katagami';
321
347
  import { disposable } from 'katagami/disposable';
322
348
 
323
349
  const root = createContainer()
@@ -336,10 +362,9 @@ Scope disposal only affects scoped instances. Singleton instances are owned by t
336
362
  The `disposable()` wrapper also narrows the returned type so that registration methods (`registerSingleton`, `registerTransient`, `registerScoped`, `use`) are removed at the type level. This prevents accidental registration on a potentially-disposed container:
337
363
 
338
364
  ```ts
339
- const container = disposable(createContainer().registerSingleton(Connection, () => new Connection()));
365
+ const dc = disposable(createContainer().registerSingleton(Connection, () => new Connection()));
340
366
 
341
- container.resolve(Connection); // OK
342
- container.registerSingleton(/* ... */); // Compile-time error
367
+ dc.registerSingleton(/* ... */); // Compile-time error — registration methods are hidden
343
368
  ```
344
369
 
345
370
  ### Lazy Resolution
@@ -347,7 +372,7 @@ container.registerSingleton(/* ... */); // Compile-time error
347
372
  The `lazy()` function from `katagami/lazy` creates a proxy that defers instance creation until the first property access. This is useful for optimizing startup time or breaking circular dependencies.
348
373
 
349
374
  ```ts
350
- import { createContainer } from 'katagami';
375
+ import { createContainer, createScope } from 'katagami';
351
376
  import { lazy } from 'katagami/lazy';
352
377
 
353
378
  class HeavyService {
@@ -360,8 +385,9 @@ class HeavyService {
360
385
  }
361
386
 
362
387
  const container = createContainer().registerSingleton(HeavyService, () => new HeavyService());
388
+ const scope = createScope(container);
363
389
 
364
- const service = lazy(container, HeavyService);
390
+ const service = lazy(scope, HeavyService);
365
391
  // HeavyService is NOT instantiated yet
366
392
 
367
393
  service.process(); // instance created here, then cached
@@ -372,11 +398,9 @@ The proxy transparently forwards all property access, method calls, `in` checks,
372
398
 
373
399
  Only **sync class tokens** are supported. Async tokens and PropertyKey tokens are rejected at the type level because Proxy traps are synchronous.
374
400
 
375
- `lazy()` works with Container, Scope, DisposableContainer, and DisposableScope:
401
+ `lazy()` works with Scope and DisposableScope:
376
402
 
377
403
  ```ts
378
- import { createScope } from 'katagami/scope';
379
-
380
404
  const root = createContainer().registerScoped(RequestContext, () => new RequestContext());
381
405
  const scope = createScope(root);
382
406
 
@@ -385,14 +409,13 @@ const ctx = lazy(scope, RequestContext); // deferred scoped resolution
385
409
 
386
410
  ### Tree Shaking
387
411
 
388
- Katagami uses subpath exports to split functionality into independent entry points. If you only need the core container, `katagami/scope`, `katagami/disposable`, and `katagami/lazy` are completely excluded from the bundle. The package declares `sideEffects: false`, so bundlers can safely eliminate any unused code.
412
+ Katagami uses subpath exports to split functionality into independent entry points. If you only need the core container, `katagami/disposable` and `katagami/lazy` are completely excluded from the bundle. The package declares `sideEffects: false`, so bundlers can safely eliminate any unused code.
389
413
 
390
414
  ```ts
391
- // Core only — scope, disposable, and lazy are not included in the bundle
392
- import { createContainer } from 'katagami';
415
+ // Core only — disposable and lazy are not included in the bundle
416
+ import { createContainer, createScope } from 'katagami';
393
417
 
394
418
  // Import only what you need
395
- import { createScope } from 'katagami/scope';
396
419
  import { disposable } from 'katagami/disposable';
397
420
  import { lazy } from 'katagami/lazy';
398
421
  ```
@@ -402,7 +425,7 @@ import { lazy } from 'katagami/lazy';
402
425
  When you pass an interface to `createContainer<T>()`, PropertyKey tokens are typed from the interface rather than accumulated through chaining. This means you can register and resolve tokens in any order:
403
426
 
404
427
  ```ts
405
- import { createContainer } from 'katagami';
428
+ import { createContainer, createScope } from 'katagami';
406
429
 
407
430
  class Logger {
408
431
  log(msg: string) {
@@ -423,7 +446,7 @@ const container = createContainer<Services>()
423
446
  })
424
447
  .registerSingleton('logger', () => new Logger());
425
448
 
426
- const greeting = container.resolve('greeting');
449
+ const greeting = createScope(container).resolve('greeting');
427
450
  // ^? string
428
451
  ```
429
452
 
@@ -468,12 +491,29 @@ const container = createContainer()
468
491
  });
469
492
  ```
470
493
 
494
+ Katagami also enforces this rule at runtime within scopes. If a singleton factory attempts to resolve a scoped token — directly or through intermediaries — a `ContainerError` is thrown:
495
+
496
+ ```ts
497
+ import { createContainer, createScope } from 'katagami';
498
+
499
+ class DbPool {}
500
+ class RequestContext {}
501
+
502
+ const container = createContainer()
503
+ .registerScoped(RequestContext, () => new RequestContext())
504
+ .registerSingleton(DbPool, r => new DbPool(r.resolve(RequestContext)));
505
+
506
+ const scope = createScope(container);
507
+ scope.resolve(DbPool);
508
+ // ContainerError: Captive dependency detected: scoped token "RequestContext" cannot be resolved inside a singleton factory.
509
+ ```
510
+
471
511
  ### Optional Resolution (tryResolve)
472
512
 
473
513
  When you need to handle optional dependencies or want to check if a token is registered without throwing an error, use `tryResolve`. Unlike `resolve`, it returns `undefined` for unregistered tokens instead of throwing `ContainerError`:
474
514
 
475
515
  ```ts
476
- import { createContainer } from 'katagami';
516
+ import { createContainer, createScope } from 'katagami';
477
517
 
478
518
  class Logger {
479
519
  log(msg: string) {
@@ -488,12 +528,13 @@ class Analytics {
488
528
  }
489
529
 
490
530
  const container = createContainer().registerSingleton(Logger, () => new Logger());
531
+ const scope = createScope(container);
491
532
 
492
533
  // resolve throws for unregistered tokens
493
- container.resolve(Analytics); // ContainerError: Token "Analytics" is not registered.
534
+ scope.resolve(Analytics); // ContainerError: Token "Analytics" is not registered.
494
535
 
495
536
  // tryResolve returns undefined for unregistered tokens
496
- const analytics = container.tryResolve(Analytics);
537
+ const analytics = scope.tryResolve(Analytics);
497
538
  // ^? Analytics | undefined
498
539
  if (analytics) {
499
540
  analytics.track('event');
@@ -542,15 +583,7 @@ Registers a factory as scoped. Within a scope, the instance is created on the fi
542
583
 
543
584
  Copies all registrations from `source` (another `Container`) into this container. Only factory and lifetime entries are copied — singleton instance caches are not shared. Returns the container for method chaining.
544
585
 
545
- ### `Container.prototype.resolve(token)`
546
-
547
- Resolves and returns the instance for the given token. Throws `ContainerError` if the token is not registered or if a circular dependency is detected.
548
-
549
- ### `Container.prototype.tryResolve(token)`
550
-
551
- Attempts to resolve the instance for the given token. Returns `undefined` if the token is not registered, instead of throwing. Still throws `ContainerError` for circular dependencies or operations on disposed containers/scopes.
552
-
553
- ### `createScope(source)` — `katagami/scope`
586
+ ### `createScope(source)`
554
587
 
555
588
  Creates a new `Scope` (child container) from a `Container` or an existing `Scope`. The scope inherits all registrations from the source. Singleton instances are shared with the parent, while scoped instances are local to the scope.
556
589
 
@@ -560,7 +593,7 @@ A scoped child container created by `createScope()`.
560
593
 
561
594
  ### `Scope.prototype.resolve(token)`
562
595
 
563
- Resolves and returns the instance for the given token. Behaves the same as `Container.prototype.resolve`, but can also resolve scoped tokens.
596
+ Resolves and returns the instance for the given token. Throws `ContainerError` if the token is not registered or if a circular dependency is detected. Can resolve both non-scoped and scoped tokens.
564
597
 
565
598
  ### `Scope.prototype.tryResolve(token)`
566
599
 
@@ -568,11 +601,11 @@ Attempts to resolve the instance for the given token. Returns `undefined` if the
568
601
 
569
602
  ### `lazy(source, token)` — `katagami/lazy`
570
603
 
571
- Creates a Proxy that defers `resolve()` until the first property access. The resolved instance is cached — subsequent accesses use the cache. Only sync class tokens are supported; async tokens and PropertyKey tokens are rejected at the type level. Works with `Container`, `Scope`, `DisposableContainer`, and `DisposableScope`.
604
+ Creates a Proxy that defers `resolve()` until the first property access. The resolved instance is cached — subsequent accesses use the cache. Only sync class tokens are supported; async tokens and PropertyKey tokens are rejected at the type level. Works with `Scope` and `DisposableScope`.
572
605
 
573
606
  ### `disposable(container)` — `katagami/disposable`
574
607
 
575
- Attaches `[Symbol.asyncDispose]` to a `Container` or `Scope`, enabling `await using` syntax. Disposes all owned instances in reverse creation order (LIFO). Calls `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` on each instance that implements them. Idempotent — subsequent calls are no-ops. After disposal, `resolve()` and `createScope()` will throw `ContainerError`. The returned type is narrowed to `DisposableContainer` or `DisposableScope`, which only expose `resolve` and `tryResolve` — registration methods are excluded at the type level.
608
+ Attaches `[Symbol.asyncDispose]` to a `Container` or `Scope`, enabling `await using` syntax. Disposes all owned instances in reverse creation order (LIFO). Calls `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` on each instance that implements them. Idempotent — subsequent calls are no-ops. After disposal, `resolve()` and `createScope()` will throw `ContainerError`. The returned type is narrowed to `DisposableContainer` or `DisposableScope`. `DisposableContainer` exposes only disposal capability — registration and resolution methods are excluded. `DisposableScope` retains `resolve`, `tryResolve`, `resolveAll`, and `tryResolveAll` for resolution.
576
609
 
577
610
  ### `class ContainerError`
578
611
 
@@ -16,15 +16,12 @@ import type { AbstractConstructor, Resolver } from '../resolver';
16
16
  */
17
17
  export declare function createContainer<T = Record<never, never>, ScopedT = Record<never, never>>(): Container<T, never, never, ScopedT>;
18
18
  /**
19
- * Lightweight DI container.
19
+ * Lightweight DI container — registration only.
20
20
  *
21
- * Provides type inference through method chaining with registerSingleton/registerTransient/resolve.
22
- *
23
- * - Singleton: Creates the instance on the first resolve and returns the cached value thereafter.
24
- * - Transient: Creates a new instance via the factory function on every resolve.
21
+ * Provides type inference through method chaining with registerSingleton/registerTransient/registerScoped.
22
+ * Resolution is performed through a Scope created via `createScope(container)`.
25
23
  *
26
24
  * Registering the same token multiple times accumulates all factories.
27
- * `resolve()` returns the last registered instance, while `resolveAll()` returns all.
28
25
  *
29
26
  * @template T PropertyKey-based token type map (defined via interface, order-independent)
30
27
  * @template Sync Union of registered sync class constructors (accumulated via chaining, order-dependent)
@@ -36,7 +33,6 @@ export declare function createContainer<T = Record<never, never>, ScopedT = Reco
36
33
  export declare class Container<T = Record<never, never>, Sync extends AbstractConstructor = never, Async extends AbstractConstructor = never, ScopedT = Record<never, never>, ScopedSync extends AbstractConstructor = never, ScopedAsync extends AbstractConstructor = never> {
37
34
  private readonly registrations;
38
35
  private readonly singletonCache;
39
- private readonly resolvingTokens;
40
36
  private disposed;
41
37
  /**
42
38
  * Internal state accessor for extension modules (scope, disposable).
@@ -100,79 +96,6 @@ export declare class Container<T = Record<never, never>, Sync extends AbstractCo
100
96
  * @returns The container for method chaining
101
97
  */
102
98
  use<MT, MSync extends AbstractConstructor, MAsync extends AbstractConstructor, MScopedT, MScopedSync extends AbstractConstructor, MScopedAsync extends AbstractConstructor>(source: Container<MT, MSync, MAsync, MScopedT, MScopedSync, MScopedAsync>): Container<T & MT, Sync | MSync, Async | MAsync, ScopedT & MScopedT, ScopedSync | MScopedSync, ScopedAsync | MScopedAsync>;
103
- /**
104
- * Resolve an instance for the given token.
105
- *
106
- * Returns the instance from the last registered factory for the token.
107
- * For singleton registrations, creates the instance on the first call and caches it.
108
- * For transient registrations, creates a new instance on every call.
109
- *
110
- * @param token A registered token
111
- * @returns The instance associated with the token
112
- * @throws ContainerError if the token is not registered
113
- */
114
- resolve<V>(token: AbstractConstructor<V> & Async): Promise<V>;
115
- resolve<V>(token: AbstractConstructor<V> & Sync): V;
116
- resolve<K extends keyof T>(token: K): T[K];
117
- /**
118
- * Try to resolve an instance for the given token.
119
- *
120
- * Returns `undefined` instead of throwing when the token is not registered.
121
- * Other errors (circular dependency, disposed container) are still thrown.
122
- *
123
- * @param token A token to resolve
124
- * @returns The instance associated with the token, or `undefined` if not registered
125
- */
126
- tryResolve<V>(token: AbstractConstructor<V> & Async): Promise<V> | undefined;
127
- tryResolve<V>(token: AbstractConstructor<V> & Sync): V | undefined;
128
- tryResolve<K extends keyof T>(token: K): T[K] | undefined;
129
- tryResolve<V>(token: AbstractConstructor<V>): V | Promise<V> | undefined;
130
- tryResolve(token: PropertyKey): unknown;
131
- /**
132
- * Resolve all instances for the given token.
133
- *
134
- * Returns an array of instances from all registered factories for the token,
135
- * in registration order.
136
- *
137
- * @param token A registered token
138
- * @returns An array of instances associated with the token
139
- * @throws ContainerError if the token is not registered
140
- */
141
- resolveAll<V>(token: AbstractConstructor<V> & Async): Promise<V>[];
142
- resolveAll<V>(token: AbstractConstructor<V> & Sync): V[];
143
- resolveAll<K extends keyof T>(token: K): T[K][];
144
- /**
145
- * Try to resolve all instances for the given token.
146
- *
147
- * Returns `undefined` instead of throwing when the token is not registered.
148
- * Other errors (circular dependency, disposed container) are still thrown.
149
- *
150
- * @param token A token to resolve
151
- * @returns An array of instances associated with the token, or `undefined` if not registered
152
- */
153
- tryResolveAll<V>(token: AbstractConstructor<V> & Async): Promise<V>[] | undefined;
154
- tryResolveAll<V>(token: AbstractConstructor<V> & Sync): V[] | undefined;
155
- tryResolveAll<K extends keyof T>(token: K): T[K][] | undefined;
156
- tryResolveAll<V>(token: AbstractConstructor<V>): (V | Promise<V>)[] | undefined;
157
- tryResolveAll(token: PropertyKey): unknown;
158
- /**
159
- * Internal resolution logic shared by resolve and tryResolve.
160
- * Resolves the last registered factory for the token.
161
- *
162
- * @param token Token to resolve
163
- * @param required If true, throws when the token is not registered. If false, returns undefined.
164
- * @returns The resolved instance, or undefined if not registered and required is false
165
- */
166
- private resolveToken;
167
- /**
168
- * Internal resolution logic shared by resolveAll and tryResolveAll.
169
- * Resolves all registered factories for the token.
170
- *
171
- * @param token Token to resolve
172
- * @param required If true, throws when the token is not registered. If false, returns undefined.
173
- * @returns An array of resolved instances, or undefined if not registered and required is false
174
- */
175
- private resolveAllTokens;
176
99
  /**
177
100
  * Add a registration entry. Accumulates registrations for the same token.
178
101
  *
@@ -5,9 +5,9 @@ import type { Scope } from '../scope';
5
5
  /**
6
6
  * A container wrapped with `disposable()`.
7
7
  *
8
- * Only `resolve`, `tryResolve`, `resolveAll`, and `tryResolveAll` are available at the type level.
9
8
  * Registration methods (`registerSingleton`, `registerTransient`, `registerScoped`, `use`)
10
9
  * are excluded, preventing accidental registration on a potentially-disposed container.
10
+ * Use `createScope()` to create a scope for resolution.
11
11
  *
12
12
  * @template T PropertyKey-based token type map
13
13
  * @template Sync Union of registered sync class constructors
@@ -16,7 +16,7 @@ import type { Scope } from '../scope';
16
16
  * @template ScopedSync Union of scoped sync class constructors
17
17
  * @template ScopedAsync Union of scoped async class constructors
18
18
  */
19
- export interface DisposableContainer<T = Record<never, never>, Sync extends AbstractConstructor = never, Async extends AbstractConstructor = never, _ScopedT = Record<never, never>, _ScopedSync extends AbstractConstructor = never, _ScopedAsync extends AbstractConstructor = never> extends Resolver<T, Sync, Async>, AsyncDisposable {
19
+ export interface DisposableContainer<_T = Record<never, never>, _Sync extends AbstractConstructor = never, _Async extends AbstractConstructor = never, _ScopedT = Record<never, never>, _ScopedSync extends AbstractConstructor = never, _ScopedAsync extends AbstractConstructor = never> extends AsyncDisposable {
20
20
  readonly [INTERNALS]: ContainerInternals;
21
21
  }
22
22
  /**
@@ -41,8 +41,8 @@ export interface DisposableScope<T = Record<never, never>, Sync extends Abstract
41
41
  * Disposes owned instances in reverse creation order (LIFO), calling
42
42
  * `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` on each instance that implements them.
43
43
  *
44
- * The returned type is narrowed to only expose `resolve`, `tryResolve`, `resolveAll`, and `tryResolveAll`,
45
- * preventing registration methods from being called on a potentially-disposed container.
44
+ * The returned type prevents registration methods from being called on a potentially-disposed container.
45
+ * For scopes, `resolve`, `tryResolve`, `resolveAll`, and `tryResolveAll` remain available.
46
46
  *
47
47
  * @param container A Container or Scope to make disposable
48
48
  * @returns The same object with `AsyncDisposable` capability added and registration methods removed from the type