Loading repository dataβ¦
Loading repository dataβ¦
exuanbo / repository
π§ββοΈ Lightweight and flexible dependency injection library for JavaScript and TypeScript, w/wo ECMAScript decorators.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
Lightweight and flexible dependency injection library for JavaScript and TypeScript, w/wo ECMAScript decorators.
npm install di-wise
pnpm add di-wise
yarn add di-wise
Also available on JSR:
deno add jsr:@exuanbo/di-wise
reflect-metadataexperimentalDecorators requiredExample:
import {createContainer, Inject, inject, Injectable, Scope, Scoped, Type} from "di-wise";
interface Spell {
cast(): void;
}
const Spell = Type<Spell>("Spell");
@Scoped(Scope.Container)
@Injectable(Spell)
class Fireball implements Spell {
cast() {
console.log("π₯");
}
}
class Wizard {
@Inject(Wand)
wand!: Wand;
// Equivalent to
wand = inject(Wand);
constructor(spell = inject(Spell)) {
// inject() can be used anywhere during construction
this.wand.store(spell);
}
}
const container = createContainer();
container.register(Fireball);
// Under the hood
[Fireball, Spell].forEach((token) => {
container.register(
token,
{useClass: Fireball},
{scope: Scope.Container},
);
});
const wizard = container.resolve(Wizard);
wizard.wand.activate(); // => π₯
Build(), Value()Example:
import {Build, createContainer, inject, Value} from "di-wise";
class Wizard {
equipment = inject(
Cloak,
// Provide a default value
Value({
activate() {
console.log("π»");
},
}),
);
wand: Wand;
constructor(wand: Wand) {
this.wand = wand;
}
}
const container = createContainer();
const wizard = container.resolve(
Build(() => {
// inject() can be used in factory functions
const wand = inject(Wand);
return new Wizard(wand);
}),
);
wizard.equipment.activate(); // => π»
Example:
import {createContainer, inject, Injectable, Type} from "di-wise";
const MagicSchool = Type<string>("MagicSchool");
const Spell = Type<{cast(): void}>("Spell");
// Parent container with shared config
const hogwarts = createContainer();
hogwarts.register(MagicSchool, {useValue: "Hogwarts"});
@Injectable(Spell)
class Fireball {
school = inject(MagicSchool);
cast() {
console.log(`π₯ from ${this.school}`);
}
}
// Child containers with isolated spells
const gryffindor = hogwarts.createChild();
gryffindor.register(Fireball);
const slytherin = hogwarts.createChild();
slytherin.register(Spell, {
useValue: {cast: () => console.log("π")},
});
gryffindor.resolve(Spell).cast(); // => π₯ from Hogwarts
slytherin.resolve(Spell).cast(); // => π
Inherited (default), Transient, Resolution, ContainerExample for singleton pattern:
import {createContainer, Scope} from "di-wise";
export const singletons = createContainer({
defaultScope: Scope.Container,
autoRegister: true,
});
// Always resolves to the same instance
const wizard = singletons.resolve(Wizard);
Inherits the scope from its dependent. If there is no dependent (top-level resolution), behaves like Transient.
import {createContainer, Scope, Scoped} from "di-wise";
@Scoped(Scope.Container)
class Wizard {
wand = inject(Wand);
}
const container = createContainer();
container.register(
Wand,
{useClass: Wand},
{scope: Scope.Inherited},
);
container.register(Wizard);
// Dependency Wand will be resolved with "Container" scope
const wizard = container.resolve(Wizard);
Creates a new instance every time the dependency is requested. No caching occurs.
Creates one instance per resolution graph. The same instance will be reused within a single dependency resolution, but new instances are created for separate resolutions.
@Scoped(Scope.Resolution)
class Wand {}
class Inventory {
wand = inject(Wand);
}
class Wizard {
inventory = inject(Inventory);
wand = inject(Wand);
}
const container = createContainer();
const wizard = container.resolve(Wizard);
expect(wizard.inventory.wand).toBe(wizard.wand);
Creates one instance per container (singleton pattern). The instance is cached and reused for all subsequent resolutions within the same container.
Type.Null and Type.UndefinedExample:
import {inject, Type} from "di-wise";
class Wizard {
wand = inject(Wand, Type.Null);
// ^? (property) Wizard.wand: Wand | null
spells = injectAll(Spell, Type.Null);
// ^? (property) Wizard.spells: Spell[]
// => []
}
@Inject() or inject.by())Example:
import {createContainer, Inject, inject} from "di-wise";
class Wand {
owner = inject(Wizard);
}
class Wizard {
@Inject(Wand)
wand!: Wand;
// Equivalent to
wand = inject.by(this, Wand);
}
const container = createContainer();
const wizard = container.resolve(Wizard);
expect(wizard.wand.owner).toBe(wizard);
InjectorExample:
import {createContainer, inject, Injector} from "di-wise";
class Wizard {
private injector = inject(Injector);
private wand?: Wand;
getWand() {
// Lazy load wand only when needed
return (this.wand ??= this.injector.inject(Wand));
}
castAllSpells() {
// Get all registered spells
const spells = this.injector.injectAll(Spell);
spells.forEach((spell) => spell.cast());
}
}
const container = createContainer();
const wizard = container.resolve(Wizard);
wizard.getWand(); // => Wand
The injector maintains the same resolution context as its injection point, allowing proper handling of scopes and circular dependencies:
import {createContainer, inject, Injector} from "di-wise";
class Wand {
owner = inject(Wizard);
}
class Wizard {
private injector = inject.by(this, Injector);
getWand() {
return this.injector.inject(Wand);
}
}
const container = createContainer();
const wizard = container.resolve(Wizard);
const wand = wizard.getWand();
expect(wand.owner).toBe(wizard);
See discussion Does di-wise support constructor injection? #12
Example:
import {applyMiddleware, createContainer, type Middleware} from "di-wise";
const logger: Middleware = (composer, _api) => {
composer
.use("resolve", (next) => (token) => {
console.log("Resolving:", token.name);
const result = next(token);
console.log("Resolved:", token.name);
return result;
})
.use("resolveAll", (next) => (token) => {
console.log("Resolving all:", token.name);
const result = next(token);
console.log("Resolved all:", token.name);
return result;
});
};
const performanceTracker: Middleware = (composer, _api) => {
composer.use("resolve", (next) => (token) => {
const start = performance.now();
const result = next(token);
const end = performance.now();
console.log(`Resolution time for ${token.name}: ${end - start}ms`);
return result;
});
};
const container = applyMiddleware(createContainer(), [logger, performanceTracker]);
// Use the container with applied middlewares
const wizard = container.resolve(Wizard);
Middlewares are applied in array order but execute in reverse order, allowing outer middlewares to wrap and control the behavior of inner middlewares.
ποΈ WIP (PR welcome)
See API documentation.
Inspired by:
MIT License @ 2024-Present Xuanbo Cheng