ioc-service-container
Advanced tools
Comparing version 1.5.2 to 1.6.0
@@ -18,12 +18,12 @@ "use strict"; | ||
function redefineObject(target, propertyKey, serviceId) { | ||
const getter = () => { | ||
function get() { | ||
return ServiceContainer_1.default.get((serviceId === null || serviceId === void 0 ? void 0 : serviceId.toLowerCase()) || propertyKey.toLowerCase()); | ||
}; | ||
const setter = () => { | ||
} | ||
function set() { | ||
throw new Error(`Injected property [${propertyKey}] can't be reset`); | ||
}; | ||
} | ||
Object.defineProperty(target, propertyKey, { | ||
get: getter, | ||
set: setter | ||
get, | ||
set | ||
}); | ||
} |
@@ -10,6 +10,7 @@ declare const _default: { | ||
get<T>(id: string): T; | ||
override(id: string, factory: () => any): void; | ||
override(id: string, factoryOrClassReference: Factory | Function, buildInstantly?: boolean): void; | ||
isSet(id: string): boolean; | ||
reset(): void; | ||
}; | ||
export default _default; | ||
declare type Factory = () => any; | ||
type Factory = () => any; |
@@ -16,5 +16,3 @@ "use strict"; | ||
} | ||
let factory = isConstructable(factoryOrClassReference) | ||
? () => new factoryOrClassReference() | ||
: factoryOrClassReference; | ||
let factory = getFactory(factoryOrClassReference); | ||
services.push({ | ||
@@ -36,13 +34,21 @@ id: lowerId, | ||
}, | ||
override(id, factory) { | ||
override(id, factoryOrClassReference, buildInstantly = false) { | ||
const lowerId = id.toLowerCase(); | ||
const index = services.findIndex(s => s.id === lowerId); | ||
let factory = getFactory(factoryOrClassReference); | ||
const service = { | ||
id: lowerId, | ||
factory, | ||
instance: buildInstantly ? factory() : undefined, | ||
}; | ||
if (index === -1) { | ||
throw new Error(`No service is registered for [${id}]`); | ||
services.push(service); | ||
} | ||
services[index] = { | ||
id: lowerId, | ||
factory | ||
}; | ||
else { | ||
services[index] = service; | ||
} | ||
}, | ||
isSet(id) { | ||
return services.some(s => s.id === id.toLowerCase()); | ||
}, | ||
reset() { | ||
@@ -55,1 +61,6 @@ services = []; | ||
} | ||
function getFactory(factoryOrClassReference) { | ||
return isConstructable(factoryOrClassReference) | ||
? () => new factoryOrClassReference() | ||
: factoryOrClassReference; | ||
} |
{ | ||
"name": "ioc-service-container", | ||
"version": "1.5.2", | ||
"version": "1.6.0", | ||
"description": "Lightweight ioc service container", | ||
@@ -16,8 +16,8 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"@types/jest": "^27.0.2", | ||
"jest": "^27.3.1", | ||
"@types/jest": "^29.2.4", | ||
"jest": "^29.3.1", | ||
"sonarqube-scanner": "^2.8.1", | ||
"ts-jest": "^27.0.7", | ||
"ts-jest": "^29.0.3", | ||
"ts-node": "^10.4.0", | ||
"typescript": "^4.4.4" | ||
"typescript": "^4.9.3" | ||
}, | ||
@@ -24,0 +24,0 @@ "files": [ |
153
README.md
@@ -14,4 +14,2 @@ # ioc-service-container | ||
This is a lightweight library for a service container written in TypeScript. | ||
<a href="https://www.buymeacoffee.com/Mrcwbr" target="_blank"> | ||
@@ -23,22 +21,59 @@ <img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" | ||
> This is a lightweight **zero-dependency** library for a service container written in TypeScript. | ||
## Features | ||
* **Fully typed** | ||
* **100% TypeScript written** | ||
* **100% test coverage** | ||
* **0 dependencies** | ||
* **< 2 KB package size** | ||
* **Typescript Decorator support** | ||
* **Simple API** | ||
* **Works beautiful with [jest-mock-extended](https://www.npmjs.com/package/jest-mock-extended)** | ||
## Demo | ||
In this [StackBlitz-Demo](https://stackblitz.com/edit/react-ts-qya4xy?file=App.tsx) you can see a demonstration of | ||
the `ioc-service-container`. In the `App.tsx` you can verify that the `UserService` is fully typed without importing the | ||
class. | ||
![TypeScriptSupport](https://i.ibb.co/stpBrkk/type.jpg) | ||
## Get started | ||
Install the dependency with `npm install ioc-service-container | ||
` | ||
Install the dependency with `npm install ioc-service-container` | ||
## Usage | ||
First set up an Enum for preventing typos or redefinition of service ids in a file called `ServiceIds.ts`: | ||
### 1. Define the Types | ||
If you use the `ioc-service-container` in a TypeScript project, define the types of your services in a `ioc.d.ts` file | ||
otherwise you can skip this step. | ||
```typescript | ||
export enum ServiceId { | ||
TestApi = 'TestApi', | ||
TestService = 'TestService', | ||
FooApi = 'FooApi', | ||
// Import your services | ||
import { TestApi } from '../your-path/to/TestApi' | ||
import { FooApi } from '../your-path/to/FooApi' | ||
import { TestService } from '../your-path/to/TestService' | ||
// Create the mapping between ServiceId and Service | ||
type IoCTypes = { | ||
TestApi: TestApi, | ||
FooApi: FooApi, | ||
TestService: TestService, | ||
// ... | ||
}; | ||
// Redeclare the scg function to get full Typscript support | ||
declare module 'ioc-service-container' { | ||
export function scg<T extends keyof IoCTypes, U extends IoCTypes[T]>(id: T): U; | ||
} | ||
``` | ||
According to this you have to pass a factory of your required services to the ioc container. So at the initial script of | ||
your application you call a function named e.g. `setupService`: | ||
### 2. Setup your services | ||
According to this you have to pass a factory or a class reference of your required services to the ioc container. So | ||
at the initial script of your application you call a function named e.g. `setupService`: | ||
```typescript | ||
@@ -48,61 +83,45 @@ import { ServiceContainer } from 'ioc-service-container'; | ||
function setupService() { | ||
ServiceContainer.set(ServiceId.TestApi, CustomTestApi); // setup by class reference | ||
ServiceContainer.set(ServiceId.FooApi, () => new CustomFooApi()); // setup by custom factory | ||
ServiceContainer.set(ServiceId.Xyz, () => 'xyz'); | ||
ServiceContainer.set('TestApi', CustomTestApi); // setup by class reference | ||
ServiceContainer.set('FooApi', () => new CustomFooApi()); // setup by custom factory | ||
ServiceContainer.set('TestService', TestService, true); // instantieate immediately | ||
} | ||
``` | ||
Now you have two options to inject the requested service. The first one is without the usage of TypeScript annotations. | ||
This can be used anywhere in your code: | ||
The factory is only instantiated at need. You can pass the `buildInstantly` attribute if the service should be | ||
initialized immediately e.g. for setting up [Sentry](https://sentry.io/welcome/) in a `LoggingService`. | ||
### Assign service to a var | ||
### 3. Inject services | ||
```typescript | ||
import { scg, ServiceContainer } from 'ioc-service-container'; | ||
Now you have 2 options to inject the requested service. | ||
const testService = ServiceContainer.get<TestService>(ServiceId.TestService); | ||
const testService1 = scg<TestService>(ServiceId.TestService); // scg is a shortcut for ServiceContainer.get() | ||
``` | ||
#### 3.1 `scg()` Function | ||
#### Full TypeScript Support without generics | ||
The first is the most common one: `const testApi = scg('TestApi);`. (Shortcut for `ServiceContainer.get()`. Because of | ||
the type declaration you have full TypeScript support at this point and no dependency on the file/class `TestApi`. (See | ||
the [Demo](https://stackblitz.com/edit/react-ts-qya4xy?file=App.tsx)) | ||
As you can see in the example above it's very unsexy to assign a service to a constant. You have to write 3 | ||
times `testService` (constant's name, generic & ServiceId). You are able to improve the typings by adding following | ||
content in your `ServiceIds.ts` file : | ||
#### 3.2 `@inject` Decorator | ||
```typescript | ||
export enum ServiceId { | ||
TestApi = 'TestApi', | ||
// ... | ||
} | ||
> This requires `"experimentalDecorators": true` to be enabled in your `tsconfig.json` | ||
> (See [Typescript Docs](https://www.typescriptlang.org/tsconfig#experimentalDecorators)) | ||
declare module 'ioc-service-container' { | ||
export function scg<T extends keyof ServiceIdMap, U extends ServiceIdMap[T]>(id: T): U; | ||
type ServiceIdMap = { | ||
[ServiceId.TestApi]: TestApi, | ||
} | ||
} | ||
``` | ||
If you now use `const a = scg(ServiceId.TestApi)`, `a` is correctly typed. | ||
### Inject service via typescript decorator | ||
The second option is to use the `@inject` decorator inside a class: | ||
```typescript | ||
export class CustomTestService implements TestService { | ||
@inject | ||
private readonly customApi!: Api; // Important is the naming of the property, its mapped to the serice id | ||
private readonly customApi!: Api; // Important is the naming of the property, it's mapped to the service id | ||
@inject(ServiceId.FooApi) // If you don't want to name your property like the service id, use this decorator | ||
@inject('FooApi') // If you don't want to name your property like the service id, pass the id as parameter | ||
private readonly nameThisHowYouWant!: Api; | ||
private readonly barApi = ServiceContainer.get<Api>(ServiceId.BarApi) // Use this syntax if you don't want to use decorators | ||
private readonly fooApi = ServiceContainer.get<Api>('FooApi') // Use this syntax if you don't want to use decorators | ||
private readonly barApi = scg('BarApi') // Shortcut for ServiceContainer.get() | ||
} | ||
``` | ||
``` | ||
Your can see a demo in the `./example` folder. To run this type in `npm run example`. | ||
### 4. Other Use-Cases | ||
For Testing or similar use cases you have the option to | ||
use `ServiceContainer.isSet('anId')`, `ServiceContainer.override('anId', () => 123)` or `ServiceContainer.reset()`. | ||
## Background | ||
@@ -123,33 +142,3 @@ | ||
## Goal | ||
The goal of DI is to encapsulate the dependencies of a class. The CustomService should work without knowing which api it | ||
is using. Following structure should be created: | ||
``` | ||
+----------+ +-------------------+ | ||
| | | | | ||
| Consumer +--->+ interface Service | | ||
| | | | | ||
+----------+ +---------+---------+ | ||
^ | ||
| | ||
| | ||
+---------+-----------+ +----------------+ | ||
| | | | | ||
| class CustomService +---->+ interface Api | | ||
| implements Service | | | | ||
| | +--------+-------+ | ||
+---------------------+ ^ | ||
| | ||
| | ||
+--------+--------+ | ||
| | | ||
| class CustomApi | | ||
| implements Api | | ||
| | | ||
+-----------------+ | ||
``` | ||
(Btw [asciiflow.com](http://asciiflow.com/) is a great tool for creating small charts for e.g. Readme.md) | ||
is using. The following structure should be created. |
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
15243
143
141