Functions
toFunction(fn, injections) binds a plain function to a key. DiCaf resolves the
declared injections, calls fn with those values, and registers whatever fn returns
as the binding's value.
This is useful when you want to produce a value — a closure, a plain object, or an object with methods — without declaring a class.
import { DiCaf } from '@caffeine-projects/dicaf'
Returning a function
A function binding can return another function. The injected dependencies are captured in the closure; callers receive a ready-to-call function.
class EmailService {
send(to: string, body: string): void { /* ... */ }
}
const SEND_WELCOME = Symbol('app:send-welcome')
function createWelcomeSender(email: EmailService) {
return (username: string) => {
email.send(username, `Welcome, ${username}!`)
}
}
const di = new DiCaf()
di.bind(EmailService).toSelf()
di.bind(SEND_WELCOME).toFunction(createWelcomeSender, [EmailService])
await di.init()
const sendWelcome = di.get<(username: string) => void>(SEND_WELCOME)
sendWelcome('alice') // sends welcome email
Returning a plain object
Use toFunction to assemble a plain record or configuration object from container
values.
class AppConfig {
readonly baseUrl = process.env.BASE_URL ?? 'http://localhost:3000'
readonly timeout = 5000
}
const HTTP_OPTIONS = Symbol('app:http-options')
function buildHttpOptions(config: AppConfig) {
return {
baseUrl: config.baseUrl,
timeout: config.timeout,
headers: { 'Content-Type': 'application/json' },
}
}
const di = new DiCaf()
di.bind(AppConfig).toSelf()
di.bind(HTTP_OPTIONS).toFunction(buildHttpOptions, [AppConfig])
await di.init()
const options = di.get<ReturnType<typeof buildHttpOptions>>(HTTP_OPTIONS)
Returning an object with methods
The module pattern — a plain object with functions as properties — works well with
toFunction. Dependencies are closed over once at resolution time.
class UserRepository {
findById(id: number) { /* ... */ }
save(user: unknown) { /* ... */ }
}
class Logger {
info(msg: string) { /* ... */ }
}
const USER_SERVICE = Symbol('app:user-service')
function createUserService(repo: UserRepository, logger: Logger) {
return {
getUser(id: number) {
logger.info(`fetching user ${id}`)
return repo.findById(id)
},
createUser(data: unknown) {
const user = repo.save(data)
logger.info(`created user`)
return user
},
}
}
const di = new DiCaf()
di.bind(UserRepository).toSelf()
di.bind(Logger).toSelf()
di.bind(USER_SERVICE).toFunction(createUserService, [UserRepository, Logger])
await di.init()
const userService = di.get<ReturnType<typeof createUserService>>(USER_SERVICE)
userService.getUser(1)
Injection functions
The injections array accepts any injection descriptor — the same as constructor
injection.
import { optional, allOf, useValue } from '@caffeine-projects/dicaf'
di.bind(PIPELINE).toFunction(
(validators, cache, maxItems) => createPipeline(validators, cache, maxItems),
[
allOf(Validator), // array of all Validator bindings
optional(CacheService), // undefined if not registered
useValue(100), // literal
],
)
The injection count must equal the function's parameter count. DiCaf throws at bind time if they differ.
Symbol keys
Functions rarely have a class constructor to use as a key. Use a Symbol to give
the binding a stable, collision-free identity. Export it alongside the return type
so callers stay type-safe.
export const SEND_WELCOME = Symbol('app:send-welcome')
export type WelcomeSender = (username: string) => void
// binding
di.bind(SEND_WELCOME).toFunction(createWelcomeSender, [EmailService])
// consumer
const send = di.get<WelcomeSender>(SEND_WELCOME)