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 +99 -66
- package/dist/container/index.d.ts +3 -80
- package/dist/disposable/index.d.ts +4 -4
- package/dist/index.cjs +110 -51
- package/dist/index.d.ts +1 -1
- package/dist/index.js +118 -31
- package/dist/lazy/index.d.ts +5 -7
- package/dist/scope/index.d.ts +1 -0
- package/package.json +3 -8
- package/dist/index-g50fxds1.js +0 -34
- package/dist/scope/index.cjs +0 -190
- package/dist/scope/index.js +0 -131
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/
|
|
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/
|
|
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 —
|
|
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
|
-
|
|
148
|
+
scope.resolve(Database) === scope.resolve(Database); // true
|
|
125
149
|
|
|
126
150
|
// Transient — new instance every time
|
|
127
|
-
|
|
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.
|
|
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
|
|
269
|
+
const scope = createScope(container);
|
|
270
|
+
|
|
271
|
+
const logger = scope.resolve(Logger);
|
|
247
272
|
// ^? Logger
|
|
248
273
|
|
|
249
|
-
const db = await
|
|
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 =
|
|
335
|
+
const container = createContainer().registerSingleton(Connection, () => new Connection());
|
|
336
|
+
const dc = disposable(container);
|
|
311
337
|
|
|
312
|
-
container.resolve(Connection);
|
|
313
|
-
await
|
|
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
|
|
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
|
|
365
|
+
const dc = disposable(createContainer().registerSingleton(Connection, () => new Connection()));
|
|
340
366
|
|
|
341
|
-
|
|
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(
|
|
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
|
|
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/
|
|
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 —
|
|
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
|
-
|
|
534
|
+
scope.resolve(Analytics); // ContainerError: Token "Analytics" is not registered.
|
|
494
535
|
|
|
495
536
|
// tryResolve returns undefined for unregistered tokens
|
|
496
|
-
const 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
|
-
### `
|
|
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.
|
|
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 `
|
|
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
|
|
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/
|
|
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<
|
|
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
|
|
45
|
-
*
|
|
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
|