Comparing version 2.1.1 to 2.2.0
@@ -1,2 +0,2 @@ | ||
import { ResolverName } from "./types"; | ||
export declare function definitionNameToString(definitionName: ResolverName): string; | ||
import { NamedResolvers, ResolverName } from "./types"; | ||
export declare function definitionNameToString<ContainerResolvers extends NamedResolvers = {}>(definitionName: ResolverName<ContainerResolvers>): string; |
@@ -5,8 +5,9 @@ "use strict"; | ||
function definitionNameToString(definitionName) { | ||
if (typeof definitionName === "string") { | ||
return definitionName; | ||
if (typeof definitionName === "object" || | ||
(typeof definitionName === "function" && definitionName.name)) { | ||
return definitionName.name.toString(); | ||
} | ||
return definitionName.name; | ||
return definitionName.toString(); | ||
} | ||
exports.definitionNameToString = definitionNameToString; | ||
//# sourceMappingURL=DefinitionName.js.map |
@@ -1,11 +0,9 @@ | ||
import { DependencyResolver, IDIContainer, ResolvedType, ResolverName } from "./types"; | ||
interface INamedResolvers { | ||
[k: string]: DependencyResolver | any; | ||
} | ||
import { DependencyResolver, IDIContainer, NamedResolvers, ResolveDependencyType, ResolverName, ConvertToDefinedDependencies, NonStrictNamedResolvers } from "./types"; | ||
import ReferenceResolver from "./resolvers/ReferenceResolver"; | ||
/** | ||
* Dependency injection container | ||
*/ | ||
export default class DIContainer implements IDIContainer { | ||
export default class DIContainer<ContainerResolvers extends NamedResolvers = {}> implements IDIContainer { | ||
private resolvers; | ||
private resolved; | ||
private resolvedDependencies; | ||
/** | ||
@@ -16,3 +14,4 @@ * Resolves dependency by name | ||
*/ | ||
get<Custom = void, Name extends ResolverName = string>(dependencyName: Name, parentDeps?: string[]): ResolvedType<Custom, Name>; | ||
get<Name extends ResolverName<ContainerResolvers> = ResolverName<ContainerResolvers>>(dependencyName: Name, parentDeps?: string[]): ResolveDependencyType<ContainerResolvers, Name>; | ||
use<Name extends ResolverName<ContainerResolvers> = ResolverName<ContainerResolvers>>(dependencyName: Name): ReferenceResolver<ContainerResolvers, Name>; | ||
/** | ||
@@ -22,3 +21,3 @@ * Adds multiple dependency resolvers to the container | ||
*/ | ||
add(resolvers: INamedResolvers): void; | ||
add<N extends NonStrictNamedResolvers>(this: DIContainer<ContainerResolvers>, resolvers: N): asserts this is DIContainer<ContainerResolvers & ConvertToDefinedDependencies<N>>; | ||
/** | ||
@@ -34,3 +33,2 @@ * Adds single dependency definition to the container | ||
*/ | ||
export declare function resolveFunctionParameters(diContainer: IDIContainer, parameters?: Array<DependencyResolver<any> | any>, parentDeps?: string[]): any[]; | ||
export {}; | ||
export declare function resolveFunctionParameters(diContainer: DIContainer, parameters?: Array<DependencyResolver<any> | any>, parentDeps?: string[]): any[]; |
@@ -20,2 +20,3 @@ "use strict"; | ||
var DefinitionName_1 = require("./DefinitionName"); | ||
var ReferenceResolver_1 = __importDefault(require("./resolvers/ReferenceResolver")); | ||
/** | ||
@@ -27,3 +28,3 @@ * Dependency injection container | ||
this.resolvers = {}; | ||
this.resolved = {}; | ||
this.resolvedDependencies = {}; | ||
} | ||
@@ -35,3 +36,5 @@ /** | ||
*/ | ||
DIContainer.prototype.get = function (dependencyName, parentDeps) { | ||
DIContainer.prototype.get = function (dependencyName, | ||
// @todo: move parent deps to separate method | ||
parentDeps) { | ||
if (parentDeps === void 0) { parentDeps = []; } | ||
@@ -45,9 +48,13 @@ var name = (0, DefinitionName_1.definitionNameToString)(dependencyName); | ||
} | ||
if (this.resolved[name] !== undefined) { | ||
return this.resolved[name]; | ||
if (this.resolvedDependencies[name] !== undefined) { | ||
return this.resolvedDependencies[name]; | ||
} | ||
var definition = this.resolvers[name]; | ||
this.resolved[name] = definition.resolve(this, __spreadArray(__spreadArray([], parentDeps, true), [name], false)); | ||
return this.resolved[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); | ||
}; | ||
/** | ||
@@ -59,3 +66,3 @@ * Adds multiple dependency resolvers to the container | ||
var _this = this; | ||
Object.keys(resolvers).map(function (name) { | ||
Object.keys(resolvers).forEach(function (name) { | ||
_this.addResolver(name, resolvers[name]); | ||
@@ -86,3 +93,4 @@ }); | ||
if (parameter instanceof AbstractResolver_1.default) { | ||
return parameter.resolve(diContainer, parentDeps); | ||
parameter.setParentDependencies(parentDeps); | ||
return parameter.resolve(diContainer); | ||
} | ||
@@ -89,0 +97,0 @@ return parameter; |
@@ -22,3 +22,3 @@ "use strict"; | ||
function CircularDependencyError(name, path) { | ||
return _super.call(this, "Circular Dependency is detected. Dependency: \"" + name + "\", path: " + | ||
return _super.call(this, "Circular Dependency is detected. Dependency: \"".concat(name, "\", path: ") + | ||
path.join(" -> ") + | ||
@@ -33,3 +33,3 @@ ".") || this; | ||
function DependencyIsMissingError(name) { | ||
return _super.call(this, "Dependency with name " + name + " is not defined") || this; | ||
return _super.call(this, "Dependency with name ".concat(name, " is not defined")) || this; | ||
} | ||
@@ -50,3 +50,3 @@ return DependencyIsMissingError; | ||
function MethodIsMissingError(objectName, methodName) { | ||
return _super.call(this, methodName + " is not a member of " + objectName) || this; | ||
return _super.call(this, "".concat(methodName, " is not a member of ").concat(objectName)) || this; | ||
} | ||
@@ -53,0 +53,0 @@ return MethodIsMissingError; |
{ | ||
"name": "rsdi", | ||
"version": "2.1.1", | ||
"description": "Simple dependency injection container for JavaScript/TypeScript", | ||
"scripts": { | ||
"test": "jest", | ||
"format": "prettier 'src/**/*.ts' --write", | ||
"build": "tsc", | ||
"build-prod": "rm -Rf ./dist/* && NODE_ENV=production tsc --build tsconfig.json && cp README.md package.json dist/", | ||
"pre-commit": "lint-staged" | ||
}, | ||
"keywords": [ | ||
"dependency injection", | ||
"dependency", | ||
"injection", | ||
"ioc", | ||
"container", | ||
"javascript", | ||
"typescript" | ||
], | ||
"homepage": "https://github.com/radzserg/rsdi", | ||
"author": "Sergey Radzishevskii <radzserg@gmail.com>", | ||
"license": "ISC", | ||
"lint-staged": { | ||
"src/**/*.ts": "prettier --write --ignore-unknown" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^26.0.23", | ||
"husky": "^7.0.2", | ||
"jest": "^27.0.6", | ||
"lint-staged": "^11.2.0", | ||
"prettier": "^2.4.1", | ||
"ts-jest": "^27.0.3", | ||
"typescript": "^4.3.5" | ||
} | ||
"name": "rsdi", | ||
"version": "2.2.0", | ||
"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", | ||
"build-prod": "rm -Rf ./dist/* && NODE_ENV=production tsc --build tsconfig.json && cp README.md package.json dist/", | ||
"pre-commit": "lint-staged", | ||
"lint": "yarn eslint src/**/**.ts", | ||
"prettify": "prettier --write --ignore-unknown" | ||
}, | ||
"keywords": [ | ||
"dependency injection", | ||
"dependency", | ||
"injection", | ||
"ioc", | ||
"container", | ||
"javascript", | ||
"typescript" | ||
], | ||
"homepage": "https://github.com/radzserg/rsdi", | ||
"author": "Sergey Radzishevskii <radzserg@gmail.com>", | ||
"license": "ISC", | ||
"lint-staged": { | ||
"src/**/*.ts": [ | ||
"prettier --write --ignore-unknown", | ||
"eslint" | ||
] | ||
}, | ||
"devDependencies": { | ||
"@tsd/typescript": "^4.6.4", | ||
"@types/jest": "^27.5.0", | ||
"@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": "^28.0.3", | ||
"jest-runner-tsd": "^3.0.0", | ||
"lint-staged": "^11.2.0", | ||
"prettier": "^2.6.2", | ||
"ts-jest": "^28.0.1", | ||
"tsd": "^0.20.0", | ||
"typescript": "^4.6.4" | ||
}, | ||
"dependencies": {} | ||
} |
457
README.md
# RSDI - Dependency Injection Container | ||
Simple and powerful dependency injection container for JavaScript/TypeScript. | ||
Simple and powerful dependency injection container for with strong type checking system. | ||
# Getting Started | ||
- [Motivation](#motivation) | ||
- [Features](#features) | ||
- [When to use](#when-to-use) | ||
- [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) | ||
- Wiki | ||
- [Async factory resolver](./docs/async_factory_resolver.md) | ||
- [DI Container vs Context](./docs/context_vs_container.md) | ||
Given that you have classes and factories in your application | ||
## Motivation | ||
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. | ||
```typescript | ||
class CookieStorage {} | ||
class AuthStorage { | ||
constructor(storage: CookieStorage) {} | ||
@injectable() | ||
class Foo {} | ||
``` | ||
Why component Foo should know that it's injectable? | ||
Your business logic depends on a specific framework that is not part of your domain model and can change. | ||
More thoughts in a [dedicated article](https://radzserg.medium.com/https-medium-com-radzserg-dependency-injection-in-react-part-2-995e93b3327c) | ||
## Features | ||
- Simple but powerful | ||
- Does not requires decorators | ||
- Great types resolution | ||
- Works great with both javascript and typescript | ||
## When to use | ||
`RSDI` is most effective in complex applications. When the complexity of your application is high, it becomes necessary to | ||
break up huge components into smaller ones to control the complexity. You have components that use other components that | ||
use other components. You have application layers and a layer hierarchy. There is a need to transfer dependencies from | ||
the upper layers to the lower ones. | ||
You like and respect and use Dependency Injection and TDD. You have to use Dependency Injection in order to have proper | ||
unit tests. Tests that test only one module - class, component, function, but not integration with nested dependencies. | ||
## Architecture | ||
`RSDI` expects (but does not require) that you build all your dependencies into a dependency tree. Let's take a typical | ||
web application as an example. Given that your application is quite large and has many layers: | ||
- controllers (REST or graphql handlers) | ||
- domain model handlers (your domain models, various managers, use-cases etc) | ||
- DB repositories, | ||
- Low level services | ||
![architecture](https://github.com/radzserg/rsdi/raw/main/docs/RSDI_architecture.jpg "RSDI Architecture") | ||
An application always has an entry point, whether it is a web application or a CLI application. This is the only place where you | ||
should configure your dependency injection container. The top level components will then have the lower level components | ||
injected. | ||
# How to use | ||
Let's take a simple web application as an example. We will cut into a small part of the application that registers a | ||
new user. A real application will consist of dozens of components. The logic of the components will be much more | ||
complicated. This is just a demo. It's up to you to use classes or factory functions for the demonstration, and we'll | ||
use both. | ||
```typescript | ||
// sample web application components | ||
export function UserController( | ||
userRegistrator: UserRegistrator, | ||
userRepository: UserRepository | ||
) { | ||
return { | ||
async create(req: Request, res: Response) { | ||
const user = await userRegistrator.register(req.body); | ||
res.send(user); | ||
}, | ||
async list(req: Request) { | ||
const users = await userRepository.findAll(req.body); | ||
res.send(users); | ||
}, | ||
}; | ||
} | ||
class Logger {} | ||
class DummyLogger extends Logger {} | ||
function loggerFactory(container: IDIContainer): Logger { | ||
const env = container.get("ENV"); | ||
if (env === "test") { | ||
return new DummyLogger(); | ||
} | ||
return new Logger(); | ||
export class UserRegistrator { | ||
public constructor(public readonly userRepository: UserRepository) {} | ||
public async register(userData: SignupData) { | ||
// validate | ||
const user = this.userRepository.saveNewUser(userData); | ||
// send sign up email | ||
return user; | ||
} | ||
} | ||
function UsersRepoFactory(knex: Knex): UsersRepo { | ||
return { | ||
async findById(id: number) { | ||
await knex("users").where({ id }); | ||
}, | ||
}; | ||
export function MyDbProviderUserRepository(db: Knex): UserRepository { | ||
return { | ||
async saveNewUser(userAccountData: SignupData): Promise<void> { | ||
await this.db("insert").insert(userAccountData); | ||
}, | ||
}; | ||
} | ||
export function buildDbConnection(): Knex { | ||
return knex({ | ||
/* db credentials */ | ||
}); | ||
} | ||
``` | ||
Your DI container initialisation will include | ||
Now we need to configure the dependency injection container before use. Dependencies are declared and not really initiated | ||
until the application really needs them. Your DI container initialization function - `configureDI` will include: | ||
@@ -39,69 +128,64 @@ ```typescript | ||
export default function configureDI() { | ||
const container = new DIContainer(); | ||
container.add({ | ||
ENV: "test", // define raw value | ||
Storage: object(CookieStorage), // constructor without arguments | ||
AuthStorage: object(AuthStorage).construct( | ||
use(Storage) // refer to another dependency | ||
), | ||
knex: knex(), // keeps raw value | ||
Logger: factory(loggerFactory), // lazy function, will be resolved when it will be needed | ||
UsersRepo: func(UsersRepoFactory, use("knex")), | ||
}); | ||
return container; | ||
const container = new DIContainer(); | ||
container.add({ | ||
buildDbConnection: factory(() => { | ||
buildDbConnection(); | ||
}), | ||
[MyDbProviderUserRepository.name]: func( | ||
MyDbProviderUserRepository, | ||
use(buildDbConnection) | ||
), | ||
[UserRegistrator.name]: object(UserRegistrator).contrstruct( | ||
use(MyDbProviderUserRepository.name) | ||
), | ||
[UserController.name]: func( | ||
UserController, | ||
use(UserRegistrator.name), | ||
use(MyDbProviderUserRepository.name) | ||
), | ||
}); | ||
return container; | ||
} | ||
``` | ||
The entry point of your application will include | ||
**All resolvers are resolved only once and their result persists over the life of the container.** | ||
Let's map our web application routes to configured controllers | ||
```typescript | ||
const container = configureDI(); | ||
const env: string = container.get("ENV"); // test | ||
const authStorage: AuthStorage = container.get(AuthStorage); // object of AuthStorage | ||
const logger: Logger = container.get(loggerFactory); // object of DummyLogger | ||
// configure Express router | ||
export default function configureRouter( | ||
app: core.Express, | ||
diContainer: IDIContainer | ||
) { | ||
const usersController = diContainer.get(UsersController); | ||
app | ||
.route("/users") | ||
.get(usersController.list.bind(usersController)) | ||
.post(usersController.create.bind(usersController)); | ||
} | ||
``` | ||
**All resolvers are resolved only once and their result persists over the life of the container.** | ||
Add `configureDI` in the entry point of your application. | ||
- [Features](#features) | ||
- [Motivation](#motivation) | ||
- [Usage](#usage) | ||
- [Raw values](#raw-values) | ||
- [Object resolver](#object-resolver) | ||
- [Function resolver](#function-resolver) | ||
- [Factory resolver](#factory-resolver) | ||
- [Advanced Usage](#advanced-usage) | ||
- [Typescript type resolution](#typescript-type-resolution) | ||
- [Dependency declaration](#dependency-declaration) | ||
- [Async factory resolver](#async-factory-resolver) | ||
```typescript | ||
// express.ts | ||
const app = express(); | ||
## Features | ||
const diContainer = configureDI(); | ||
configureRouter(app, diContainer); | ||
- Simple but powerful | ||
- Does not requires decorators | ||
- Great types resolution | ||
- Works great with both javascript and typescript | ||
## Motivation | ||
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. | ||
```typescript | ||
@injectable() | ||
class Foo {} | ||
app.listen(8000, () => { | ||
console.log(`⚡️[server]: Server is running`); | ||
}); | ||
``` | ||
Why component Foo should know that it's injectable? | ||
The complete web application example can be found [here](https://radzserg.medium.com/dependency-injection-in-express-application-dd85295694ab) | ||
Your business logic depends on a specific framework that is not part of your domain model and can change. | ||
## Dependency Resolvers | ||
More thoughts in a [dedicated article](https://radzserg.medium.com/https-medium-com-radzserg-dependency-injection-in-react-part-2-995e93b3327c) | ||
### Raw values resolver | ||
## Usage | ||
Dependencies are set as raw values. Container keeps and return raw values. | ||
### Raw values | ||
Dependencies are set as raw values. No lazy initialisation happens. Container keeps and return raw values. | ||
```typescript | ||
@@ -112,5 +196,5 @@ import DIContainer from "rsdi"; | ||
container.add({ | ||
ENV: "PRODUCTION", | ||
HTTP_PORT: 3000, | ||
storage: new CookieStorage(), | ||
ENV: "PRODUCTION", | ||
HTTP_PORT: 3000, | ||
storage: new CookieStorage(), | ||
}); | ||
@@ -131,9 +215,8 @@ const env: string = container.get("ENV"); | ||
```typescript | ||
// test class | ||
class ControllerContainer { | ||
constructor(authStorage: AuthStorage, logger: Logger) {} | ||
constructor(authStorage: AuthStorage, logger: Logger) {} | ||
add(controller: Controller) { | ||
this.controllers.push(controller); | ||
} | ||
add(controller: Controller) { | ||
this.controllers.push(controller); | ||
} | ||
} | ||
@@ -144,12 +227,12 @@ | ||
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)), | ||
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)), | ||
}); | ||
@@ -164,7 +247,7 @@ ``` | ||
function UsersRepoFactory(knex: Knex): UsersRepo { | ||
return { | ||
async findById(id: number) { | ||
await knex("users").where({ id }); | ||
}, | ||
}; | ||
return { | ||
async findById(id: number) { | ||
await knex("users").where({ id }); | ||
}, | ||
}; | ||
} | ||
@@ -174,4 +257,4 @@ | ||
container.add({ | ||
DBConnection: knex(/* ... */), | ||
UsersRepoFactory: func(UsersRepoFactory, use("DBConnection")), | ||
DBConnection: knex(/* ... */), | ||
UsersRepoFactory: func(UsersRepoFactory, use("DBConnection")), | ||
}); | ||
@@ -185,4 +268,4 @@ | ||
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. So you can | ||
resolve other dependencies inside the factory function. | ||
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. | ||
@@ -192,135 +275,135 @@ ```typescript | ||
container.add({ | ||
BrowserHistory: factory(configureHistory), | ||
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 = createBrowserHistory(); | ||
const env = container.get("ENV"); | ||
if (env === "production") { | ||
// do what you need | ||
} | ||
return history; | ||
} | ||
const history = container.get<History>("BrowserHistory"); | ||
const history = container.get("BrowserHistory"); | ||
``` | ||
## Advanced Usage | ||
## Typescript type resolution | ||
### Typescript type resolution | ||
`container.get` - return type based on declaration. | ||
`container.get` and `use` helper resolve type based on following convention: | ||
- if given name is class - instance of a class | ||
- if given name is function - return type of function | ||
- if custom generic type is provided - custom type | ||
- otherwise - any | ||
```typescript | ||
const container: DIContainer = new DIContainer(); | ||
container.add({ | ||
Bar: new Bar(), | ||
Foo: new Bar(), // fake foo example | ||
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 | ||
}), | ||
}); | ||
let bar: Bar = container.get(Bar); // types defined based on a given type Bar | ||
let foo: Foo = container.get(Foo); // you can trick TS compiler (it's your responsibility) | ||
let foo2: Foo = container.get<Foo>("Foo"); // custom generic type is provided | ||
let foo3: Foo = container.get("Foo"); // any type | ||
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 | ||
``` | ||
Example: `use` defines type for a class constructor. | ||
`contrainer.use` - allows to reference declared dependency with respect to types. | ||
```typescript | ||
class Foo { | ||
constructor(private readonly bar: Bar) {} | ||
export class Foo { | ||
constructor(name: string, bar: Bar) {} | ||
} | ||
const container: DIContainer = new DIContainer(); | ||
container.add({ | ||
Bar: new Bar(), | ||
Foo: object(Foo).construct(use(Bar)), // constuct method checks type and use return Bar type | ||
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")), | ||
}); | ||
``` | ||
Example: `container.get` resolve type based on a given factory return type. | ||
```typescript | ||
function myFactory() { | ||
return { a: 123 }; | ||
} | ||
container.add({ | ||
myFactory: factory((container: IDIContainer) => { | ||
return myFactory(); | ||
}), | ||
foo: new ObjectResolver(Foo).construct("foo", container.use("bar")), | ||
}); | ||
let { a } = container.get(myFactory); | ||
``` | ||
### Dependency declaration | ||
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. | ||
RSDI resolves dependencies on a given type. It can be string or function. In the simplest case, you can use strings. | ||
`use` helper | ||
```typescript | ||
container.add({ | ||
Foo: new Foo(), | ||
}); | ||
const foo = container.get<Foo>("Foo"); | ||
import { use } from "rsdi"; | ||
``` | ||
In order to avoid magic strings you can operate with types. | ||
`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 | ||
const foo = container.get(Foo); | ||
``` | ||
class Foo { | ||
constructor(private readonly bar: Bar) {} | ||
} | ||
RSDI uses `Foo.name` behind the scene that equals to "Foo". Remember that this approach will not work for uglified code. | ||
You can also rename the function Foo => Buzz, and forget to rename the declaration. From that perspective you can | ||
declare dependencies this way. | ||
function returnBoolean() { | ||
return true; | ||
} | ||
```typescript | ||
const container: DIContainer = new DIContainer(); | ||
container.add({ | ||
[Foo.name]: new Foo(), | ||
[MyFactory.name]: MyFactory(), | ||
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 | ||
}); | ||
const foo = container.get(Foo); | ||
const buzz = container.get(MyFactory); | ||
``` | ||
### Async factory resolver | ||
## Dependency declaration | ||
RSDI intentionally does not provide the ability to resolve asynchronous dependencies. The container works with | ||
resources. All resources will be used sooner or later. The lazy initialization feature won't be of much benefit | ||
in this case. At the same time, mixing synchronous and asynchronous resolution will cause confusion primarily for | ||
the consumers. | ||
**Use string names** | ||
The following approach will work in most scenarios. | ||
```typescript | ||
// UserRepository.ts | ||
class UserRepository { | ||
public constructor(private readonly dbConnection: any) {} // some ORM that requires opened connection | ||
const container: DIContainer = new DIContainer(); | ||
async findUser() { | ||
return await this.dbConnection.find(/*...params...*/); | ||
} | ||
} | ||
container.add({ | ||
bar: new ObjectResolver(Bar), | ||
key1: new RawValueResolver("value1"), | ||
}); | ||
// configureDI.ts | ||
import { createConnections } from "my-orm-library"; | ||
import DIContainer, { factory, use, IDIContainer } from "rsdi"; | ||
container.add({ | ||
foo: new ObjectResolver(Foo).construct("foo", container.use("bar")), | ||
}); | ||
``` | ||
async function configureDI() { | ||
// initialize async factories before DI container initialisation | ||
const dbConnection = await createConnections(); | ||
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. | ||
const container = new DIContainer(); | ||
container.addDefinitions({ | ||
DbConnection: dbConnection, | ||
UserRepository: object(UserRepository).construct(use("DbConnection")), | ||
}); | ||
return container; | ||
} | ||
// main.ts | ||
const diContainer = await configureDI(); | ||
const userRepository = diContainer.get<UserRepository>("UserRepository"); | ||
```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); | ||
``` |
@@ -1,8 +0,11 @@ | ||
import { DependencyResolver, IDIContainer } from "../types"; | ||
import { DependencyResolver } from "../types"; | ||
import DIContainer from "../DIContainer"; | ||
/** | ||
* Keep AbstractResolver so we can use `if (dep instanceof AbstractResolver) ` checks | ||
* Keep AbstractResolver, so we can use `if (dep instanceof AbstractResolver) ` checks | ||
*/ | ||
declare abstract class AbstractResolver<T = any> implements DependencyResolver<T> { | ||
abstract resolve: (container: IDIContainer, parentDeps?: string[]) => T; | ||
protected parentDeps: string[]; | ||
abstract resolve: (container: DIContainer) => T; | ||
setParentDependencies(parentDeps: string[]): void; | ||
} | ||
export default AbstractResolver; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* Keep AbstractResolver so we can use `if (dep instanceof AbstractResolver) ` checks | ||
* Keep AbstractResolver, so we can use `if (dep instanceof AbstractResolver) ` checks | ||
*/ | ||
var AbstractResolver = /** @class */ (function () { | ||
function AbstractResolver() { | ||
this.parentDeps = []; | ||
} | ||
AbstractResolver.prototype.setParentDependencies = function (parentDeps) { | ||
this.parentDeps = parentDeps; | ||
}; | ||
return AbstractResolver; | ||
@@ -10,0 +14,0 @@ }()); |
import AbstractResolver from "./AbstractResolver"; | ||
import { IDIContainer } from "../types"; | ||
import DIContainer from "../DIContainer"; | ||
export declare type Factory = (container: IDIContainer) => any; | ||
@@ -10,3 +11,3 @@ /** | ||
constructor(factory: T); | ||
resolve: (container: IDIContainer) => ReturnType<T>; | ||
resolve: (container: DIContainer) => ReturnType<T>; | ||
} |
import AbstractResolver from "./AbstractResolver"; | ||
import { IDIContainer, WrapWithResolver } from "../types"; | ||
import { WrapWithResolver } from "../types"; | ||
import DIContainer from "../DIContainer"; | ||
/** | ||
@@ -11,3 +12,3 @@ * FunctionResolver - allows to use custom function with specified parameters, where parameters are references to | ||
constructor(func: T, ...parameters: T extends (...args: infer ArgTypes) => any ? WrapWithResolver<ArgTypes> : never); | ||
resolve: (container: IDIContainer, parentDeps?: string[] | undefined) => ReturnType<T>; | ||
resolve: (container: DIContainer) => ReturnType<T>; | ||
} |
@@ -37,4 +37,4 @@ "use strict"; | ||
_this.func = func; | ||
_this.resolve = function (container, parentDeps) { | ||
var parameters = (0, DIContainer_1.resolveFunctionParameters)(container, _this.parameters, parentDeps); | ||
_this.resolve = function (container) { | ||
var parameters = (0, DIContainer_1.resolveFunctionParameters)(container, _this.parameters, _this.parentDeps); | ||
return _this.func.apply(_this, parameters); | ||
@@ -41,0 +41,0 @@ }; |
import AbstractResolver from "./AbstractResolver"; | ||
import { ClassOf, DependencyResolver, IDIContainer, MethodArgs, WrapWithResolver } from "../types"; | ||
import { ClassOf, DependencyResolver, MethodArgs, WrapWithResolver } from "../types"; | ||
import DIContainer from "../DIContainer"; | ||
/** | ||
@@ -27,3 +28,3 @@ * ObjectDefinition creates objects from the provided class. | ||
method<MethodName extends keyof InstanceType<T>>(methodName: MethodName, ...args: MethodArgs<T, MethodName>): ObjectResolver<T>; | ||
resolve: (diContainer: IDIContainer, parentDeps?: string[]) => InstanceType<T>; | ||
resolve: (diContainer: DIContainer) => InstanceType<T>; | ||
} |
@@ -43,6 +43,5 @@ "use strict"; | ||
_this.methods = []; | ||
_this.resolve = function (diContainer, parentDeps) { | ||
_this.resolve = function (diContainer) { | ||
var _a; | ||
if (parentDeps === void 0) { parentDeps = []; } | ||
var constructorParameters = (0, DIContainer_1.resolveFunctionParameters)(diContainer, _this.deps, parentDeps); | ||
var constructorParameters = (0, DIContainer_1.resolveFunctionParameters)(diContainer, _this.deps, _this.parentDeps); | ||
var object = new ((_a = _this.constructorFunction).bind.apply(_a, __spreadArray([void 0], constructorParameters, false)))(); | ||
@@ -54,3 +53,3 @@ _this.methods.forEach(function (method) { | ||
} | ||
var resolvedArgs = (0, DIContainer_1.resolveFunctionParameters)(diContainer, args, parentDeps); | ||
var resolvedArgs = (0, DIContainer_1.resolveFunctionParameters)(diContainer, args, _this.parentDeps); | ||
object[methodName].apply(object, resolvedArgs); | ||
@@ -57,0 +56,0 @@ }); |
import AbstractResolver from "./AbstractResolver"; | ||
import { IDIContainer } from "../types"; | ||
import DIContainer from "../DIContainer"; | ||
import { AnyNamedResolvers, NamedResolvers, ResolveDependencyType, ResolverName } from "../types"; | ||
/** | ||
* Refers to existing definition. i.e. definition with provided name must exists in DIContainer | ||
*/ | ||
export default class ReferenceResolver<T = any> extends AbstractResolver<T> { | ||
export default class ReferenceResolver<ExistingNamedResolvers extends NamedResolvers = AnyNamedResolvers, Name extends ResolverName<ExistingNamedResolvers> = ResolverName<ExistingNamedResolvers>> extends AbstractResolver<ResolveDependencyType<ExistingNamedResolvers, Name>> { | ||
private readonly existingDefinitionName; | ||
constructor(existingDefinitionName: string); | ||
resolve: <Y extends T>(container: IDIContainer, parentDeps?: string[]) => Y; | ||
constructor(existingDefinitionName: Name); | ||
resolve: (container: DIContainer<ExistingNamedResolvers>) => ResolveDependencyType<ExistingNamedResolvers, Name>; | ||
} |
@@ -29,5 +29,4 @@ "use strict"; | ||
var _this = _super.call(this) || this; | ||
_this.resolve = function (container, parentDeps) { | ||
if (parentDeps === void 0) { parentDeps = []; } | ||
return container.get(_this.existingDefinitionName, parentDeps); | ||
_this.resolve = function (container) { | ||
return container.get(_this.existingDefinitionName, _this.parentDeps); | ||
}; | ||
@@ -34,0 +33,0 @@ _this.existingDefinitionName = existingDefinitionName; |
import ObjectResolver from "./resolvers/ObjectResolver"; | ||
import RawValueResolver from "./resolvers/RawValueResolver"; | ||
import ReferenceResolver from "./resolvers/ReferenceResolver"; | ||
import FactoryResolver, { Factory } from "./resolvers/FactoryResolver"; | ||
import { ClassOf, ResolverName, WrapWithResolver } from "./types"; | ||
import { ClassOf, DependencyResolver, ResolverName, ResolveUsingSelfType, WrapWithResolver } from "./types"; | ||
import FunctionResolver from "./resolvers/FunctionResolver"; | ||
@@ -18,13 +17,9 @@ /** | ||
/** | ||
* Defines the type of resolved dependency | ||
* - if Custom type is given - it will be returned | ||
* - if name of Class is provided - instance type will be returned | ||
* - if function is provided - function return type will be returned | ||
*/ | ||
declare type ResolvedType<Custom, Name extends ResolverName> = Custom extends void ? Name extends string ? any : Name extends ClassOf<any> ? InstanceType<Name> : Name extends (...args: any) => infer FT ? FT : never : Custom; | ||
/** | ||
* Refers to existing definition. i.e. definition with provided name must exists in DIContainer | ||
* Refers to existing definition. i.e. definition with provided name must exist in DIContainer. | ||
* Fallback to self type | ||
* | ||
* @param definitionName | ||
* | ||
*/ | ||
export declare function diUse<Custom = void, Name extends ResolverName = ResolverName>(definitionName: Name): ReferenceResolver<ResolvedType<Custom, Name>>; | ||
export declare function diUse<Custom = void, Name extends ResolverName = ResolverName>(definitionName: Name): Custom extends void ? DependencyResolver<ResolveUsingSelfType<Name>> : DependencyResolver<Custom>; | ||
/** | ||
@@ -42,2 +37,1 @@ * FactoryResolver - allows to use custom function to build dependency | ||
export declare function diFunc<T extends (...args: any) => any>(func: T, ...parameters: T extends (...args: infer ArgTypes) => any ? WrapWithResolver<ArgTypes> : never): FunctionResolver<T>; | ||
export {}; |
@@ -22,3 +22,2 @@ "use strict"; | ||
var FunctionResolver_1 = __importDefault(require("./resolvers/FunctionResolver")); | ||
// shorthands for Definition classes | ||
/** | ||
@@ -41,7 +40,11 @@ * ObjectDefinition creates objects from the provided class. | ||
/** | ||
* Refers to existing definition. i.e. definition with provided name must exists in DIContainer | ||
* Refers to existing definition. i.e. definition with provided name must exist in DIContainer. | ||
* Fallback to self type | ||
* | ||
* @param definitionName | ||
* | ||
*/ | ||
function diUse(definitionName) { | ||
return new ReferenceResolver_1.default((0, DefinitionName_1.definitionNameToString)(definitionName)); | ||
var dependencyName = (0, DefinitionName_1.definitionNameToString)(definitionName); | ||
return new ReferenceResolver_1.default(dependencyName); | ||
} | ||
@@ -48,0 +51,0 @@ exports.diUse = diUse; |
@@ -0,28 +1,87 @@ | ||
import ObjectResolver from "./resolvers/ObjectResolver"; | ||
import FunctionResolver from "./resolvers/FunctionResolver"; | ||
import DIContainer from "./DIContainer"; | ||
import FactoryResolver from "./resolvers/FactoryResolver"; | ||
export declare type DependencyResolver<T extends any = any> = { | ||
setParentDependencies: (parentDeps: string[]) => void; | ||
resolve: (container: DIContainer) => T; | ||
}; | ||
export declare type AnyNamedResolvers = { | ||
[k: string]: DependencyResolver; | ||
}; | ||
/** | ||
* Dependency injection container interface to expose | ||
* Resolvers map | ||
* { | ||
* a: new FunctionResolver(() => 123), | ||
* b: new RawValueResolver("stringValue"), | ||
* // and also can contain raw value declarations | ||
* c: "StringValue" | ||
* } | ||
*/ | ||
export interface IDIContainer { | ||
get: <Custom, Name extends ResolverName = ResolverName>(dependencyName: Name) => ResolvedType<Custom, Name>; | ||
} | ||
export declare type DependencyResolver<T extends any = unknown> = { | ||
resolve: (container: IDIContainer, parentDeps?: string[]) => T; | ||
export declare type NonStrictNamedResolvers = { | ||
[k: string]: DependencyResolver | any; | ||
}; | ||
/** | ||
* Resolvers map | ||
* { | ||
* a: new FunctionResolver(() => 123), | ||
* b: new RawValueResolver("stringValue"), | ||
* } | ||
*/ | ||
export declare type NamedResolvers = { | ||
[k: string]: DependencyResolver; | ||
}; | ||
export declare type ConvertToDefinedDependencies<T = NonStrictNamedResolvers> = { | ||
[K in keyof T]: T[K] extends DependencyResolver ? T[K] : DependencyResolver<T[K]>; | ||
}; | ||
export declare type WrapWithResolver<T extends any[]> = { | ||
[K in keyof T]: T[K] | DependencyResolver<T[K]>; | ||
}; | ||
export declare type ResolverName = string | { | ||
name: string; | ||
}; | ||
export declare type ParametersWithResolver<T extends (...args: any) => any> = T extends (...args: infer P) => any ? WrapWithResolver<P> : never; | ||
declare type Resolve<N extends DependencyResolver> = N extends { | ||
resolve(...args: any[]): infer R; | ||
} ? R : never; | ||
export interface ClassOf<C extends Object> { | ||
new (...args: any[]): C; | ||
} | ||
export declare type FetchClassesFromContainerResolvers<ContainerResolvers extends NamedResolvers = AnyNamedResolvers> = { | ||
[P in keyof ContainerResolvers]: ContainerResolvers[P] extends ObjectResolver<ClassOf<any>> ? ContainerResolvers[P] extends ObjectResolver<infer X> ? X : never : never; | ||
}[keyof ContainerResolvers]; | ||
export declare type FetchFunctionsFromContainerResolvers<ContainerResolvers extends NamedResolvers = AnyNamedResolvers> = { | ||
[P in keyof ContainerResolvers]: ContainerResolvers[P] extends FunctionResolver<infer FT> ? FT : never; | ||
}[keyof ContainerResolvers]; | ||
export declare type FetchFactoriesFromContainerResolvers<ContainerResolvers extends NamedResolvers = AnyNamedResolvers> = { | ||
[P in keyof ContainerResolvers]: ContainerResolvers[P] extends FactoryResolver<infer FT> ? FT : never; | ||
}[keyof ContainerResolvers]; | ||
export declare type ResolverName<ContainerResolvers extends NamedResolvers = AnyNamedResolvers> = keyof ContainerResolvers | { | ||
name: keyof ContainerResolvers; | ||
} | FetchClassesFromContainerResolvers<ContainerResolvers> | FetchFunctionsFromContainerResolvers<ContainerResolvers> | FetchFactoriesFromContainerResolvers<ContainerResolvers>; | ||
export declare type ParametersWithResolver<T extends (...args: any) => any> = T extends (...args: infer P) => any ? WrapWithResolver<P> : never; | ||
export declare type MethodArgs<T extends ClassOf<any>, K extends keyof InstanceType<T>> = ParametersWithResolver<InstanceType<T>[K]>; | ||
/** | ||
* Defines the type of resolved dependency | ||
* - if name of Class is provided - instance type will be returned | ||
* - if function is provided - function return type will be returned | ||
* - if Custom type is provided - it will be returned | ||
* Resolves types using self type | ||
* - if T is a class - instance type will be returned | ||
* - if T is a function - function return type will be returned | ||
* - else any | ||
*/ | ||
export declare type ResolvedType<Custom = void, Name extends ResolverName = ResolverName> = Name extends ClassOf<any> ? InstanceType<Name> : Name extends (...args: any) => infer FT ? FT : Custom extends void ? any : Custom; | ||
export declare type ResolveUsingSelfType<T> = T extends ClassOf<any> ? InstanceType<T> : T extends (...args: any) => infer FT ? FT : never; | ||
/** | ||
* Tries to resolve type based on provided name and accumulated | ||
* dependencies in the NamedResolvers | ||
*/ | ||
export declare type TryResolveUsingExistingResolvers<Name, ExistingNamedResolvers extends NamedResolvers> = Name extends keyof NamedResolvers ? ExistingNamedResolvers[Name] extends DependencyResolver ? Resolve<ExistingNamedResolvers[Name]> : never : never; | ||
/** | ||
* Resolve dependency type | ||
* - tries to resolve using already defined dependencies | ||
* - reties to resolve using custom type, | ||
* substituted by TS - const a: MyType = container.get("name") | ||
* or explicitly provided const a = container.get<MyType>("name") | ||
* - tries to resolve using self type. If a class or function is provided | ||
* instance of a class or function return type will be returned | ||
*/ | ||
export declare type ResolveDependencyType<ExistingNamedResolvers extends NamedResolvers = NamedResolvers, Name extends ResolverName<ExistingNamedResolvers> = ResolverName<ExistingNamedResolvers>> = TryResolveUsingExistingResolvers<Name, ExistingNamedResolvers> extends never ? ResolveUsingSelfType<Name> : TryResolveUsingExistingResolvers<Name, ExistingNamedResolvers>; | ||
export interface IDIContainer { | ||
get: (dependencyName: string | { | ||
name: string; | ||
}) => any; | ||
} | ||
export {}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
57858
791
402
17