katagami 1.1.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/LICENSE +21 -0
- package/README.de.md +438 -0
- package/README.es.md +386 -0
- package/README.fr.md +386 -0
- package/README.ja.md +438 -0
- package/README.ko.md +438 -0
- package/README.md +438 -0
- package/README.zh-CN.md +438 -0
- package/README.zh-TW.md +438 -0
- package/dist/container/index.d.ts +146 -0
- package/dist/error/index.d.ts +11 -0
- package/dist/index.cjs +264 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +232 -0
- package/dist/resolver/index.d.ts +68 -0
- package/dist/scope/index.d.ts +81 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
[English](./README.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | [繁體中文](./README.zh-TW.md) | [简体中文](./README.zh-CN.md) | [Español](./README.es.md) | [Deutsch](./README.de.md) | [Français](./README.fr.md)
|
|
2
|
+
|
|
3
|
+
# Katagami
|
|
4
|
+
|
|
5
|
+
Lightweight TypeScript DI container with full type inference.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/katagami)
|
|
8
|
+
[](https://github.com/hiroiku/katagami/blob/master/LICENSE)
|
|
9
|
+
[](https://bundlephobia.com/package/katagami)
|
|
10
|
+
|
|
11
|
+
> The name comes from 型紙 _(katagami)_ — precision stencil paper used in traditional Japanese dyeing to transfer exact patterns onto fabric. Multiple stencils are layered to compose intricate designs, just as types accumulate through each method-chain call. A stencil needs only paper and a brush, no elaborate machinery — likewise, Katagami requires no decorators or metadata mechanisms and works with any build tool out of the box. And like stencils that work across different fabrics and techniques, Katagami adapts across TypeScript and JavaScript, class tokens and PropertyKey tokens — a hybrid approach to strict, composable DI.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
| Feature | Description |
|
|
16
|
+
| ----------------------------- | -------------------------------------------------------------------------------------------- |
|
|
17
|
+
| Full type inference | Types accumulate through method chaining; unregistered tokens are compile-time errors |
|
|
18
|
+
| Three lifetimes | Singleton, Transient, and Scoped with child containers |
|
|
19
|
+
| Async factories | Promise-returning factories are automatically tracked by the type system |
|
|
20
|
+
| Circular dependency detection | Clear error messages with the full cycle path |
|
|
21
|
+
| Disposable support | TC39 Explicit Resource Management (`Symbol.dispose` / `Symbol.asyncDispose` / `await using`) |
|
|
22
|
+
| Captive dependency prevention | Singleton/Transient factories cannot access scoped tokens; caught at compile time |
|
|
23
|
+
| Optional resolution | `tryResolve` returns `undefined` for unregistered tokens instead of throwing |
|
|
24
|
+
| Hybrid token strategy | Class tokens for strict type safety, PropertyKey tokens for flexibility |
|
|
25
|
+
| Interface type map | Pass an interface to `createContainer<T>()` for order-independent registration |
|
|
26
|
+
| Zero dependencies | No decorators, no reflect-metadata, no polyfills |
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install katagami
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createContainer } from 'katagami';
|
|
38
|
+
|
|
39
|
+
class Logger {
|
|
40
|
+
log(msg: string) {
|
|
41
|
+
console.log(msg);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class UserService {
|
|
46
|
+
constructor(private logger: Logger) {}
|
|
47
|
+
greet(name: string) {
|
|
48
|
+
this.logger.log(`Hello, ${name}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const container = createContainer()
|
|
53
|
+
.registerSingleton(Logger, () => new Logger())
|
|
54
|
+
.registerSingleton(UserService, r => new UserService(r.resolve(Logger)));
|
|
55
|
+
|
|
56
|
+
const userService = container.resolve(UserService);
|
|
57
|
+
// ^? UserService (fully inferred)
|
|
58
|
+
userService.greet('world');
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Why Katagami
|
|
62
|
+
|
|
63
|
+
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.
|
|
64
|
+
|
|
65
|
+
### No decorators, no reflect-metadata
|
|
66
|
+
|
|
67
|
+
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.
|
|
68
|
+
|
|
69
|
+
### Full type inference from class tokens
|
|
70
|
+
|
|
71
|
+
String-token DI forces you to maintain manual token-to-type mappings. Parameter-name matching breaks under minification. Katagami uses classes directly as tokens, so `resolve` automatically infers the correct return type — synchronous or `Promise` — with no extra annotations.
|
|
72
|
+
|
|
73
|
+
### Method-chain type accumulation
|
|
74
|
+
|
|
75
|
+
Types accumulate with each `register` call. Inside a factory, the resolver only accepts tokens that have already been registered at that point in the chain. Resolving an unregistered token is a compile-time error, not a runtime surprise.
|
|
76
|
+
|
|
77
|
+
### Hybrid token strategy
|
|
78
|
+
|
|
79
|
+
Class tokens give you strict, order-dependent type safety through method chaining. But sometimes you want to define a set of services upfront and register them in any order. Pass an interface to `createContainer<T>()` and use PropertyKey tokens — the type map is fixed at creation time, so registration order does not matter.
|
|
80
|
+
|
|
81
|
+
### Zero dependencies
|
|
82
|
+
|
|
83
|
+
No runtime dependencies, no polyfills. No need to add reflect-metadata (~50 KB unminified) to your bundle.
|
|
84
|
+
|
|
85
|
+
## Guide
|
|
86
|
+
|
|
87
|
+
### Singleton & Transient
|
|
88
|
+
|
|
89
|
+
Singleton creates the instance on the first `resolve` and caches it. Transient creates a new instance every time.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createContainer } from 'katagami';
|
|
93
|
+
|
|
94
|
+
class Database {
|
|
95
|
+
constructor(public id = Math.random()) {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class RequestHandler {
|
|
99
|
+
constructor(public id = Math.random()) {}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const container = createContainer()
|
|
103
|
+
.registerSingleton(Database, () => new Database())
|
|
104
|
+
.registerTransient(RequestHandler, () => new RequestHandler());
|
|
105
|
+
|
|
106
|
+
// Singleton — same instance every time
|
|
107
|
+
container.resolve(Database) === container.resolve(Database); // true
|
|
108
|
+
|
|
109
|
+
// Transient — new instance every time
|
|
110
|
+
container.resolve(RequestHandler) === container.resolve(RequestHandler); // false
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Scoped Lifetime & Child Containers
|
|
114
|
+
|
|
115
|
+
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.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { createContainer } from 'katagami';
|
|
119
|
+
|
|
120
|
+
class DbPool {
|
|
121
|
+
constructor(public name = 'main') {}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class RequestContext {
|
|
125
|
+
constructor(public id = Math.random()) {}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const root = createContainer()
|
|
129
|
+
.registerSingleton(DbPool, () => new DbPool())
|
|
130
|
+
.registerScoped(RequestContext, () => new RequestContext());
|
|
131
|
+
|
|
132
|
+
// Create a scope for each request
|
|
133
|
+
const scope1 = root.createScope();
|
|
134
|
+
const scope2 = root.createScope();
|
|
135
|
+
|
|
136
|
+
// Scoped — same within a scope, different across scopes
|
|
137
|
+
scope1.resolve(RequestContext) === scope1.resolve(RequestContext); // true
|
|
138
|
+
scope1.resolve(RequestContext) === scope2.resolve(RequestContext); // false
|
|
139
|
+
|
|
140
|
+
// Singleton — shared across all scopes
|
|
141
|
+
scope1.resolve(DbPool) === scope2.resolve(DbPool); // true
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Scopes can also be nested. Each nested scope has its own scoped instance cache while sharing singletons with its parent:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const parentScope = root.createScope();
|
|
148
|
+
const childScope = parentScope.createScope();
|
|
149
|
+
|
|
150
|
+
// Each nested scope gets its own scoped instances
|
|
151
|
+
parentScope.resolve(RequestContext) === childScope.resolve(RequestContext); // false
|
|
152
|
+
|
|
153
|
+
// Singletons are still shared
|
|
154
|
+
parentScope.resolve(DbPool) === childScope.resolve(DbPool); // true
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Async Factories
|
|
158
|
+
|
|
159
|
+
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`:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { createContainer } from 'katagami';
|
|
163
|
+
|
|
164
|
+
class Database {
|
|
165
|
+
constructor(public connected: boolean) {}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class Logger {
|
|
169
|
+
log(msg: string) {
|
|
170
|
+
console.log(msg);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const container = createContainer()
|
|
175
|
+
.registerSingleton(Logger, () => new Logger())
|
|
176
|
+
.registerSingleton(Database, async () => {
|
|
177
|
+
await new Promise(r => setTimeout(r, 100)); // simulate async init
|
|
178
|
+
return new Database(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const logger = container.resolve(Logger);
|
|
182
|
+
// ^? Logger
|
|
183
|
+
|
|
184
|
+
const db = await container.resolve(Database);
|
|
185
|
+
// ^? Promise<Database> (awaited → Database)
|
|
186
|
+
db.connected; // true
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Async factories can depend on both sync and async registrations:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const container = createContainer()
|
|
193
|
+
.registerSingleton(Logger, () => new Logger())
|
|
194
|
+
.registerSingleton(Database, async r => {
|
|
195
|
+
const logger = r.resolve(Logger); // sync → Logger
|
|
196
|
+
logger.log('Connecting...');
|
|
197
|
+
return new Database(true);
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Circular Dependency Detection
|
|
202
|
+
|
|
203
|
+
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:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { createContainer } from 'katagami';
|
|
207
|
+
|
|
208
|
+
class ServiceA {
|
|
209
|
+
constructor(public b: ServiceB) {}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
class ServiceB {
|
|
213
|
+
constructor(public a: ServiceA) {}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const container = createContainer()
|
|
217
|
+
.registerSingleton(ServiceA, r => new ServiceA(r.resolve(ServiceB)))
|
|
218
|
+
.registerSingleton(ServiceB, r => new ServiceB(r.resolve(ServiceA)));
|
|
219
|
+
|
|
220
|
+
container.resolve(ServiceA);
|
|
221
|
+
// ContainerError: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Indirect cycles are also detected:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
ContainerError: Circular dependency detected: ServiceX -> ServiceY -> ServiceZ -> ServiceX
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Disposable Support
|
|
231
|
+
|
|
232
|
+
Both `Container` and `Scope` implement `AsyncDisposable`. When disposed, managed instances are iterated in reverse creation order (LIFO) and their `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` methods are called automatically.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { createContainer } from 'katagami';
|
|
236
|
+
|
|
237
|
+
class Connection {
|
|
238
|
+
async [Symbol.asyncDispose]() {
|
|
239
|
+
console.log('Connection closed');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Manual disposal
|
|
244
|
+
const container = createContainer().registerSingleton(Connection, () => new Connection());
|
|
245
|
+
|
|
246
|
+
container.resolve(Connection);
|
|
247
|
+
await container[Symbol.asyncDispose]();
|
|
248
|
+
// => "Connection closed"
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
With `await using`, scopes are automatically disposed at the end of the block:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
const root = createContainer()
|
|
255
|
+
.registerSingleton(DbPool, () => new DbPool())
|
|
256
|
+
.registerScoped(Connection, () => new Connection());
|
|
257
|
+
|
|
258
|
+
{
|
|
259
|
+
await using scope = root.createScope();
|
|
260
|
+
const conn = scope.resolve(Connection);
|
|
261
|
+
// ... use conn ...
|
|
262
|
+
} // scope is disposed here — Connection is cleaned up, DbPool is not
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Scope disposal only affects scoped instances. Singleton instances are owned by the root container and are disposed when the container itself is disposed.
|
|
266
|
+
|
|
267
|
+
### Interface Type Map
|
|
268
|
+
|
|
269
|
+
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:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { createContainer } from 'katagami';
|
|
273
|
+
|
|
274
|
+
class Logger {
|
|
275
|
+
log(msg: string) {
|
|
276
|
+
console.log(msg);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
interface Services {
|
|
281
|
+
logger: Logger;
|
|
282
|
+
greeting: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const container = createContainer<Services>()
|
|
286
|
+
// 'greeting' can reference 'logger' even though it is registered later
|
|
287
|
+
.registerSingleton('greeting', r => {
|
|
288
|
+
r.resolve('logger').log('Building greeting...');
|
|
289
|
+
return 'Hello!';
|
|
290
|
+
})
|
|
291
|
+
.registerSingleton('logger', () => new Logger());
|
|
292
|
+
|
|
293
|
+
const greeting = container.resolve('greeting');
|
|
294
|
+
// ^? string
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Hybrid Token Strategy
|
|
298
|
+
|
|
299
|
+
You can mix both approaches — use class tokens for order-dependent type safety and PropertyKey tokens for order-independent flexibility:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
const container = createContainer<Services>()
|
|
303
|
+
.registerSingleton(Logger, () => new Logger())
|
|
304
|
+
.registerSingleton('logger', () => new Logger())
|
|
305
|
+
.registerSingleton('greeting', r => {
|
|
306
|
+
r.resolve(Logger).log('Building greeting...');
|
|
307
|
+
return 'Hello!';
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Captive Dependency Prevention
|
|
312
|
+
|
|
313
|
+
A "captive dependency" occurs when a long-lived service (singleton or transient) captures a short-lived service (scoped), keeping it alive beyond its intended scope. Katagami prevents this at compile time — singleton and transient factories only receive a resolver limited to non-scoped tokens:
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
import { createContainer } from 'katagami';
|
|
317
|
+
|
|
318
|
+
class DbPool {}
|
|
319
|
+
class RequestContext {}
|
|
320
|
+
|
|
321
|
+
const container = createContainer()
|
|
322
|
+
.registerScoped(RequestContext, () => new RequestContext())
|
|
323
|
+
// @ts-expect-error — singleton factory cannot resolve scoped token
|
|
324
|
+
.registerSingleton(DbPool, r => new DbPool(r.resolve(RequestContext)));
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Scoped factories, on the other hand, can resolve both scoped and non-scoped tokens:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
const container = createContainer()
|
|
331
|
+
.registerSingleton(DbPool, () => new DbPool())
|
|
332
|
+
.registerScoped(RequestContext, r => {
|
|
333
|
+
r.resolve(DbPool); // OK — scoped factory can resolve singleton tokens
|
|
334
|
+
return new RequestContext();
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Optional Resolution (tryResolve)
|
|
339
|
+
|
|
340
|
+
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`:
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { createContainer } from 'katagami';
|
|
344
|
+
|
|
345
|
+
class Logger {
|
|
346
|
+
log(msg: string) {
|
|
347
|
+
console.log(msg);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
class Analytics {
|
|
352
|
+
track(event: string) {
|
|
353
|
+
console.log(`Track: ${event}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const container = createContainer().registerSingleton(Logger, () => new Logger());
|
|
358
|
+
|
|
359
|
+
// resolve throws for unregistered tokens
|
|
360
|
+
container.resolve(Analytics); // ContainerError: Token "Analytics" is not registered.
|
|
361
|
+
|
|
362
|
+
// tryResolve returns undefined for unregistered tokens
|
|
363
|
+
const analytics = container.tryResolve(Analytics);
|
|
364
|
+
// ^? Analytics | undefined
|
|
365
|
+
if (analytics) {
|
|
366
|
+
analytics.track('event');
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`tryResolve` is especially useful for optional dependencies in factories. Unlike `resolve`, it accepts unregistered tokens without compile-time errors:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
const container = createContainer()
|
|
374
|
+
.registerSingleton(Logger, () => new Logger())
|
|
375
|
+
.registerSingleton('UserService', r => {
|
|
376
|
+
const logger = r.tryResolve(Logger); // Optional dependency
|
|
377
|
+
const analytics = r.tryResolve(Analytics); // No compile error even though Analytics is not registered
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
greet(name: string) {
|
|
381
|
+
logger?.log(`Hello, ${name}`);
|
|
382
|
+
analytics?.track('user_greeted');
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`tryResolve` still throws `ContainerError` for circular dependencies and operations on disposed containers/scopes — only unregistered tokens return `undefined`.
|
|
389
|
+
|
|
390
|
+
## API
|
|
391
|
+
|
|
392
|
+
### `createContainer<T, ScopedT>()`
|
|
393
|
+
|
|
394
|
+
Creates a new DI container. Pass an interface as `T` to define the type map for PropertyKey tokens. Pass `ScopedT` to define a separate type map for scoped PropertyKey tokens (order-independent, just like `T`).
|
|
395
|
+
|
|
396
|
+
### `container.registerSingleton(token, factory)`
|
|
397
|
+
|
|
398
|
+
Registers a factory as a singleton. The instance is created on the first `resolve` and cached thereafter. Returns the container for method chaining.
|
|
399
|
+
|
|
400
|
+
### `container.registerTransient(token, factory)`
|
|
401
|
+
|
|
402
|
+
Registers a factory as transient. A new instance is created on every `resolve`. Returns the container for method chaining.
|
|
403
|
+
|
|
404
|
+
### `container.registerScoped(token, factory)`
|
|
405
|
+
|
|
406
|
+
Registers a factory as scoped. Within a scope, the instance is created on the first `resolve` and cached for that scope. Each scope maintains its own cache. Scoped tokens cannot be resolved from the root container. Returns the container for method chaining.
|
|
407
|
+
|
|
408
|
+
### `container.resolve(token)`
|
|
409
|
+
|
|
410
|
+
Resolves and returns the instance for the given token. Throws `ContainerError` if the token is not registered or if a circular dependency is detected.
|
|
411
|
+
|
|
412
|
+
### `container.tryResolve(token)` / `scope.tryResolve(token)`
|
|
413
|
+
|
|
414
|
+
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.
|
|
415
|
+
|
|
416
|
+
### `container.createScope()`
|
|
417
|
+
|
|
418
|
+
Creates a new `Scope` (child container). The scope inherits all registrations from the parent. Singleton instances are shared with the parent, while scoped instances are local to the scope.
|
|
419
|
+
|
|
420
|
+
### `Scope`
|
|
421
|
+
|
|
422
|
+
A scoped child container created by `createScope()`. Provides `resolve(token)`, `tryResolve(token)`, `createScope()` (for nested scopes), and `[Symbol.asyncDispose]()`.
|
|
423
|
+
|
|
424
|
+
### `container[Symbol.asyncDispose]()` / `scope[Symbol.asyncDispose]()`
|
|
425
|
+
|
|
426
|
+
Disposes all managed 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`.
|
|
427
|
+
|
|
428
|
+
### `ContainerError`
|
|
429
|
+
|
|
430
|
+
Error class thrown for container failures such as resolving an unregistered token, circular dependencies, or operations on a disposed container/scope.
|
|
431
|
+
|
|
432
|
+
### `Resolver`
|
|
433
|
+
|
|
434
|
+
Type export representing the resolver passed to factory callbacks. Useful when you need to type a function that accepts a resolver parameter.
|
|
435
|
+
|
|
436
|
+
## License
|
|
437
|
+
|
|
438
|
+
MIT
|