Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,18 @@ const baz /*: number*/ = injector.injectFunction(Foo);

#### `injector.resolve(token: Token): CorrespondingType<TContext, Token>`

The `resolve` method lets you resolve tokens by hand.
The `resolve` method allows you to manually resolve a single token from the injector. This is useful when you need to retrieve a dependency without injecting it into a class or function. Use it when you need to retrieve a single dependency programmatically, in scenarios where you want to conditionally resolve dependencies, when integrating with code that doesn't use dependency injection, or for debugging and testing purposes.

```ts
const logger = injector.resolve('logger');
const httpClient = injector.resolve('httpClient');

// Use the resolved dependencies directly
logger.info('Application started');
const response = await httpClient.get('/api/data');
```

The resolve method can be compared with injection methods:

```ts
const foo = injector.resolve('foo');
Expand All @@ -526,6 +537,70 @@ function retrieveFoo(foo: number) {
}
retrieveFoo.inject = ['foo'] as const;
const foo2 = injector.injectFunction(retrieveFoo);

#### `injector.resolveAll(): Promise<void>`

The `resolveAll` method pre-caches all singleton-scoped dependencies in the current injector and its child injectors. This is useful for eager initialization and ensuring all dependencies are created upfront. Use it when you want to validate that all dependencies can be created successfully at startup, to pre-warm your application by creating all singletons before handling requests, for detecting circular dependencies or configuration issues early, or when you need predictable initialization order.

```ts
const rootInjector = createInjector();
const appInjector = rootInjector
.provideClass('database', Database, Scope.Singleton)
.provideFactory('logger', createLogger, Scope.Singleton)
.provideClass('cache', RedisCache, Scope.Singleton);

// Pre-initialize all singletons
// Note: this only traverses children, just like dispose, so we keep a reference to the root injector
await rootInjector.resolveAll();

// Now all singletons are cached and ready
const db = appInjector.resolve('database'); // Returns cached instance
const logger = appInjector.resolve('logger'); // Returns cached instance
const cache = appInjector.resolve('cache'); // Returns cached instance
```

The behavior differs between singleton and transient scopes:

```ts
const rootInjector = createInjector();
const mixedInjector = rootInjector
.provideFactory('singleton', () => {
console.log('Creating singleton');
return { type: 'singleton' };
}, Scope.Singleton)
.provideFactory('transient', () => {
console.log('Creating transient');
return { type: 'transient' };
}, Scope.Transient);

await rootInjector.resolveAll();
// Output: "Creating singleton"
// Note: transient is NOT created

mixedInjector.resolve('singleton'); // No output - uses cached instance
mixedInjector.resolve('transient'); // Output: "Creating transient"
```

The method recursively resolves singletons in child injectors:

```ts
const parentInjector = createInjector()
.provideClass('parentService', ParentService, Scope.Singleton);

const childInjector = parentInjector
.provideClass('childService', ChildService, Scope.Singleton);

const grandchildInjector = childInjector
.provideClass('grandchildService', GrandchildService, Scope.Singleton);

// Resolves all singletons in the entire injector chain
// Note: this follows the same scoping as dispose
await parentInjector.resolveAll();

// All of these return cached instances
const parent = grandchildInjector.resolve('parentService');
const child = grandchildInjector.resolve('childService');
const grandchild = grandchildInjector.resolve('grandchildService');
```

#### `injector.provideValue(token: Token, value: R): Injector<ChildContext<TContext, Token, R>>`
Expand Down
20 changes: 19 additions & 1 deletion src/InjectorImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const DEFAULT_SCOPE = Scope.Singleton;
*/

abstract class AbstractInjector<TContext> implements Injector<TContext> {
private childInjectors: Set<Injector<any>> = new Set();
protected childInjectors: Set<Injector<any>> = new Set();

public injectClass<R, Tokens extends InjectionToken<TContext>[]>(
Class: InjectableClass<TContext, R, Tokens>,
Expand Down Expand Up @@ -142,6 +142,14 @@ abstract class AbstractInjector<TContext> implements Injector<TContext> {
return this.resolveInternal(token, target);
}

public async resolveAll(): Promise<void> {
const promises: Promise<void>[] = [];
for (const child of this.childInjectors) {
promises.push(child.resolveAll());
}
await Promise.all(promises);
}

protected throwIfDisposed(injectableOrToken: InjectionTarget) {
if (this.isDisposed) {
throw new InjectorDisposedError(injectableOrToken);
Expand Down Expand Up @@ -256,6 +264,16 @@ abstract class ChildWithProvidedInjector<
}
}

public override async resolveAll(): Promise<void> {
this.throwIfDisposed(this.token);
if (this.scope === Scope.Singleton && !this.cached) {
const value = this.result(undefined);
this.addToCacheIfNeeded(value);
this.registerProvidedValue(value);
}
await super.resolveAll();
}

private addToCacheIfNeeded(value: TProvided) {
if (this.scope === Scope.Singleton) {
this.cached = { value };
Expand Down
23 changes: 22 additions & 1 deletion src/api/Injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,28 @@ export interface Injector<TContext = {}> {
createChildInjector(): Injector<TContext>;

/**
* Explicitly dispose the `injector`.
* Resolve all tokens in the current dispose scope, pre-caching all singleton instances.
* This will traverse through all child injectors and resolve any ClassProvider and FactoryProvider
* instances that have Singleton scope, ensuring they are cached for future use.
* @example
* ```ts
* const rootInjector = createInjector();
* const injector = rootInjector
* .provideClass('database', Database, Scope.Singleton)
* .provideFactory('logger', createLogger, Scope.Singleton);
*
* // Pre-cache all singleton instances
* await rootInjector.resolveAll();
*
* // These will now use cached instances
* const db = injector.resolve('database');
* const logger = injector.resolve('logger');
* ```
*/
resolveAll(): Promise<void>;

/**
* Explicitly dispose the `injector` and all it's child injectors.
* @see {@link https://github.com/nicojs/typed-inject?tab=readme-ov-file#disposing-provided-stuff}
*/
dispose(): Promise<void>;
Expand Down
Loading