https://github.com/TypeFox/djinject
Dependency injection done right.
https://github.com/TypeFox/djinject
Last synced: 6 months ago
JSON representation
Dependency injection done right.
- Host: GitHub
- URL: https://github.com/TypeFox/djinject
- Owner: TypeFox
- License: mit
- Created: 2022-09-16T08:21:22.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2023-03-23T00:10:22.000Z (about 3 years ago)
- Last Synced: 2025-02-08T04:45:39.317Z (about 1 year ago)
- Language: TypeScript
- Homepage:
- Size: 244 KB
- Stars: 24
- Watchers: 5
- Forks: 1
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://www.npmjs.com/package/djinject/)
[](https://github.com/langium/djinject/actions/workflows/build.yml)
[](https://gitpod.io/#https://github.com/langium/djinject)
**Djinject** empowers developers designing decoupled applications and frameworks. **Djinject**'s main goal is increasing the developer experience by offering a tiny, yet powerful API, keeping dependencies in central module definitions and by using TypeScript's type system to restrain runtime challenges.
| | djinject | inversify |
|------------------|:----------:|:-----------:|
| minified | [](https://bundlephobia.com/result?p=djinject@latest) | [](https://bundlephobia.com/result?p=inversify@latest) |
| minzipped | [](https://bundlephobia.com/result?p=djinject@latest) | [](https://bundlephobia.com/result?p=inversify@latest) |
| typesafe | ✅ | ❌ |
| requirements | none | decorators |
| style | functional | imperative |
| API surface area | tiny | non-trivial |
## Features
* type-safe
* tiny footprint
* property injection
* rebinding dependencies
* dependency cycle detection
* lazy and eager initialization
* no magic, no global state
* no decorators
* no dependencies
* no configuration
* no boilerplate
## Quickstart
The first step is to add **djinject** to your application.
```sh
npm i djinject
```
Bascially, the only thing needed is to define **modules** of **factories** and finally call **inject**. The resulting **container** provides concrete **instances**.
```ts
import { inject } from 'djinject';
// create an inversion of control container
const container = inject({
hi: () => 'Hi',
sayHi: () => (name: string) => `${container.hi} ${name}!`
});
// prints 'Hi Djinject!'
console.log(container.sayHi('Djinject'));
```
## API
### Terminology
The **inject** function is turning **modules** into a **container**. A **module** is a plain vanilla JS object, composed of nested **groups** and **dependency factories**. Factories may return any JS value, e.g. constants, singletons and providers. Unlike [Inversify](https://github.com/inversify/InversifyJS), there is no need to decorate classes.
```ts
import { inject, Module } from 'djinject';
// Defining a _context_ of dependencies
type Context = {
group: {
value: Value // any JS type, here a class
}
}
// A _module_ contains nested _groups_ (optional) and _factories_
const module = {
group: {
// a factory of type Factory
value: (ctx: Context) => new Value(ctx)
}
} satisfies Module;
// A _container_ of type Container<[Module]> = Context
const container = inject(module);
// Values can be obtained from the container
const value = container.group.value;
```
### Context
A **container** provides each **factory** with a parameter called **context**.
```ts
type C = {
value: string
}
// ❌ compiler error: value is missing
const container = inject({
factory: (ctx: C) => () => ctx.value
});
```
The **context** of type **C** provides a **value** that can't be resolved. The **inject** call is type-checked by TS the way that the completeness of the arguments is checked.
Such **missing dependencies** need to be provided by adding additional **modules** to the **inject** call.
```ts
// ✅ fixed, value is defined
const container = inject({
createValue: (ctx: C) => () => ctx.value
}, {
value: () => '🧞♀️'
});
```
Now the compiler is satisfied and we can start using the **container**.
```ts
// prints 🧞♀️
console.log(container.createValue());
```
You might have noticed that the **container** automatically **injects** itself as the **context** when calling the **createValue** function.
### Eager vs lazy initialization
A dependency **container.group.value** is **lazily** initialized when first accessed on the container. Initialize a factory **eagerly** at the time of the **inject** call by wrapping it in an **init** call. Hint: groups can be eagerly initialized as well.
A use case for **eager initialization** would be to ensure that **side effects** take place during the initialization of the **container**.
```ts
import { init, inject, Module } from 'djinject';
type C = {
logger: string
}
const module = {
service: init(() => {
console.log('Service initialized');
})
} satisfies Module;
const ctr = inject(module);
console.log('App started');
ctr.service
```
In the **eager** case, the output is
```plain
Service initialized
App started
```
In the **lazy** case, the output is
```plain
App started
Service initialized
```
Please note that **eager factories** overwrite **lazy factories** vice versa when **rebinding** them using **additional modules** in the **inject** call.
### Rebinding dependencies
The main advantage of **dependency injection** arises from the fact that an application is able to **rebind dependencies**. That way the **structure** of a system can be fixated while the **behavior** can be changed.
The main vehicle for **rebinding dependencies** is the **inject** function which receives a variable amount of **modules**.
The behavior of an application can be enhanced by overwriting existing functionality using additional modules.
```ts
type C = {
test: () => void
eval: (a: number, b: number) => number
}
const m1 = {
test: (ctx) => () => {
console.log(ctx.eval(1, 1));
},
eval: () => (a, b) => a + b
} satisfies Module; // requires C
const m2 = {
eval: () => (a, b) => a * b
} satisfies Module; // partial C
const ctr = inject(m1, m2);
// = 1
ctr.test();
```