injeca
Pure functional dependency injection with application lifecycle management.
Core Concepts
- No classes, no decorators — everything is plain functions and objects
- Singleton by default —
buildruns once, the result is cached - Lifecycle hooks —
onStart/onStopfor ordered startup and graceful shutdown - Topological resolution — dependencies are resolved in correct order, circular deps are detected
API Quick Reference
Global Container (Singleton)
The simplest way — injeca is a pre-created global container:
import { injeca, lifecycle } from 'injeca'
// Register a provider (returns a typed key for dependency declaration)
const config = injeca.provide('config', () => ({ port: 3000 }))
// Provider with dependencies and lifecycle
const server = injeca.provide({
dependsOn: { config, lifecycle },
async build({ dependsOn }) {
const { config, lifecycle } = dependsOn
const app = createServer()
lifecycle.appHooks.onStart(() => app.listen(config.port))
lifecycle.appHooks.onStop(() => app.close())
return app
},
})
// Invoke — runs after all providers are resolved
injeca.invoke({
dependsOn: { server },
async callback({ server }) {
console.log('Server ready:', server.address())
},
})
// Start the application (resolves deps → runs onStart hooks → runs invocations)
await injeca.start()
// Graceful shutdown (runs onStop hooks in reverse topological order)
process.once('SIGINT', () => injeca.stop())
Scoped Container (for tests, per-request, or multiple instances)
import { createContainer, invoke, lifecycle, provide, start, stop } from 'injeca'
const container = createContainer()
const token = provide(container, 'token', () => crypto.randomUUID())
invoke(container, {
dependsOn: { token },
callback: ({ token }) => console.log('token', token),
})
await start(container)
await stop(container)
Provider Patterns
Simple value provider
const config = injeca.provide('config', () => ({ port: 3000 }))
// or with scoped container:
const config = provide(container, 'config', () => ({ port: 3000 }))
Provider with dependencies
const db = injeca.provide({
dependsOn: { config },
build: ({ dependsOn }) => createDatabase(dependsOn.config.connectionString),
})
Provider with lifecycle hooks
const server = injeca.provide({
dependsOn: { config, lifecycle },
async build({ dependsOn }) {
const { config, lifecycle } = dependsOn
const app = createServer()
lifecycle.appHooks.onStart(() => app.listen(config.port))
lifecycle.appHooks.onStop(() => app.close())
return app
},
})
Lazy / reusable provider
If build returns a function, that function itself is cached (not its return value). Useful for factory patterns:
const createWindow = injeca.provide('createWindow', {
dependsOn: { config },
build: ({ dependsOn }) => {
const getWindow = setupReusableWindow(dependsOn.config)
return async () => await getWindow() // callable many times, same window
},
})
Lifecycle
Import lifecycle from injeca to declare start/stop hooks inside providers:
import { lifecycle } from 'injeca'
const service = injeca.provide({
dependsOn: { lifecycle },
build({ dependsOn }) {
dependsOn.lifecycle.appHooks.onStart(() => { /* runs on start */ })
dependsOn.lifecycle.appHooks.onStop(() => { /* runs on stop */ })
return myService
},
})
onStarthooks run in topological order (dependencies first)onStophooks run in reverse topological order (dependents first)- Both are async-safe
Manual Resolution
import { resolve } from 'injeca'
const resolved = await resolve(container, { config, db })
// resolved.config and resolved.db are ready to use
Logger Configuration
import { createContainer } from 'injeca'
// Disable logging
const container = createContainer({ enabled: false })
// Custom logger
import { createLoggLogger } from 'injeca'
const container = createContainer({ logger: createLoggLogger() })
Key Rules
- Singletons:
buildonly runs once — the return value is cached. If you return a function, the function is cached, not its result. - Dependency declaration: Use
dependsOnwith typed keys fromprovide()for full type safety. - Lifecycle: Always import
lifecyclefrominjecaand declare it independsOnto getonStart/onStophooks. - Start order: Call
start(container)orinjeca.start()after all providers and invocations are registered. - Circular deps: Injeca detects circular dependencies at resolution time and throws an error.
- No
any: Provider types are fully inferred frombuildreturn type anddependsOndeclarations.
Documentation
For the latest API reference, use context7 to query injeca documentation.