Skip to main content

Factory

toFactory(fn) binds a key to a raw factory function. Unlike toFunction(), the factory receives a ResolutionContext — giving it direct access to the container — instead of having dependencies injected positionally.

import type { ResolutionContext } from '@caffeine-projects/dicaf'

Use toFactory() when the set of dependencies is dynamic, conditional, or determined at resolution time rather than at bind time.


Basic example

import { DiCaf } from '@caffeine-projects/dicaf'

class DatabaseService { /* ... */ }
class CacheService { /* ... */ }

const REPOSITORY = Symbol('app:repository')

const di = new DiCaf()

di.bind(DatabaseService).toSelf()
di.bind(CacheService).toSelf()

di.bind(REPOSITORY).toFactory(({ container }) => {
const db = container.get(DatabaseService)
const cache = container.get(CacheService)
return { db, cache }
})

await di.init()

const repo = di.get(REPOSITORY)

The factory is called once per scope cycle, the same as toClass() or toFunction().


Conditional dependencies

ctx.container.getOptional() returns undefined when a key is not registered. Use it to make a dependency optional inside the factory body.

class MetricsService { record(event: string): void { /* ... */ } }

const HANDLER = Symbol('app:handler')

di.bind(HANDLER).toFactory(({ container }) => {
const metrics = container.getOptional(MetricsService)

return {
handle(event: string) {
metrics?.record(event)
// ...
},
}
})

Collecting multiple bindings

ctx.container.getMany() returns all bindings registered under a key.

abstract class Plugin { abstract run(): void }

const RUNNER = Symbol('app:runner')

di.bind(RUNNER).toFactory(({ container }) => {
const plugins = container.getMany(Plugin)

return {
runAll() {
for (const plugin of plugins) plugin.run()
},
}
})

Decorator-based: @UseFactory

@UseFactory(factory) is the decorator equivalent of toFactory(). Apply it to a class alongside @Injectable() to replace the default constructor-based instantiation with a custom factory function.

The factory receives the same ResolutionContext and can access ctx.container for manual resolution.

Customising construction

Use @UseFactory when you need to configure a class in a way that constructor injection alone cannot express — fixed options, external configuration, or wrapping the class before returning.

import { Injectable } from '@caffeine-projects/dicaf/decorators'
import { UseFactory } from '@caffeine-projects/dicaf/decorators'

class AppConfig {
readonly timeout = Number(process.env.TIMEOUT ?? 5000)
}

@UseFactory(({ container }) => {
const config = container.get(AppConfig)
return new HttpClient({ timeout: config.timeout, retries: 3 })
})
@Injectable()
class HttpClient {
constructor(private readonly opts: { timeout: number; retries: number }) {}
}

Returning a different type

The factory is not required to return an instance of the decorated class. This lets you bind the class key to a plain object, a Proxy, or any other value while keeping the class as the resolution key.

interface Repo<T> {
findById(id: number): T | undefined
save(entity: T): T
}

function inMemoryRepo<T>(): Repo<T> {
const store = new Map<number, T>()
let seq = 1
return {
findById: id => store.get(id),
save: entity => { store.set(seq++, entity); return entity },
}
}

@UseFactory(() => inMemoryRepo<User>())
@Injectable()
class UserRepository {}

// di.get(UserRepository) returns a Repo<User>, not a UserRepository instance

Accessing ctx.key and ctx.binding

ResolutionContext exposes the binding's key and metadata. This is useful for generic factory functions reused across multiple classes.

import type { ResolutionContext } from '@caffeine-projects/dicaf'

function tracingFactory<T>(inner: (ctx: ResolutionContext) => T) {
return (ctx: ResolutionContext): T => {
const instance = inner(ctx)
console.log(`Resolved ${String(ctx.key)}`)
return instance
}
}

@UseFactory(tracingFactory(ctx => new OrderService()))
@Injectable()
class OrderService {}

When to use toFactory() vs toFunction()

NeedUse
Fixed set of deps, known at bind timetoFunction(fn, [deps])
Dynamic or conditional depstoFactory(ctx => ...)
Deps determined by runtime statetoFactory(ctx => ...)
Collecting all bindings for a keytoFactory(ctx => ...) with ctx.container.getMany()

Async factories

For factories that perform I/O during construction, see Async Bindings.