rsdi
Advanced tools
Comparing version 2.4.1 to 3.0.0-alpha.1
@@ -1,32 +0,18 @@ | ||
import { DependencyResolver, IDIContainer, NamedResolvers, ResolveDependencyType, ResolverName, ConvertToDefinedDependencies, NonStrictNamedResolvers } from "./types"; | ||
import ReferenceResolver from "./resolvers/ReferenceResolver"; | ||
/** | ||
* Dependency injection container | ||
*/ | ||
export default class DIContainer<ContainerResolvers extends NamedResolvers = {}> implements IDIContainer<ContainerResolvers> { | ||
type Factory<ContainerResolvers extends ResolvedDependencies> = (resolvers: ContainerResolvers) => any; | ||
type ResolvedDependencies = { | ||
[k: string]: any; | ||
}; | ||
type StringLiteral<T> = T extends string ? string extends T ? never : T : never; | ||
type Container<ContainerResolvers extends ResolvedDependencies> = DIContainer<ContainerResolvers> & ContainerResolvers; | ||
export default class DIContainer<ContainerResolvers extends ResolvedDependencies = {}> { | ||
private resolvers; | ||
private resolvedDependencies; | ||
/** | ||
* Resolves dependency by name | ||
* @param dependencyName - DefinitionName name of the dependency. String or class name. | ||
* @param parentDeps - array of parent dependencies (used to detect circular dependencies) | ||
*/ | ||
get<UserDefinedType = void, Name extends ResolverName<ContainerResolvers> = ResolverName<ContainerResolvers>>(dependencyName: Name, parentDeps?: string[]): UserDefinedType extends void ? ResolveDependencyType<ContainerResolvers, Name> : UserDefinedType; | ||
use<Name extends ResolverName<ContainerResolvers> = ResolverName<ContainerResolvers>>(dependencyName: Name): ReferenceResolver<ContainerResolvers, Name>; | ||
/** | ||
* Adds multiple dependency resolvers to the container | ||
* @param resolvers - named dependency object | ||
*/ | ||
add<N extends NonStrictNamedResolvers>(resolvers: N): asserts this is this & DIContainer<ConvertToDefinedDependencies<N>>; | ||
/** | ||
* Adds single dependency definition to the container | ||
* @param name - string name for the dependency | ||
* @param resolver - raw value or instance of IDefinition | ||
*/ | ||
private addResolver; | ||
__(): ContainerResolvers; | ||
private context; | ||
add<N extends string, R extends Factory<ContainerResolvers>>(name: StringLiteral<N>, resolver: R): Container<ContainerResolvers & { | ||
[n in N]: ReturnType<R>; | ||
}>; | ||
get<Name extends keyof ContainerResolvers>(dependencyName: Name): ContainerResolvers[Name]; | ||
extend<E extends (container: Container<ContainerResolvers>) => any>(f: E): ReturnType<E>; | ||
private toContainer; | ||
} | ||
/** | ||
* Resolves given function parameters | ||
*/ | ||
export declare function resolveFunctionParameters(diContainer: DIContainer, parameters?: Array<DependencyResolver<any> | any>, parentDeps?: string[]): any[]; | ||
export {}; |
"use strict"; | ||
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { | ||
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { | ||
if (ar || !(i in from)) { | ||
if (!ar) ar = Array.prototype.slice.call(from, 0, i); | ||
ar[i] = from[i]; | ||
} | ||
} | ||
return to.concat(ar || Array.prototype.slice.call(from)); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.resolveFunctionParameters = void 0; | ||
var AbstractResolver_1 = __importDefault(require("./resolvers/AbstractResolver")); | ||
var RawValueResolver_1 = __importDefault(require("./resolvers/RawValueResolver")); | ||
var errors_1 = require("./errors"); | ||
var DefinitionName_1 = require("./DefinitionName"); | ||
var ReferenceResolver_1 = __importDefault(require("./resolvers/ReferenceResolver")); | ||
/** | ||
* Dependency injection container | ||
*/ | ||
var DIContainer = /** @class */ (function () { | ||
function DIContainer() { | ||
const errors_1 = require("./errors"); | ||
const containerMethods = ["add", "get", "extend"]; | ||
class DIContainer { | ||
constructor() { | ||
this.resolvers = {}; | ||
this.resolvedDependencies = {}; | ||
this.context = {}; | ||
} | ||
/** | ||
* Resolves dependency by name | ||
* @param dependencyName - DefinitionName name of the dependency. String or class name. | ||
* @param parentDeps - array of parent dependencies (used to detect circular dependencies) | ||
*/ | ||
DIContainer.prototype.get = function (dependencyName, parentDeps) { | ||
if (parentDeps === void 0) { parentDeps = []; } | ||
var name = (0, DefinitionName_1.definitionNameToString)(dependencyName); | ||
if (!(name in this.resolvers)) { | ||
throw new errors_1.DependencyIsMissingError(name); | ||
add(name, resolver) { | ||
if (containerMethods.includes(name)) { | ||
throw new errors_1.ForbiddenNameError(name); | ||
} | ||
if (parentDeps.includes(name)) { | ||
throw new errors_1.CircularDependencyError(name, parentDeps); | ||
this.resolvers = Object.assign(Object.assign({}, this.resolvers), { [name]: resolver }); | ||
let updatedObject = this; | ||
if (!this.hasOwnProperty(name)) { | ||
updatedObject = Object.defineProperty(this, name, { | ||
get() { | ||
return this.get(name); | ||
}, | ||
}); | ||
} | ||
if (this.resolvedDependencies[name] !== undefined) { | ||
return this.resolvedDependencies[name]; | ||
} | ||
var definition = this.resolvers[name]; | ||
definition.setParentDependencies(__spreadArray(__spreadArray([], parentDeps, true), [name], false)); | ||
this.resolvedDependencies[name] = definition.resolve(this); | ||
return this.resolvedDependencies[name]; | ||
}; | ||
DIContainer.prototype.use = function (dependencyName) { | ||
return new ReferenceResolver_1.default(dependencyName); | ||
}; | ||
/** | ||
* Adds multiple dependency resolvers to the container | ||
* @param resolvers - named dependency object | ||
*/ | ||
DIContainer.prototype.add = function (resolvers) { | ||
var _this = this; | ||
Object.keys(resolvers).forEach(function (name) { | ||
_this.addResolver(name, resolvers[name]); | ||
this.context = new Proxy(this, { | ||
get(target, property) { | ||
if (containerMethods.includes(property.toString())) { | ||
throw new errors_1.IncorrectInvocationError(); | ||
} | ||
// @ts-ignore | ||
return target[property]; | ||
}, | ||
}); | ||
}; | ||
/** | ||
* Adds single dependency definition to the container | ||
* @param name - string name for the dependency | ||
* @param resolver - raw value or instance of IDefinition | ||
*/ | ||
DIContainer.prototype.addResolver = function (name, resolver) { | ||
if (!(resolver instanceof AbstractResolver_1.default)) { | ||
resolver = new RawValueResolver_1.default(resolver); | ||
return updatedObject; | ||
} | ||
get(dependencyName) { | ||
if (this.resolvedDependencies[dependencyName] !== undefined) { | ||
return this.resolvedDependencies[dependencyName]; | ||
} | ||
this.resolvers[name] = resolver; | ||
}; | ||
DIContainer.prototype.__ = function () { | ||
throw new Error("Method not implemented."); | ||
}; | ||
return DIContainer; | ||
}()); | ||
exports.default = DIContainer; | ||
/** | ||
* Resolves given function parameters | ||
*/ | ||
function resolveFunctionParameters(diContainer, parameters, parentDeps) { | ||
if (parameters === void 0) { parameters = []; } | ||
if (parentDeps === void 0) { parentDeps = []; } | ||
return parameters.map(function (parameter) { | ||
if (parameter instanceof AbstractResolver_1.default) { | ||
parameter.setParentDependencies(parentDeps); | ||
return parameter.resolve(diContainer); | ||
const resolver = this.resolvers[dependencyName]; | ||
if (!resolver) { | ||
throw new errors_1.DependencyIsMissingError(dependencyName); | ||
} | ||
return parameter; | ||
}); | ||
this.resolvedDependencies[dependencyName] = resolver(this.context); | ||
return this.resolvedDependencies[dependencyName]; | ||
} | ||
extend(f) { | ||
return f(this.toContainer()); | ||
} | ||
toContainer() { | ||
return this; | ||
} | ||
} | ||
exports.resolveFunctionParameters = resolveFunctionParameters; | ||
//# sourceMappingURL=DIContainer.js.map | ||
exports.default = DIContainer; |
@@ -1,15 +0,9 @@ | ||
export declare class CircularDependencyError extends Error { | ||
constructor(name: string, path: string[]); | ||
} | ||
export declare class DependencyIsMissingError extends Error { | ||
constructor(name: string); | ||
} | ||
export declare class InvalidConstructorError extends Error { | ||
constructor(); | ||
export declare class ForbiddenNameError extends Error { | ||
constructor(name: string); | ||
} | ||
export declare class MethodIsMissingError extends Error { | ||
constructor(objectName: string, methodName: string); | ||
} | ||
export declare class FactoryDefinitionError extends Error { | ||
export declare class IncorrectInvocationError extends Error { | ||
constructor(); | ||
} |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.FactoryDefinitionError = exports.MethodIsMissingError = exports.InvalidConstructorError = exports.DependencyIsMissingError = exports.CircularDependencyError = void 0; | ||
var CircularDependencyError = /** @class */ (function (_super) { | ||
__extends(CircularDependencyError, _super); | ||
function CircularDependencyError(name, path) { | ||
return _super.call(this, "Circular Dependency is detected. Dependency: \"".concat(name, "\", path: ") + | ||
path.join(" -> ") + | ||
".") || this; | ||
exports.IncorrectInvocationError = exports.ForbiddenNameError = exports.DependencyIsMissingError = void 0; | ||
class DependencyIsMissingError extends Error { | ||
constructor(name) { | ||
super(`Dependency with name ${name} is not defined`); | ||
} | ||
return CircularDependencyError; | ||
}(Error)); | ||
exports.CircularDependencyError = CircularDependencyError; | ||
var DependencyIsMissingError = /** @class */ (function (_super) { | ||
__extends(DependencyIsMissingError, _super); | ||
function DependencyIsMissingError(name) { | ||
return _super.call(this, "Dependency with name ".concat(name, " is not defined")) || this; | ||
} | ||
return DependencyIsMissingError; | ||
}(Error)); | ||
} | ||
exports.DependencyIsMissingError = DependencyIsMissingError; | ||
var InvalidConstructorError = /** @class */ (function (_super) { | ||
__extends(InvalidConstructorError, _super); | ||
function InvalidConstructorError() { | ||
return _super.call(this, "Invalid constructor have been provided") || this; | ||
class ForbiddenNameError extends Error { | ||
constructor(name) { | ||
super(`Dependency with name ${name} is not allowed`); | ||
} | ||
return InvalidConstructorError; | ||
}(Error)); | ||
exports.InvalidConstructorError = InvalidConstructorError; | ||
var MethodIsMissingError = /** @class */ (function (_super) { | ||
__extends(MethodIsMissingError, _super); | ||
function MethodIsMissingError(objectName, methodName) { | ||
return _super.call(this, "".concat(methodName, " is not a member of ").concat(objectName)) || this; | ||
} | ||
exports.ForbiddenNameError = ForbiddenNameError; | ||
class IncorrectInvocationError extends Error { | ||
constructor() { | ||
super(`Incorrect invocation of DIContainer`); | ||
} | ||
return MethodIsMissingError; | ||
}(Error)); | ||
exports.MethodIsMissingError = MethodIsMissingError; | ||
var FactoryDefinitionError = /** @class */ (function (_super) { | ||
__extends(FactoryDefinitionError, _super); | ||
function FactoryDefinitionError() { | ||
return _super.call(this, "Factory must be a function") || this; | ||
} | ||
return FactoryDefinitionError; | ||
}(Error)); | ||
exports.FactoryDefinitionError = FactoryDefinitionError; | ||
//# sourceMappingURL=errors.js.map | ||
} | ||
exports.IncorrectInvocationError = IncorrectInvocationError; |
import DIContainer from "./DIContainer"; | ||
import { diObject as object, diValue as value, diUse as use, diFactory as factory, diFunc as func } from "./resolversShorthands"; | ||
import { IDIContainer } from "./types"; | ||
export default DIContainer; | ||
export { object, value, use, func, factory, IDIContainer }; |
@@ -6,11 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.factory = exports.func = exports.use = exports.value = exports.object = void 0; | ||
var DIContainer_1 = __importDefault(require("./DIContainer")); | ||
var resolversShorthands_1 = require("./resolversShorthands"); | ||
Object.defineProperty(exports, "object", { enumerable: true, get: function () { return resolversShorthands_1.diObject; } }); | ||
Object.defineProperty(exports, "value", { enumerable: true, get: function () { return resolversShorthands_1.diValue; } }); | ||
Object.defineProperty(exports, "use", { enumerable: true, get: function () { return resolversShorthands_1.diUse; } }); | ||
Object.defineProperty(exports, "factory", { enumerable: true, get: function () { return resolversShorthands_1.diFactory; } }); | ||
Object.defineProperty(exports, "func", { enumerable: true, get: function () { return resolversShorthands_1.diFunc; } }); | ||
const DIContainer_1 = __importDefault(require("./DIContainer")); | ||
exports.default = DIContainer_1.default; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "rsdi", | ||
"version": "2.4.1", | ||
"description": "Simple dependency injection container for JavaScript/TypeScript", | ||
"scripts": { | ||
"test": "jest", | ||
"test:types": "jest -c jest.config.tsd.js", | ||
"format": "prettier 'src/**/*.ts' --write", | ||
"build": "tsc", | ||
"deploy": "yarn version minor && yarn build && yarn npm publish", | ||
"pre-commit": "lint-staged", | ||
"lint": "yarn eslint src/**/**.ts", | ||
"prettify": "prettier --write --ignore-unknown" | ||
}, | ||
"version": "3.0.0-alpha.1", | ||
"description": "TypeScript dependency injection container. Strong types without decorators.", | ||
"keywords": [ | ||
@@ -27,7 +17,2 @@ "dependency injection", | ||
], | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"homepage": "https://github.com/radzserg/rsdi", | ||
"author": "Sergey Radzishevskii <radzserg@gmail.com>", | ||
"license": "ISC", | ||
"lint-staged": { | ||
@@ -39,22 +24,20 @@ "src/**/*.ts": [ | ||
}, | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"homepage": "https://github.com/radzserg/rsdi", | ||
"author": "Sergey Radzishevskii <radzserg@gmail.com>", | ||
"license": "ISC", | ||
"devDependencies": { | ||
"@tsd/typescript": "^5.0.4", | ||
"@types/jest": "^29.5.1", | ||
"@types/node": "^20.1.2", | ||
"@typescript-eslint/eslint-plugin": "^5.27.1", | ||
"eslint": "^8.17.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-config-standard-with-typescript": "^21.0.1", | ||
"eslint-plugin-import": "^2.26.0", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-promise": "^6.0.0", | ||
"husky": "^7.0.2", | ||
"jest": "^29.5.0", | ||
"jest-runner-tsd": "^5.0.0", | ||
"lint-staged": "^11.2.0", | ||
"prettier": "^2.6.2", | ||
"ts-jest": "^29.1.0", | ||
"tsd": "^0.20.0", | ||
"typescript": "^5.0.4" | ||
"eslint": "^8.44.0", | ||
"husky": "^8.0.3", | ||
"lint-staged": "^13.2.3", | ||
"prettier": "^3.0.0", | ||
"tsd": "^0.28.1", | ||
"typescript": "^5.1.6", | ||
"vitest": "^0.33.0" | ||
}, | ||
"scripts": { | ||
"build": "tsc", | ||
"test": "vitest" | ||
} | ||
} |
304
README.md
# RSDI - Dependency Injection Container | ||
Simple and powerful dependency injection container for with strong type checking system. | ||
Simple and powerful dependency injection container for with strong type checking system. `rsdi` offers strong | ||
type-safety support. | ||
@@ -9,9 +10,4 @@ - [Motivation](#motivation) | ||
- [Architecture](#architecture) | ||
- [Usage](#usage) | ||
- [Raw values](#raw-values) | ||
- [Object resolver](#object-resolver) | ||
- [Function resolver](#function-resolver) | ||
- [Factory resolver](#factory-resolver) | ||
- [Typescript type resolution](#typescript-type-resolution) | ||
- [Dependency declaration](#dependency-declaration) | ||
- [How to use](#how-to-use) | ||
- [Strict types](#strict-types) | ||
- Wiki | ||
@@ -23,13 +19,18 @@ - [Async factory resolver](./docs/async_factory_resolver.md) | ||
Popular dependency injection libraries use `reflect-metadata` that allows to fetch argument types and based on | ||
those types and do autowiring. Autowiring is a nice feature but the trade-off is decorators. | ||
Popular dependency injection libraries utilize reflect-metadata to retrieve argument types and use those types | ||
to carry out autowiring. Autowiring is an advantageous feature, but it necessitates the wrapping of all your | ||
components with decorators. | ||
```typescript | ||
@injectable() | ||
class Foo {} | ||
class Foo { | ||
constructor(@inject("Database") private database?: Database) {} | ||
} | ||
// Notice how in order to allow the use of the empty constructor new Foo(), | ||
// we need to make the parameters optional, e.g. database?: Database. | ||
``` | ||
Why component Foo should know that it's injectable? | ||
Why should component Foo be aware that it's injectable? | ||
Your business logic depends on a specific framework that is not part of your domain model and can change. | ||
Your business logic relies on a particular framework, which isn't part of your domain model and is subject to change. | ||
@@ -42,4 +43,3 @@ More thoughts in a [dedicated article](https://radzserg.medium.com/https-medium-com-radzserg-dependency-injection-in-react-part-2-995e93b3327c) | ||
- Does not requires decorators | ||
- Great types resolution | ||
- Works great with both javascript and typescript | ||
- Strict types resolution | ||
@@ -66,3 +66,3 @@ ## When to use | ||
![architecture](https://github.com/radzserg/rsdi/raw/main/docs/RSDI_architecture.jpg "RSDI Architecture") | ||
![architecture](https://github.com/radzserg/rsdi3/raw/main/docs/RSDI_architecture.jpg "RSDI Architecture") | ||
@@ -80,3 +80,16 @@ An application always has an entry point, whether it is a web application or a CLI application. This is the only place where you | ||
### Simple use-case | ||
```typescript | ||
const container = new DIContainer() | ||
.add("a", () => "name1") | ||
.add("bar", () => new Bar()) | ||
.add("foo", (get) => new Foo(get("a"), get("bar"))); | ||
const foo = container.get("foo"); | ||
``` | ||
### Real life example | ||
```typescript | ||
// sample web application components | ||
@@ -86,3 +99,3 @@ | ||
userRegistrator: UserRegistrator, | ||
userRepository: UserRepository | ||
userRepository: UserRepository, | ||
) { | ||
@@ -129,27 +142,21 @@ return { | ||
```typescript | ||
import DIContainer, { object, use, factory, func, IDIContainer } from "rsdi"; | ||
import DIContainer from "rsdi"; | ||
export type AppDIContainer = ReturnType<typeof configureDI>; | ||
export default function configureDI() { | ||
const container = new DIContainer(); | ||
container.add({ | ||
buildDbConnection: factory(() => { | ||
buildDbConnection(); | ||
}), | ||
[MyDbProviderUserRepository.name]: func( | ||
MyDbProviderUserRepository, | ||
use(buildDbConnection) | ||
), | ||
[UserRegistrator.name]: object(UserRegistrator).construct( | ||
use(MyDbProviderUserRepository.name) | ||
), | ||
[UserController.name]: func( | ||
UserController, | ||
use(UserRegistrator.name), | ||
use(MyDbProviderUserRepository.name) | ||
), | ||
}); | ||
return container; | ||
return new DIContainer() | ||
.add("dbConnection", buildDbConnection()) | ||
.add("userRepository", (get) => | ||
MyDbProviderUserRepository(get("dbConnection")), | ||
) | ||
.add("userRegistrator", (get) => new UserRegistrator(get("userRepository"))) | ||
.add("userController", (get) => | ||
UserController(get("userRepository"), get("userRegistrator")), | ||
); | ||
} | ||
``` | ||
`container.get` - return type based on declaration. | ||
**All resolvers are resolved only once and their result persists over the life of the container.** | ||
@@ -163,5 +170,5 @@ | ||
app: core.Express, | ||
diContainer: IDIContainer | ||
diContainer: AppDIContainer, | ||
) { | ||
const usersController = diContainer.get(UsersController); | ||
const usersController = diContainer.get("UsersController"); | ||
app | ||
@@ -190,219 +197,8 @@ .route("/users") | ||
## Dependency Resolvers | ||
### Raw values resolver | ||
## Strict types | ||
Dependencies are set as raw values. Container keeps and return raw values. | ||
`rsdi` offers strong type-safety due to its native TypeScript support. It leverages TypeScript's type system to provide | ||
compile-time checks and ensure proper injection of dependencies. | ||
```typescript | ||
import DIContainer from "rsdi"; | ||
const container = new DIContainer(); | ||
container.add({ | ||
ENV: "PRODUCTION", | ||
HTTP_PORT: 3000, | ||
storage: new CookieStorage(), | ||
}); | ||
const env: string = container.get("ENV"); | ||
const port: number = container.get("HTTP_PORT"); | ||
const authStorage: AuthStorage = container.get(AuthStorage); // instance of AuthStorage | ||
``` | ||
### Object resolver | ||
`object(ClassName)` - constructs an instance of the given class. The simplest scenario it calls the class constructor `new ClassName()`. | ||
When you need to pass arguments to the constructor, you can use `construct` method. You can refer to the already defined | ||
dependencies via the `use` helper, or you can pass raw values. | ||
If you need to call object method after initialization you can use `method` it will be called after constructor. | ||
```typescript | ||
class ControllerContainer { | ||
constructor(authStorage: AuthStorage, logger: Logger) {} | ||
add(controller: Controller) { | ||
this.controllers.push(controller); | ||
} | ||
} | ||
// container | ||
const container = new DIContainer(); | ||
container.add({ | ||
Storage: object(CookieStorage), // constructor without arguments | ||
AuthStorage: object(AuthStorage).construct( | ||
use(Storage) // refers to existing dependency | ||
), | ||
UsersController: object(UserController), | ||
PostsController: object(PostsController), | ||
ControllerContainer: object(MainCliCommand) | ||
.construct(use(AuthStorage), new Logger()) // use existing dependency, or pass raw values | ||
.method("add", use(UsersController)) // call class method after initialization | ||
.method("add", use(PostsController)), | ||
}); | ||
``` | ||
### Function resolver | ||
Function resolver allows declaring lazy functions. Function will be called when it's actually needed. | ||
```typescript | ||
function UsersRepoFactory(knex: Knex): UsersRepo { | ||
return { | ||
async findById(id: number) { | ||
await knex("users").where({ id }); | ||
}, | ||
}; | ||
} | ||
const container = new DIContainer(); | ||
container.add({ | ||
DBConnection: knex(/* ... */), | ||
UsersRepoFactory: func(UsersRepoFactory, use("DBConnection")), | ||
}); | ||
const userRepo = container.get(UsersRepoFactory); | ||
``` | ||
### Factory resolver | ||
Factory resolver is similar to a Function resolver. You can use factory resolver when you need more flexibility | ||
during initialization. `container: IDIContainer` will be passed in as an argument to the factory method. You can | ||
resolve other dependencies inside the factory function and have conditions inside of it. | ||
```typescript | ||
const container = new DIContainer(); | ||
container.add({ | ||
BrowserHistory: factory(configureHistory), | ||
}); | ||
function configureHistory(container: IDIContainer): History { | ||
const history = createBrowserHistory(); | ||
const env = container.get("ENV"); | ||
if (env === "production") { | ||
// do what you need | ||
} | ||
return history; | ||
} | ||
const history = container.get("BrowserHistory"); | ||
``` | ||
## Typescript type resolution | ||
`container.get` - return type based on declaration. | ||
```typescript | ||
const container: DIContainer = new DIContainer(); | ||
container.add({ | ||
strVal: "raw string value", | ||
numberVal: 123, | ||
boolVal: true, | ||
objectVal: object(Buzz), // resolvers to object of class Buzz | ||
dateVal: func(function () { | ||
return new Date(); // resolves to ReturnType of the function | ||
}), | ||
}); | ||
const strVal = container.get("strVal"); // strVal: string | ||
const numberVal = container.get("numberVal"); // numberVal: number | ||
const boolVal = container.get("boolVal"); // boolVal: boolean | ||
const objectVal = container.get("objectVal"); // boolVal: Buzz | ||
const dateVal = container.get("dateVal"); // dateVal: Date | ||
``` | ||
`contrainer.use` - allows to reference declared dependency with respect to types. | ||
```typescript | ||
export class Foo { | ||
constructor(name: string, bar: Bar) {} | ||
} | ||
const container: DIContainer = new DIContainer(); | ||
container.add({ | ||
bar: new ObjectResolver(Bar), | ||
key1: new RawValueResolver("value1"), | ||
// `bar` dependency cannot be referenced in the same `add` call | ||
// Argument of type 'string' is not assignable to parameter of type... ' | ||
// foo: new ObjectResolver(Foo).construct("foo", container.use("bar")), | ||
}); | ||
container.add({ | ||
foo: new ObjectResolver(Foo).construct("foo", container.use("bar")), | ||
}); | ||
``` | ||
To support lazy loading `construct` method changes original Foo constructor to expect | ||
`(string | ReferenceResolver<string>, Bar | ReferenceResolver<Bar>)`. | ||
`container.use("bar")` - will return object `ReferenceResolver<Bar>` to respect safe types. | ||
`use` helper | ||
```typescript | ||
import { use } from "rsdi"; | ||
``` | ||
`use` helper is less strict version of `container.use`. It allows to reference dependencies that **will be defined** relying | ||
on convention over configuration rule. | ||
- if given name is a class - instance of the class | ||
- if given name is a function - return type of the function | ||
- if custom type is provided - return ReferenceResolver<Custom> | ||
- otherwise - any | ||
```typescript | ||
class Foo { | ||
constructor(private readonly bar: Bar) {} | ||
} | ||
function returnBoolean() { | ||
return true; | ||
} | ||
const container: DIContainer = new DIContainer(); | ||
container.add({ | ||
Bar: new Bar(), | ||
Foo: object(Foo).construct(use(Bar)), // resolves Bar | ||
expectBoolFunc: func(function (a: boolean) { | ||
return null; | ||
}, use(returnBoolean)), // resolves to ReturnType of returnBoolean function | ||
expectDateFunc: func(function (a: Date) { | ||
return null; | ||
}, use<Date>("date")), // resolves to Date | ||
expectDateFunc2: func(function (a: Date) { | ||
return null; | ||
}, use("date")), // resolves to any | ||
}); | ||
``` | ||
## Dependency declaration | ||
**Use string names** | ||
```typescript | ||
const container: DIContainer = new DIContainer(); | ||
container.add({ | ||
bar: new ObjectResolver(Bar), | ||
key1: new RawValueResolver("value1"), | ||
}); | ||
container.add({ | ||
foo: new ObjectResolver(Foo).construct("foo", container.use("bar")), | ||
}); | ||
``` | ||
Use function and class names by declaring them as `[MyClass.name]`. In this case, it is safe to rename functions and | ||
classes in the IDE. IDE will identify all usages and rename them in the container as well. You can declare all dependencies | ||
in a single `add` method and use `use` helper to reference other dependencies. | ||
```typescript | ||
container.add({ | ||
[Foo.name]: new Foo(), | ||
[MyFactory.name]: MyFactory(), | ||
[Foo.name]: object(Foo).construct(use(Bar)), | ||
}); | ||
const foo = container.get(Foo); | ||
const buzz = container.get(MyFactory); | ||
``` | ||
![strict type](https://github.com/radzserg/rsdi3/raw/main/docs/RSDI_types.png "RSDI types") |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
7
22869
9
110
1
195
1