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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hiroiku
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.de.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
|
+
Leichtgewichtiger TypeScript-DI-Container mit vollständiger Typinferenz.
|
|
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
|
+
> Der Name stammt von 型紙 _(katagami)_ — präzise Schablonenpapiere, die in der traditionellen japanischen Färbetechnik verwendet werden, um exakte Muster auf Stoff zu übertragen. Mehrere Schablonen werden übereinandergelegt, um kunstvolle Designs zu komponieren, genau wie sich Typen mit jedem Methodenkettenaufruf ansammeln. Eine Schablone benötigt nur Papier und einen Pinsel, keine aufwendige Maschinerie — ebenso benötigt Katagami keine Decorators oder Metadaten-Mechanismen und funktioniert mit jedem Build-Tool direkt einsatzbereit. Und wie Schablonen, die mit verschiedenen Stoffen und Techniken funktionieren, passt sich Katagami an TypeScript und JavaScript, Klassen-Token und PropertyKey-Token an — ein hybrider Ansatz für strikte, komponierbare DI.
|
|
12
|
+
|
|
13
|
+
## Funktionen
|
|
14
|
+
|
|
15
|
+
| Funktion | Beschreibung |
|
|
16
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| Vollständige Typinferenz | Typen akkumulieren sich durch Methodenverkettung; nicht registrierte Token erzeugen Kompilierzeitfehler |
|
|
18
|
+
| Drei Lebenszyklen | Singleton, Transient und Scoped mit Kind-Containern |
|
|
19
|
+
| Asynchrone Factories | Promise-zurückgebende Factories werden automatisch vom Typsystem verfolgt |
|
|
20
|
+
| Erkennung zirkulärer Abhängigkeiten | Klare Fehlermeldungen mit dem vollständigen Zykluspfad |
|
|
21
|
+
| Disposable-Unterstützung | TC39 Explicit Resource Management (`Symbol.dispose` / `Symbol.asyncDispose` / `await using`) |
|
|
22
|
+
| Verhinderung gefangener Abhängigkeiten | Singleton-/Transient-Factories können nicht auf Scoped-Token zugreifen; wird zur Kompilierzeit erkannt |
|
|
23
|
+
| Optionale Auflösung | `tryResolve` gibt `undefined` für nicht registrierte Token zurück statt zu werfen |
|
|
24
|
+
| Hybride Token-Strategie | Klassen-Token für strikte Typsicherheit, PropertyKey-Token für Flexibilität |
|
|
25
|
+
| Interface-Typ-Map | Übergeben Sie ein Interface an `createContainer<T>()` für reihenfolgeunabhängige Registrierung |
|
|
26
|
+
| Null Abhängigkeiten | Keine Decorators, kein reflect-metadata, keine Polyfills |
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install katagami
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Schnellstart
|
|
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 (vollständig inferiert)
|
|
58
|
+
userService.greet('world');
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Warum Katagami
|
|
62
|
+
|
|
63
|
+
Die meisten TypeScript-DI-Container basieren auf Decorators, reflect-metadata oder stringbasierten Token — jeder mit Kompromissen bei Toolkompatibilität, Typsicherheit oder Bundle-Größe. Katagami verfolgt einen anderen Ansatz.
|
|
64
|
+
|
|
65
|
+
### Keine Decorators, kein reflect-metadata
|
|
66
|
+
|
|
67
|
+
Decorator-basierte DI erfordert die Compiler-Optionen `experimentalDecorators` und `emitDecoratorMetadata`. Moderne Build-Tools wie esbuild und Vite (Standardkonfiguration) unterstützen `emitDecoratorMetadata` nicht, und der TC39-Standarddecorators-Vorschlag enthält kein Äquivalent für die automatische Typ-Metadaten-Emission. Katagami ist von nichts davon abhängig — es funktioniert mit jedem Build-Tool direkt einsatzbereit.
|
|
68
|
+
|
|
69
|
+
### Vollständige Typinferenz durch Klassen-Token
|
|
70
|
+
|
|
71
|
+
String-Token-DI zwingt Sie, manuelle Token-zu-Typ-Zuordnungen zu pflegen. Parameternamen-Matching bricht bei Minifizierung. Katagami verwendet Klassen direkt als Token, sodass `resolve` automatisch den korrekten Rückgabetyp inferiert — synchron oder `Promise` — ohne zusätzliche Annotationen.
|
|
72
|
+
|
|
73
|
+
### Methodenketten-Typakkumulation
|
|
74
|
+
|
|
75
|
+
Typen akkumulieren sich mit jedem `register`-Aufruf. Innerhalb einer Factory akzeptiert der Resolver nur Token, die an diesem Punkt in der Kette bereits registriert wurden. Das Auflösen eines nicht registrierten Tokens ist ein Kompilierzeitfehler, keine Laufzeitüberraschung.
|
|
76
|
+
|
|
77
|
+
### Hybride Token-Strategie
|
|
78
|
+
|
|
79
|
+
Klassen-Token bieten strikte, reihenfolgeabhängige Typsicherheit durch Methodenverkettung. Aber manchmal möchten Sie eine Reihe von Services vorab definieren und in beliebiger Reihenfolge registrieren. Übergeben Sie ein Interface an `createContainer<T>()` und verwenden Sie PropertyKey-Token — die Typ-Map wird zum Erstellungszeitpunkt festgelegt, sodass die Registrierungsreihenfolge keine Rolle spielt.
|
|
80
|
+
|
|
81
|
+
### Null Abhängigkeiten
|
|
82
|
+
|
|
83
|
+
Keine Laufzeitabhängigkeiten, keine Polyfills. Kein Bedarf, reflect-metadata (~50 KB unminifiziert) zu Ihrem Bundle hinzuzufügen.
|
|
84
|
+
|
|
85
|
+
## Leitfaden
|
|
86
|
+
|
|
87
|
+
### Singleton und Transient
|
|
88
|
+
|
|
89
|
+
Singleton erstellt die Instanz beim ersten `resolve` und speichert sie im Cache. Transient erstellt jedes Mal eine neue Instanz.
|
|
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 — jedes Mal dieselbe Instanz
|
|
107
|
+
container.resolve(Database) === container.resolve(Database); // true
|
|
108
|
+
|
|
109
|
+
// Transient — jedes Mal eine neue Instanz
|
|
110
|
+
container.resolve(RequestHandler) === container.resolve(RequestHandler); // false
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Scoped-Lebenszyklus und Kind-Container
|
|
114
|
+
|
|
115
|
+
Scoped-Registrierungen verhalten sich innerhalb eines Scopes wie Singletons, erzeugen aber in jedem neuen Scope eine neue Instanz. Verwenden Sie `createScope()`, um einen Kind-Container zu erstellen. Scoped-Token können nicht vom Root-Container aufgelöst werden.
|
|
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
|
+
// Einen Scope für jede Anfrage erstellen
|
|
133
|
+
const scope1 = root.createScope();
|
|
134
|
+
const scope2 = root.createScope();
|
|
135
|
+
|
|
136
|
+
// Scoped — gleich innerhalb eines Scopes, unterschiedlich zwischen Scopes
|
|
137
|
+
scope1.resolve(RequestContext) === scope1.resolve(RequestContext); // true
|
|
138
|
+
scope1.resolve(RequestContext) === scope2.resolve(RequestContext); // false
|
|
139
|
+
|
|
140
|
+
// Singleton — über alle Scopes geteilt
|
|
141
|
+
scope1.resolve(DbPool) === scope2.resolve(DbPool); // true
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Scopes können auch verschachtelt werden. Jeder verschachtelte Scope hat seinen eigenen Scoped-Instanz-Cache, während Singletons mit dem Eltern-Scope geteilt werden:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const parentScope = root.createScope();
|
|
148
|
+
const childScope = parentScope.createScope();
|
|
149
|
+
|
|
150
|
+
// Jeder verschachtelte Scope bekommt eigene Scoped-Instanzen
|
|
151
|
+
parentScope.resolve(RequestContext) === childScope.resolve(RequestContext); // false
|
|
152
|
+
|
|
153
|
+
// Singletons werden weiterhin geteilt
|
|
154
|
+
parentScope.resolve(DbPool) === childScope.resolve(DbPool); // true
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Asynchrone Factories
|
|
158
|
+
|
|
159
|
+
Factories, die ein `Promise` zurückgeben, werden automatisch vom Typsystem verfolgt. Wenn Sie ein asynchrones Token auflösen, ist der Rückgabetyp `Promise<V>` statt `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)); // Asynchrone Initialisierung simulieren
|
|
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> (nach await → Database)
|
|
186
|
+
db.connected; // true
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Asynchrone Factories können sowohl von synchronen als auch von asynchronen Registrierungen abhängen:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const container = createContainer()
|
|
193
|
+
.registerSingleton(Logger, () => new Logger())
|
|
194
|
+
.registerSingleton(Database, async r => {
|
|
195
|
+
const logger = r.resolve(Logger); // synchron → Logger
|
|
196
|
+
logger.log('Verbindung wird hergestellt...');
|
|
197
|
+
return new Database(true);
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Erkennung zirkulärer Abhängigkeiten
|
|
202
|
+
|
|
203
|
+
Katagami verfolgt, welche Token gerade aufgelöst werden. Wenn eine zirkuläre Abhängigkeit gefunden wird, wird ein `ContainerError` mit einer klaren Nachricht geworfen, die den vollständigen Zykluspfad zeigt:
|
|
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
|
+
Indirekte Zyklen werden ebenfalls erkannt:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
ContainerError: Circular dependency detected: ServiceX -> ServiceY -> ServiceZ -> ServiceX
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Disposable-Unterstützung
|
|
231
|
+
|
|
232
|
+
Sowohl `Container` als auch `Scope` implementieren `AsyncDisposable`. Bei der Freigabe werden verwaltete Instanzen in umgekehrter Erstellungsreihenfolge (LIFO) durchlaufen und ihre `[Symbol.asyncDispose]()` oder `[Symbol.dispose]()` Methoden werden automatisch aufgerufen.
|
|
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
|
+
// Manuelle Freigabe
|
|
244
|
+
const container = createContainer().registerSingleton(Connection, () => new Connection());
|
|
245
|
+
|
|
246
|
+
container.resolve(Connection);
|
|
247
|
+
await container[Symbol.asyncDispose]();
|
|
248
|
+
// => "Connection closed"
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Mit `await using` werden Scopes am Ende des Blocks automatisch freigegeben:
|
|
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
|
+
// ... conn verwenden ...
|
|
262
|
+
} // Scope wird hier freigegeben — Connection wird bereinigt, DbPool nicht
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Die Scope-Freigabe betrifft nur Scoped-Instanzen. Singleton-Instanzen gehören dem Root-Container und werden freigegeben, wenn der Container selbst freigegeben wird.
|
|
266
|
+
|
|
267
|
+
### Interface-Typ-Map
|
|
268
|
+
|
|
269
|
+
Wenn Sie ein Interface an `createContainer<T>()` übergeben, werden PropertyKey-Token vom Interface typisiert, anstatt durch Verkettung akkumuliert zu werden. Das bedeutet, Sie können Token in beliebiger Reihenfolge registrieren und auflösen:
|
|
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' kann 'logger' referenzieren, obwohl es später registriert wird
|
|
287
|
+
.registerSingleton('greeting', r => {
|
|
288
|
+
r.resolve('logger').log('Greeting wird erstellt...');
|
|
289
|
+
return 'Hello!';
|
|
290
|
+
})
|
|
291
|
+
.registerSingleton('logger', () => new Logger());
|
|
292
|
+
|
|
293
|
+
const greeting = container.resolve('greeting');
|
|
294
|
+
// ^? string
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Hybride Token-Strategie
|
|
298
|
+
|
|
299
|
+
Sie können beide Ansätze mischen — verwenden Sie Klassen-Token für reihenfolgeabhängige Typsicherheit und PropertyKey-Token für reihenfolgeunabhängige Flexibilität:
|
|
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('Greeting wird erstellt...');
|
|
307
|
+
return 'Hello!';
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Verhinderung gefangener Abhängigkeiten
|
|
312
|
+
|
|
313
|
+
Eine „gefangene Abhängigkeit" tritt auf, wenn ein langlebiger Service (Singleton oder Transient) einen kurzlebigen Service (Scoped) einfängt und über seinen vorgesehenen Scope hinaus am Leben hält. Katagami verhindert dies zur Kompilierzeit — Singleton- und Transient-Factories erhalten nur einen Resolver, der auf nicht-Scoped-Token beschränkt ist:
|
|
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 kann keine Scoped-Token auflösen
|
|
324
|
+
.registerSingleton(DbPool, r => new DbPool(r.resolve(RequestContext)));
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Scoped-Factories hingegen können sowohl Scoped- als auch nicht-Scoped-Token auflösen:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
const container = createContainer()
|
|
331
|
+
.registerSingleton(DbPool, () => new DbPool())
|
|
332
|
+
.registerScoped(RequestContext, r => {
|
|
333
|
+
r.resolve(DbPool); // OK — Scoped-Factory kann Singleton-Token auflösen
|
|
334
|
+
return new RequestContext();
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Optionale Auflösung (tryResolve)
|
|
339
|
+
|
|
340
|
+
Wenn Sie optionale Abhängigkeiten handhaben möchten oder prüfen wollen, ob ein Token registriert ist, ohne einen Fehler zu werfen, verwenden Sie `tryResolve`. Im Gegensatz zu `resolve` gibt es `undefined` für nicht registrierte Token zurück, anstatt einen `ContainerError` zu werfen:
|
|
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 wirft für nicht registrierte Token
|
|
360
|
+
container.resolve(Analytics); // ContainerError: Token "Analytics" is not registered.
|
|
361
|
+
|
|
362
|
+
// tryResolve gibt undefined für nicht registrierte Token zurück
|
|
363
|
+
const analytics = container.tryResolve(Analytics);
|
|
364
|
+
// ^? Analytics | undefined
|
|
365
|
+
if (analytics) {
|
|
366
|
+
analytics.track('event');
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`tryResolve` ist besonders nützlich für optionale Abhängigkeiten in Factories. Im Gegensatz zu `resolve` akzeptiert es nicht registrierte Token ohne Kompilierungsfehler:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
const container = createContainer()
|
|
374
|
+
.registerSingleton(Logger, () => new Logger())
|
|
375
|
+
.registerSingleton('UserService', r => {
|
|
376
|
+
const logger = r.tryResolve(Logger); // Optionale Abhängigkeit
|
|
377
|
+
const analytics = r.tryResolve(Analytics); // Kein Kompilierungsfehler, obwohl Analytics nicht registriert ist
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
greet(name: string) {
|
|
381
|
+
logger?.log(`Hello, ${name}`);
|
|
382
|
+
analytics?.track('user_greeted');
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`tryResolve` wirft immer noch einen `ContainerError` bei zirkulären Abhängigkeiten und Operationen auf freigegebenen Containern/Scopes — nur nicht registrierte Token geben `undefined` zurück.
|
|
389
|
+
|
|
390
|
+
## API
|
|
391
|
+
|
|
392
|
+
### `createContainer<T, ScopedT>()`
|
|
393
|
+
|
|
394
|
+
Erstellt einen neuen DI-Container. Übergeben Sie ein Interface als `T`, um die Typ-Map für PropertyKey-Token zu definieren. Übergeben Sie `ScopedT`, um eine separate Typ-Map für Scoped-PropertyKey-Token zu definieren (reihenfolgeunabhängig, genau wie `T`).
|
|
395
|
+
|
|
396
|
+
### `container.registerSingleton(token, factory)`
|
|
397
|
+
|
|
398
|
+
Registriert eine Factory als Singleton. Die Instanz wird beim ersten `resolve` erstellt und danach gecacht. Gibt den Container für Methodenverkettung zurück.
|
|
399
|
+
|
|
400
|
+
### `container.registerTransient(token, factory)`
|
|
401
|
+
|
|
402
|
+
Registriert eine Factory als Transient. Bei jedem `resolve` wird eine neue Instanz erstellt. Gibt den Container für Methodenverkettung zurück.
|
|
403
|
+
|
|
404
|
+
### `container.registerScoped(token, factory)`
|
|
405
|
+
|
|
406
|
+
Registriert eine Factory als Scoped. Innerhalb eines Scopes wird die Instanz beim ersten `resolve` erstellt und für diesen Scope gecacht. Jeder Scope pflegt seinen eigenen Cache. Scoped-Token können nicht vom Root-Container aufgelöst werden. Gibt den Container für Methodenverkettung zurück.
|
|
407
|
+
|
|
408
|
+
### `container.resolve(token)`
|
|
409
|
+
|
|
410
|
+
Löst die Instanz für das gegebene Token auf und gibt sie zurück. Wirft `ContainerError`, wenn das Token nicht registriert ist oder eine zirkuläre Abhängigkeit erkannt wird.
|
|
411
|
+
|
|
412
|
+
### `container.tryResolve(token)` / `scope.tryResolve(token)`
|
|
413
|
+
|
|
414
|
+
Versucht, die Instanz für das gegebene Token aufzulösen. Gibt `undefined` zurück, wenn das Token nicht registriert ist, anstatt zu werfen. Wirft immer noch `ContainerError` bei zirkulären Abhängigkeiten oder Operationen auf freigegebenen Containern/Scopes.
|
|
415
|
+
|
|
416
|
+
### `container.createScope()`
|
|
417
|
+
|
|
418
|
+
Erstellt einen neuen `Scope` (Kind-Container). Der Scope erbt alle Registrierungen vom Eltern-Container. Singleton-Instanzen werden mit dem Eltern-Container geteilt, während Scoped-Instanzen lokal zum Scope sind.
|
|
419
|
+
|
|
420
|
+
### `Scope`
|
|
421
|
+
|
|
422
|
+
Ein Scoped-Kind-Container, erstellt durch `createScope()`. Bietet `resolve(token)`, `tryResolve(token)`, `createScope()` (für verschachtelte Scopes) und `[Symbol.asyncDispose]()`.
|
|
423
|
+
|
|
424
|
+
### `container[Symbol.asyncDispose]()` / `scope[Symbol.asyncDispose]()`
|
|
425
|
+
|
|
426
|
+
Gibt alle verwalteten Instanzen in umgekehrter Erstellungsreihenfolge (LIFO) frei. Ruft `[Symbol.asyncDispose]()` oder `[Symbol.dispose]()` für jede Instanz auf, die diese implementiert. Idempotent — nachfolgende Aufrufe sind No-Ops. Nach der Freigabe werfen `resolve()` und `createScope()` einen `ContainerError`.
|
|
427
|
+
|
|
428
|
+
### `ContainerError`
|
|
429
|
+
|
|
430
|
+
Fehlerklasse, die bei Container-Fehlern geworfen wird, wie z. B. das Auflösen eines nicht registrierten Tokens, zirkuläre Abhängigkeiten oder Operationen auf einem freigegebenen Container/Scope.
|
|
431
|
+
|
|
432
|
+
### `Resolver`
|
|
433
|
+
|
|
434
|
+
Typexport, der den an Factory-Callbacks übergebenen Resolver repräsentiert. Nützlich, wenn Sie eine Funktion typisieren müssen, die einen Resolver-Parameter akzeptiert.
|
|
435
|
+
|
|
436
|
+
## Lizenz
|
|
437
|
+
|
|
438
|
+
MIT
|