Comparing version 2.0.2 to 2.1.0
import { IFactories, VoidFn } from './types'; | ||
declare const createInstanceFactory: <C extends object>(factories: IFactories<C>, instances: Map<keyof C, any>, stack?: import("./unique-stack").IUniqueStack<keyof C>, initializers?: VoidFn[]) => <N extends keyof C>(container: C, name: N) => C[N]; | ||
declare const $INIT: unique symbol; | ||
declare const createInstanceFactory: <C extends object>(factories: IFactories<C>, instances?: Map<keyof C, any>, stack?: import("./unique-stack").IUniqueStack<typeof $INIT | keyof C>, initializers?: VoidFn[]) => <N extends keyof C>(container: C, name: N) => C[N]; | ||
export default createInstanceFactory; |
import UniqueStack from './unique-stack'; | ||
const call = (fn) => fn(); | ||
const createInstanceFactory = (factories, instances, stack = UniqueStack(), initializers = []) => (container, name) => { | ||
const $INIT = Symbol('$INIT'); | ||
const createInstanceFactory = (factories, instances = new Map(), stack = UniqueStack(), initializers = []) => (container, name) => { | ||
if (instances.has(name)) | ||
return instances.get(name); | ||
if (stack.push(name)[0] != null) { | ||
@@ -21,4 +24,7 @@ throw new Error('Cyclic dependencies couldn\'t be resolved.'); | ||
if (stack.size === 0) { | ||
stack.push($INIT); | ||
initializers.forEach(call); | ||
initializers = []; | ||
instances.clear(); | ||
stack.pop($INIT); | ||
} | ||
@@ -25,0 +31,0 @@ return instance; |
import { IFactories, IPureFactories } from './types'; | ||
export declare const isReady: <C extends object>(container: C, name: keyof C) => boolean; | ||
export declare const prepareAll: <C extends object>(container: C) => C; | ||
export declare const releaseAll: <C extends object>(container: C) => void; | ||
export declare const factoriesFrom: <C extends object, N extends keyof C = keyof C>(container: C, names?: N[]) => IPureFactories<Pick<C, N>>; | ||
declare function diContainer<C extends object>(factories: IFactories<C>): C; | ||
export default diContainer; |
import createInstanceFactory from './create-instance'; | ||
import allNames from './utils/all-names'; | ||
import assertExists from './utils/assert-exists'; | ||
import mapObject from './utils/map-object'; | ||
const $INSTANCES = Symbol('TRUE-DI::INSTANCES'); | ||
const itemsOf = (container) => assertExists(container[$INSTANCES], 'Argument is not a container'); | ||
export const isReady = (container, name) => itemsOf(container).has(name); | ||
export const prepareAll = (container) => mapObject(container, name => container[name]); | ||
export const releaseAll = (container) => { | ||
itemsOf(container).clear(); | ||
}; | ||
export const factoriesFrom = (container, names) => mapObject(container, name => () => container[name], names); | ||
function diContainer(factories) { | ||
const instances = new Map(); | ||
const createInstance = createInstanceFactory(factories, instances); | ||
const createInstance = createInstanceFactory(factories); | ||
const container = allNames(factories).reduce((containerObj, name) => Object.defineProperty(containerObj, name, { | ||
configurable: false, | ||
enumerable: Object.getOwnPropertyDescriptor(factories, name).enumerable, | ||
get: () => (instances.has(name) ? instances.get(name) : createInstance(container, name)), | ||
set: (value) => { | ||
if (value != null) { | ||
throw new Error('Container does\'t allow to replace items'); | ||
} | ||
instances.delete(name); | ||
}, | ||
}), Object.create(Object.create(null, { | ||
[$INSTANCES]: { | ||
configurable: false, writable: false, enumerable: false, value: instances, | ||
}, | ||
}))); | ||
get: () => createInstance(container, name), | ||
}), Object.create(null)); | ||
return container; | ||
} | ||
export default diContainer; |
export declare type IInstanceInitializer<IContainer, N extends keyof IContainer> = (instance: IContainer[N], container: IContainer) => void; | ||
export declare type VoidFn = () => void; | ||
export declare type IFactory<IContainer, N extends keyof IContainer> = (container: IContainer) => IContainer[N]; | ||
export declare type IFactoryTuple<IContainer, N extends keyof IContainer> = [IFactory<IContainer, N>, IInstanceInitializer<IContainer, N>]; | ||
export declare type IFactoryTuple<IContainer, N extends keyof IContainer> = [ | ||
IFactory<IContainer, N>, | ||
IInstanceInitializer<IContainer, N> | ||
]; | ||
export declare type IFactories<IContainer extends object> = { | ||
@@ -6,0 +9,0 @@ [name in keyof IContainer]: IFactory<IContainer, name> | IFactoryTuple<IContainer, name>; |
import { IFactories, VoidFn } from './types'; | ||
declare const createInstanceFactory: <C extends object>(factories: IFactories<C>, instances: Map<keyof C, any>, stack?: import("./unique-stack").IUniqueStack<keyof C>, initializers?: VoidFn[]) => <N extends keyof C>(container: C, name: N) => C[N]; | ||
declare const $INIT: unique symbol; | ||
declare const createInstanceFactory: <C extends object>(factories: IFactories<C>, instances?: Map<keyof C, any>, stack?: import("./unique-stack").IUniqueStack<typeof $INIT | keyof C>, initializers?: VoidFn[]) => <N extends keyof C>(container: C, name: N) => C[N]; | ||
export default createInstanceFactory; |
@@ -5,6 +5,10 @@ "use strict"; | ||
var call = function (fn) { return fn(); }; | ||
var $INIT = Symbol('$INIT'); | ||
var createInstanceFactory = function (factories, instances, stack, initializers) { | ||
if (instances === void 0) { instances = new Map(); } | ||
if (stack === void 0) { stack = unique_stack_1.default(); } | ||
if (initializers === void 0) { initializers = []; } | ||
return function (container, name) { | ||
if (instances.has(name)) | ||
return instances.get(name); | ||
if (stack.push(name)[0] != null) { | ||
@@ -27,4 +31,7 @@ throw new Error('Cyclic dependencies couldn\'t be resolved.'); | ||
if (stack.size === 0) { | ||
stack.push($INIT); | ||
initializers.forEach(call); | ||
initializers = []; | ||
instances.clear(); | ||
stack.pop($INIT); | ||
} | ||
@@ -31,0 +38,0 @@ return instance; |
import { IFactories, IPureFactories } from './types'; | ||
export declare const isReady: <C extends object>(container: C, name: keyof C) => boolean; | ||
export declare const prepareAll: <C extends object>(container: C) => C; | ||
export declare const releaseAll: <C extends object>(container: C) => void; | ||
export declare const factoriesFrom: <C extends object, N extends keyof C = keyof C>(container: C, names?: N[]) => IPureFactories<Pick<C, N>>; | ||
declare function diContainer<C extends object>(factories: IFactories<C>): C; | ||
export default diContainer; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.factoriesFrom = exports.releaseAll = exports.prepareAll = exports.isReady = void 0; | ||
exports.factoriesFrom = exports.prepareAll = void 0; | ||
var create_instance_1 = require("./create-instance"); | ||
var all_names_1 = require("./utils/all-names"); | ||
var assert_exists_1 = require("./utils/assert-exists"); | ||
var map_object_1 = require("./utils/map-object"); | ||
var $INSTANCES = Symbol('TRUE-DI::INSTANCES'); | ||
var itemsOf = function (container) { | ||
return assert_exists_1.default(container[$INSTANCES], 'Argument is not a container'); | ||
}; | ||
exports.isReady = function (container, name) { | ||
return itemsOf(container).has(name); | ||
}; | ||
exports.prepareAll = function (container) { | ||
return map_object_1.default(container, function (name) { return container[name]; }); | ||
}; | ||
exports.releaseAll = function (container) { | ||
itemsOf(container).clear(); | ||
}; | ||
exports.factoriesFrom = function (container, names) { | ||
@@ -25,5 +14,3 @@ return map_object_1.default(container, function (name) { return function () { return container[name]; }; }, names); | ||
function diContainer(factories) { | ||
var _a; | ||
var instances = new Map(); | ||
var createInstance = create_instance_1.default(factories, instances); | ||
var createInstance = create_instance_1.default(factories); | ||
var container = all_names_1.default(factories).reduce(function (containerObj, name) { | ||
@@ -33,17 +20,7 @@ return Object.defineProperty(containerObj, name, { | ||
enumerable: Object.getOwnPropertyDescriptor(factories, name).enumerable, | ||
get: function () { return (instances.has(name) ? instances.get(name) : createInstance(container, name)); }, | ||
set: function (value) { | ||
if (value != null) { | ||
throw new Error('Container does\'t allow to replace items'); | ||
} | ||
instances.delete(name); | ||
}, | ||
get: function () { return createInstance(container, name); }, | ||
}); | ||
}, Object.create(Object.create(null, (_a = {}, | ||
_a[$INSTANCES] = { | ||
configurable: false, writable: false, enumerable: false, value: instances, | ||
}, | ||
_a)))); | ||
}, Object.create(null)); | ||
return container; | ||
} | ||
exports.default = diContainer; |
@@ -10,3 +10,3 @@ "use strict"; | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
@@ -13,0 +13,0 @@ Object.defineProperty(exports, "__esModule", { value: true }); |
export declare type IInstanceInitializer<IContainer, N extends keyof IContainer> = (instance: IContainer[N], container: IContainer) => void; | ||
export declare type VoidFn = () => void; | ||
export declare type IFactory<IContainer, N extends keyof IContainer> = (container: IContainer) => IContainer[N]; | ||
export declare type IFactoryTuple<IContainer, N extends keyof IContainer> = [IFactory<IContainer, N>, IInstanceInitializer<IContainer, N>]; | ||
export declare type IFactoryTuple<IContainer, N extends keyof IContainer> = [ | ||
IFactory<IContainer, N>, | ||
IInstanceInitializer<IContainer, N> | ||
]; | ||
export declare type IFactories<IContainer extends object> = { | ||
@@ -6,0 +9,0 @@ [name in keyof IContainer]: IFactory<IContainer, name> | IFactoryTuple<IContainer, name>; |
@@ -10,3 +10,3 @@ "use strict"; | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
@@ -13,0 +13,0 @@ Object.defineProperty(exports, "__esModule", { value: true }); |
{ | ||
"name": "true-di", | ||
"version": "2.0.2", | ||
"version": "2.1.0", | ||
"description": "Simple Dependency Injection solution for TypeScript and Javascript", | ||
@@ -5,0 +5,0 @@ "main": "./lib/index.js", |
145
README.md
# true-di | ||
`true-di` is a Simple Dependency Injection Container for TypeScript and JavaScript | ||
Framework Agnostic, Zero Dependency, Isomorphic & Minimalistic **Dependency Injection Container** for TypeScript and JavaScript projects | ||
@@ -10,3 +10,3 @@ [![Build Status](https://travis-ci.org/DScheglov/true-di.svg?branch=master)](https://travis-ci.org/DScheglov/true-di) [![Coverage Status](https://coveralls.io/repos/github/DScheglov/true-di/badge.svg?branch=master)](https://coveralls.io/github/DScheglov/true-di?branch=master) [![npm version](https://img.shields.io/npm/v/true-di.svg?style=flat-square)](https://www.npmjs.com/package/true-di) [![npm downloads](https://img.shields.io/npm/dm/true-di.svg?style=flat-square)](https://www.npmjs.com/package/true-di) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/DScheglov/true-di/blob/master/LICENSE) | ||
```bash | ||
npm i --S true-di | ||
npm i --save true-di | ||
``` | ||
@@ -24,2 +24,6 @@ | ||
- [Live Demo on Sandbox](https://codesandbox.io/s/github/DScheglov/true-di/tree/master/examples/getting-started?fontsize=14&hidenavigation=1&initialpath=%2Forders&module=%2Fsrc%2Fcontainer.ts&theme=dark) | ||
- [Example Source Code](./examples/getting-started) | ||
**./src/container.ts** | ||
@@ -29,3 +33,3 @@ | ||
import diContainer from 'true-di'; | ||
import { IContainer } from './interfaces'; | ||
import { ILogger, IDataSourceService, IECommerceService } from './interfaces'; | ||
import Logger from './Logger'; | ||
@@ -35,8 +39,13 @@ import DataSourceService from './DataSourceService'; | ||
type IServices = { | ||
logger: ILogger, | ||
dataSourceService: IDataSourceService, | ||
ecommerceService: IECommerceService, | ||
} | ||
const container = diContainer<IContainer>({ | ||
export default diContainer<IServices>({ | ||
logger: () => | ||
new Logger(), | ||
dataSourceService: ({ logger }) => | ||
dataSourceService: ({ logger }) => | ||
new DataSourceService(logger), | ||
@@ -47,4 +56,31 @@ | ||
}); | ||
``` | ||
export default container; | ||
**./src/ECommerceService/index.ts** | ||
```typescript | ||
import { | ||
IECommerceService, IDataSourceService, Order, IInfoLogger | ||
} from '../interfaces'; | ||
class ECommerceSerive implements IECommerceService { | ||
constructor( | ||
private readonly _logger: IInfoLogger, | ||
private readonly _dataSourceService: IDataSourceService, | ||
) { | ||
_logger.info('ECommerceService has been created'); | ||
} | ||
async getOrders(): Promise<Order[]> { | ||
const { _logger, _dataSourceService } = this; | ||
// do something | ||
} | ||
async getOrderById(id: string): Promise<Order | null> { | ||
const { _logger, _dataSourceService } = this; | ||
// do something | ||
} | ||
} | ||
export default ECommerceSerive; | ||
``` | ||
@@ -56,9 +92,26 @@ | ||
import Express from 'express'; | ||
import { IGetOrderById, IGetOrders } from './interfaces'; | ||
import { Injected } from './interfaces/IRequestInjected'; | ||
import { sendJson } from './utils/sendJson'; | ||
import { expectFound } from './utils/NotFoundError'; | ||
export const getOrders = async (req: Express.Request, res: Express.Response) => { | ||
const { ecommerceService } = req.injected; | ||
res.json( | ||
await ecommerceService.getOrders() | ||
); | ||
} | ||
export const getOrders = ( | ||
{ injected: { ecommerceService } }: Injected<{ ecommerceService: IGetOrders }>, | ||
res: Express.Response, | ||
next: Express.NextFunction, | ||
) => | ||
ecommerceService | ||
.getOrders() | ||
.then(sendJson(res), next); | ||
export const getOrderById = ( | ||
{ params, injected: { ecommerceService } }: | ||
{ params: { id: string } } & Injected<{ ecommerceService: IGetOrderById }>, | ||
res: Express.Response, | ||
next: Express.NextFunction, | ||
) => | ||
ecommerceService | ||
.getOrderById(params.id) | ||
.then(expectFound(`Order(${params.id})`)) | ||
.then(sendJson(res), next); | ||
``` | ||
@@ -71,7 +124,8 @@ | ||
import container from './container'; | ||
import { getOrders } from './controller'; | ||
import { getOrderById, getOrders } from './controller'; | ||
import { handleErrors } from './middlewares'; | ||
const app = express(); | ||
app.use((req, res, next) => { | ||
app.use((req, _, next) => { | ||
req.injected = container; | ||
@@ -82,30 +136,45 @@ next(); | ||
app.get('/orders', getOrders); | ||
app.listen(8080); | ||
app.get('/orders/:id', getOrderById); | ||
app.use(handleErrors); | ||
if (module.parent == null) { | ||
app.listen(8080, () => { | ||
console.log('Server is listening on port: 8080'); | ||
console.log('Follow: http://localhost:8080/orders'); | ||
}); | ||
} | ||
export default app; | ||
``` | ||
### [Live Demo on Sandbox](https://codesandbox.io/s/github/DScheglov/true-di/tree/master/examples/getting-started?fontsize=14&hidenavigation=1&initialpath=%2Forders&module=%2Fsrc%2Fcontainer.ts&theme=dark) | ||
## Motivation | ||
![Live Demo Preview](./docs/assets/image.png) | ||
`true-di` is designed to be used with [Apollo Server](https://github.com/apollographql/apollo-server) considering to make resolvers as thin as possible, to keep all business logic testable and framework agnostic by strictly following **S**.**O**.**L**.**I**.**D** Principles. | ||
## Why `true-di`? | ||
Despite the origin motivation being related to Apollo Server the library could be used with any other framework or library that supports injection through the context. | ||
1. It's light and idiomatic. | ||
1. It works with **JavaScript** and **TypeScript**. | ||
1. It's well-typed. Items types are always known whenever your code assess them. | ||
1. It is Isomorphic: works on Back end and on Front end. | ||
1. It works with Classes and with Closures. | ||
1. It doesn't require decorators (annotations). | ||
1. It doesn't depend on `reflect-metadata`. | ||
1. It allows referring items without strings or other artificial elements. | ||
1. It uses `getters` under the hood. | ||
1. It's lazy: container doesn't create an item until your code requests | ||
1. It automatically resolves dependencies | ||
1. It immediately falls down on constructor-injection of cyclic dependency | ||
1. It supports property-, setter- cyclic dependencies | ||
1. It doesn't know anything about context but it could be easily placed into the context. | ||
1. Its containers are composable. | ||
1. It's SOLID-compatible. | ||
1. It's more SOLID-compatible then other IOC solutions. Your code doesn't need to depend on the container (nor even on its interface). | ||
1. It's testable. | ||
1. It supports symbolic names. | ||
1. It doesn't have any other dependencies (and it will be kept) | ||
The [Getting Started Example](./examples/getting-started) shows how to use `true-di` with one of the most popular nodejs libraries: [Express](https://expressjs.com/). Almost all code in the example (~95%) is covered with tests that prove your business logic could be easily decoupled and kept independent even from dependency injection mechanism. | ||
Thanking to business logic does not depend on specific injection mechanism you can defer the | ||
choice of framework. Such deferring is suggested by Robert Martin: | ||
> The purpose of a good architecture is to defer decisions, delay decisions. The job of an architect is not to make decisions, the job of an architect is to build a structure that allows decisions to be delayed as long as possible. | ||
[© 2014, Robert C. Martin: Clean Architecture and Design, NDC Conference](https://vimeo.com/68215570), | ||
Summarizing, `true-di` is based on: | ||
- emulattion of a plain object that allows to specify exact type for each item and makes strict static type checking possible | ||
- explicit dependency injection for business logic (see example: [./src/container.ts](./examples/getting-started/src/container.ts)) | ||
- transperent injection through the context for framework-integrated components (see example: [./src/index.ts](./examples/getting-started/src/index.ts)) | ||
## Some useful facts about `true-di`? | ||
1. No syntatic decorators (annotations) as well as `reflect-metadata` are needed | ||
1. `diContainer` uses `getters` under the hood. Container doesn't create an item until your code requests | ||
1. Constructor-injection of cyclic dependency raises an exception | ||
1. The property-, setter- dependency injects is also supported (what allows to resolve cyclic dependencies) | ||
1. `diContainer`-s could be composed | ||
1. Symbolic names are also supported for items | ||
@@ -32,3 +32,3 @@ import UniqueStack from './unique-stack'; | ||
if (typeof initializer === 'function') { | ||
initializers.push(() => initializer(instance, container)); | ||
initializers.push(() => initializer(instance, container, name)); | ||
} | ||
@@ -35,0 +35,0 @@ |
import diContainer from './di-container'; | ||
import multiple from './multiple'; | ||
export * from './types'; | ||
export * from './di-container'; | ||
export { multiple }; | ||
export default diContainer; |
export type IInstanceInitializer<IContainer, N extends keyof IContainer> = ( | ||
instance: IContainer[N], | ||
container: IContainer | ||
container: IContainer, | ||
name?: N, | ||
) => void; | ||
@@ -5,0 +6,0 @@ |
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
55808
80
1154
173