
Security News
Another Round of TEA Protocol Spam Floods npm, But It’s Not a Worm
Recent coverage mislabels the latest TEA protocol spam as a worm. Here’s what’s actually happening.
@mikrokit/di
Advanced tools
A lightweight TypeScript dependency injection container that uses only strip-tipes compliant methodologies and does not rely on reflect-metadata
A lightweight TypeScript dependency injection container that uses only strip-tipes compliant methodologies and does not rely on reflect-metadata
npm install @mikrokit/di
# or
yarn add @mikrokit/di
# or
pnpm add @mikrokit/di
Container is a thing that will manage you dependencies and give you a way to inject them
import { createContainer } from '@mikrokit/di'
const container = createContainer()
The simplest entity of dependency injection is a provider.
import { defineProvider } from '@mikrokit/di'
export const Logger = defineProvider(() => {
const info = (message: string) => {
console.log(`[INFO]: ${message}`)
}
const error = (message: string) => {
console.error(`[ERROR]: ${message}`)
}
return {
info,
error,
}
})
In order for Logger to be accessible through container we will need to provide it to the container.
import { createContainer } from '@mikrokit/di'
import { Logger } from './logger'
const container = createContainer().provide(Logger)
In order to instantiate and get the provider you provided, we can use .inject. Warning: this call always returns promise
// ...
await container.inject(Logger)
That's what we call DI. Let's define a different provider Fetcher for this. In order to inject Logger into Fetcher we will need to call injector.inject with the provider definition as a parameter. From this example we can actually see why Logger and Fetcher are named in PascalCase instead of camelCase - because when you will be referencing them to inject, you will probably use the camelCase version of name for the exact instance of the provider
import { defineProvider } from '@mikrokit/di'
import { Logger } from './logger'
export const Fetcher = defineProvider(async (injector) => {
const logger = await injector.inject(Logger)
const fetchAndLogData = async (url: string) => {
try {
const response = await fetch(url)
const data = await response.text()
logger.info(data)
} catch (e) {
logger.error(`${e}`)
}
}
return {
fetchAndLogData,
}
})
FetcherFor this, we will need to provide both Logger and Fetcher into our container and just inject the Fetcher.
*NOTE: the order of .provide calls in container doesn't matter (at least for single providers)
import { createContainer } from '@mikrokit/di'
import { Logger } from './logger'
import { Fetcher } from './fetcher'
const container = createContainer().provide(Logger).provide(Fetcher)
const fetcher = await container.inject(Fetcher)
Though it is generally a bad practice that code has any circular dependencies, there are cases when there is no other way to overcome this (for example, in chat-bot development). For this matter, there is a mechanism of "lazy" dependency injection.
To show an example, let's use existing Logger and Fetcher and extend Logger to send error logs over HTTP:
import { defineProvider } from '@mikrokit/di'
import { Logger } from './logger'
export const Fetcher = defineProvider(async (injector) => {
const logger = await injector.inject(Logger)
const postData = async (url: string, data: any) => {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
} catch (e) {
logger.error(`${e}`)
}
}
const fetchAndLogData = async (url: string) => {
try {
const response = await fetch(url)
const data = await response.text()
logger.info(data)
} catch (e) {
logger.error(`${e}`)
}
}
return {
postData,
fetchAndLogData,
}
})
import { defineProvider } from '@mikrokit/di'
export const Logger = defineProvider((injector) => {
const fetcher = injector.injectLazy(Fetcher)
const info = (message: string) => {
const fullMessage = `[INFO]: ${message}`
console.log(fullMessage)
fetcher.value.postData('https://myserver.com/logs', fullMessage)
}
const error = (message: string) => {
const fullMessage = `[ERROR]: ${message}`
console.log(fullMessage)
fetcher.value.postData('https://myserver.com/logs', fullMessage)
}
return {
info,
error,
}
})
In this case, we are using injectLazy API to get Fetcher inside Logger though Fetcher is already dependent on Logger. When using this API, Fetcher instantiation happens right after the Logger is fully instanciated.
The big limitation of lazy-injected providers is that it is only possible to use lazy-injected providers after the provider has been fully built, as before that they are not instanciated at all.
The injected provider is available via .value getter of the object returned by injectLazy. As when using injectLazy the injection doesn't happen before the provider is instanciated, it isn't needed to await injectLazy call.
Provider is... well it's basically anything. Anything that is provided to injection container or module is a provider.
Any provider is defined as a factory function that accepts Injector, that is injection context that allows injecting other providers.
Static values are defined as factories too, which adds a little overhead but don't add too much noize in codebase of the library and the API
The providers are provided into modules/containers using Provider Token, which acts like an identifier for provider without declaring its specific implementation.
When you use defineProvider the token is generated automatically and is provided through the factory. If you don't want to have attached token automatically, you can use defineProviderFactory, which is only a type helper that doesn't do anything to the factory you provide to it.
// Both a factory and a token
const MyProvider = defineProvider(() => 'test')
// Only a function
const myProviderFactory = defineProviderFactory(() => 'test')
For the cases when you already have the token that you will be defining a provider for (for example, this is the case for group providers), defineProvider can accept the token as a 2-nd parameter into it.
If there is some asynchronous action that should be taken on provider instantiation, the factory can also be async.
const Redis = defineProvider(async () => {
const redis = new RedisConnection()
await redis.connect()
return redis
})
This, however, creates a limitation: all injections are asynchronous.
Provider token is a typed "pointer" to some provider. It is used as an injection-key
The provider tokens in @mikrokit/di are based on javascript symbols. This way, we ensure that each token is completely unique.
There are two types of tokens: single tokens and group tokens
Single token is a token that references only one provider in the injection. It can only be provided once and will fail to provide the second time.
Most of thins in your codebase will probably be referenced by a single-type token, such as services, repositories, configurations etc.
Group token is a token that can reference 0 or more providers. There is no limit on how many providers you can provide using this token.
Group tokens are great for defining providers that would be used on higher-levels. For example, API handlers or queue system queues.
Container is a thing that allows you to provide and inject your providers. It is basically an injection container itself.
Module is a simple part that allows grouping of some providers into one thing, which can then either be:
This allows creating cleaner definition files
Injector is passed inside your providers and is basically a Container, but covered with the Injector interface so we don't have situations when providers are provided inside providers (which is not good)
It allows injecting other providers by a token
Library supports two provision scoped defined in ProvideScope enum:
SINGLETON - uses a shared copy of a providerTRANSIENT - uses a freshly instantiated copy of a provider. Note: if a provider is injected as transient, it doesn't mean that its sub-dependencies will be injected as transient as well. If provider uses singleton injection internally, then its dependencies will be loaded as singletonsOne of the greatest advantages of dependency injection is code being easier to test. Example of how it might work:
// In your test
const testContainer = createContainer()
// Mock dependencies
testContainer.provide(Logger, defineStaticProvider(/* Mock code */))
testContainer.provide(Fetcher, defineStaticProvider(/* Mock code */))
// Test with mocked dependencies
const fetcher = await testContainer.inject(Fetcher)
The MIT License (MIT)
Copyright (c) 2025-present, Artem Tarasenko
FAQs
A lightweight TypeScript dependency injection container that uses only strip-tipes compliant methodologies and does not rely on reflect-metadata
We found that @mikrokit/di demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Recent coverage mislabels the latest TEA protocol spam as a worm. Here’s what’s actually happening.

Security News
PyPI adds Trusted Publishing support for GitLab Self-Managed as adoption reaches 25% of uploads

Research
/Security News
A malicious Chrome extension posing as an Ethereum wallet steals seed phrases by encoding them into Sui transactions, enabling full wallet takeover.