This package is intended to use as a Container for Dependency Injection... making Inversion of Control: Easy, Fast, Clean and Typesafe.
This implementation wraps https://github.com/jeffijoe/awilix package! Check their docs for detailed/advanced usage.
# Yarn
yarn add @occupop/lib-container
# NPM
npm install @occupop/lib-container
# Bun
bun add @occupop/lib-container
import { createTypedContainer } from '@occupop/lib-container'
//... all other imports omited...
export const container = createTypedContainer({
// Top-level
graphqlServer: { asFunction: makeGraphqlServer, singleton: true },
mongoClient: { asFunction: makeMongoClient, singleton: true },
getMongoCollection: { asFunction: makeGetMongoCollection, singleton: true },
// ... add .singleton() to top-level services to cache factory call
// Services
fooService: { asFunction: makeFooService, scoped: true },
barService: { asClass: BarService, scoped: true },
// ... you can add .scoped() cache some lifecicle factory calls
// Repositories
fooRepository: { asFunction: makeFooRepository, singleton: true },
barRepository: { asFunction: makeBarRepository, singleton: true },
// Session scope (possible null)
authUser: { asValue: null as AuthUser | null }, // for injecting AuthService.getUser()
request: { asValue: null as Request | null }, // for injecting Express request
})
// typed dependencies to make inject easy...
type Deps = typeof container.cradle
In order to have a scoped container available in the request scope, we use to
apply a context function to the server to intercept the request and create a
scoped container createTypedContainerScope
with request available and even the auth user ready to be used.
// context-function.ts
import { createTypedContainerScope } from '@occupop/lib-container'
//... all other imports omited...
export function makeAuthContextFunction({ container, getAuthUser }: Deps) {
return async (context) => {
const authUser = await getAuthUser(context.req)
const scopedContainer = createTypedContainerScope(container, {
request: { asValue: context.req },
authUser: { asValue: authUser },
})
return { ...context, container: scopedContainer }
}
}
export type ScopedDeps = ReturnType<ReturnType<typeof makeAuthContextFunction>>['container']
NOTE: even having AuthUser available, NEVER require as a Service dependency! REMEMBER: all services should be able to be used from commands, queue, etc!
// amazing-service.ts
import type { Deps } from '.container'
export function makeAmazingService({
fooRepository,
barRepository,
randomDependencieHere,
// ... dependencies will be type-hinted thanks to type Deps!
}: Deps) {
return {
foo: () => { /** ... nice and clean implementation ... */ },
bar: () => { /** ... another clean implementation ... */ },
}
}
// server.ts
import { type Deps, container } from '.container'
container.build(async ({ logger, mongoClient, eventService }: Deps) => {
await mongoClient.connect()
logger.log('Mongo connected')
eventService.consumer().start()
logger.log('Worker ready!')
})
// OR ...
// server.ts
import { container } from '.container'
const { logger, mongoClient, eventService } = container.cradle
await mongoClient.connect()
logger.log('Mongo connected')
eventService.consumer().start()
logger.log('Worker ready!')
- Improve later register to be made in the same format
- Correct container self signature to inferred cradle type
- Resolve circular reference from old version and keep awilix signature format