Reference
yieldless/di
Reader-lite dependency binding for plain functions.
yieldless/di is intentionally small. It binds stable dependencies at the application edge and returns the executable version of the function.
Exports
type Injectable<Deps, Args extends unknown[], Return> = (deps: Deps, ...args: Args) => Returninject(core, deps): (...args) => Return
Example
import { inject } from "yieldless/di";
const createHandler = (
deps: {
logger: { info(message: string): void };
audit: { write(message: string): Promise<void> };
},
repoId: string,
) => {
deps.logger.info(`Loading ${repoId}`);
return deps.audit.write(`repo:${repoId}`);
};
const handler = inject(createHandler, {
logger: console,
audit,
});Why this stays readable
- All required dependencies are still visible in the function signature
- There is no hidden container lookup
- TypeScript enforces that the injected object satisfies
Depsbefore the returned function can be called
Use it for
- Route handlers configured with repositories, loggers, and feature flags
- CLI commands configured with a filesystem or process adapter
- Background jobs configured with queues or telemetry sinks
Good
Put stable dependencies in the first argument and domain inputs after that.
type Deps = {
readonly logger: { info(message: string): void };
readonly users: { find(id: string): Promise<User | null> };
};
const loadUser = async (deps: Deps, id: string) => {
deps.logger.info(`loading ${id}`);
return await deps.users.find(id);
};
export const handler = inject(loadUser, {
logger,
users,
});Avoid
Do not hide dependencies behind a global lookup.
const loadUser = async (id: string) => {
const users = container.get("users");
return await users.find(id);
};The point of inject() is that the dependency list remains visible and type-checked.