Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

cheap-di

Package Overview
Dependencies
Maintainers
1
Versions
71
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cheap-di - npm Package Compare versions

Comparing version 4.0.0-rc.1 to 4.0.0-rc.2

src/decorators/inject.ts

2

package.json
{
"name": "cheap-di",
"description": "TypeScript dependency injection like Autofac in .Net",
"version": "4.0.0-rc.1",
"version": "4.0.0-rc.2",
"scripts": {

@@ -6,0 +6,0 @@ "compile": "tsc --build tsconfig.cjs.json ./tsconfig.esm.json ./tsconfig.types.json",

@@ -5,13 +5,11 @@ # cheap-di

* [Minimal Sample (TypeScript)](#minimal-sample)
* [How to use (JavaScript)](#how-to-use)
* [TypeScript](#type-script)
* [Decorators](#decorators)
* [dependencies](#dependencies)
* [inject](#inject)
* [di](#di)
* [singleton](#singleton)
* [How to use](#how-to-use)
* [Registration variants](#registration-variants)
* [registerImplementation](#register-implementation)
* [registerInstance](#register-instance)
## <a name="minimal-sample"></a> Minimal Sample (TypeScript)
## <a name="how-to-use"></a> How to use
The recommended way of using this package is using it with code transformers like `cheap-di-ts-transform`. Because in this way you will get the truly dependency injection:
```ts

@@ -23,5 +21,3 @@ abstract class Logger {

class ConsoleLogger implements Logger {
constructor(prefix) {
this.prefix = prefix;
}
constructor(public prefix: string) {}

@@ -33,5 +29,2 @@ debug(message: string) {

const metadata = <T>(constructor: T): T => constructor;
@metadata
class Service {

@@ -44,5 +37,25 @@ constructor(private logger: Logger) {}

}
/**
* With cheap-di-ts-transform here will be added information about Service dependencies.
* It will looks like:
* @example
* import { findOrCreateMetadata } from 'cheap-di';
*
* // for Logger
* try {
* const metadata = findOrCreateMetadata(Logger);
*
* // only classes may be instantiated with DI, other parameters can be filled with argument injection
* metadata.dependencies = ["unknown"];
* } catch {}
*
* // for Service
* try {
* const metadata = findOrCreateMetadata(Service);
*
* metadata.dependencies = [Logger];
* } catch {}
* */
// somewhere
// somewhere in you application initialization
import { container } from 'cheap-di';

@@ -52,4 +65,7 @@

container.registerType(ConsoleLogger).as(Logger).with(myLogPrefix);
container.registerType(Service);
// somewhere in inside your code
// or you may use some middleware to do this, to get rid of Service Locator antipattern
import { container } from 'cheap-di';
const service = container.resolve(Service);

@@ -59,356 +75,145 @@ service.doSome();

## <a name="how-to-use"></a> How to use (JavaScript)
But if you can't use transformers you still may use cheap-di with decorators:
You have an interface (base/abstract class) and its implementation (derived class)
```ts
import { inject } from 'cheap-di';
`user-repository.js`
```js
export class UserRepository {
constructor() {
if (new.target === UserRepository) {
throw new TypeError('Cannot construct UserRepository instance directly');
}
}
list() {
throw new Error("Not implemented");
}
getById(userId) {
throw new Error("Not implemented");
}
abstract class SessionAccessor {
abstract getSession(): string;
}
```
`fake-user-repository.js`
```js
import { UserRepository } from './user-repository';
export class FakeUserRepository extends UserRepository {
constructor() {
super();
this.users = [
{
id: 1,
name: 'user-1'
},
{
id: 2,
name: 'user-2'
},
];
}
list() {
return this.users;
}
getById(userId) {
return this.users.find(user => user.id === userId);
}
abstract class Logger {
abstract debug(message: string): void;
}
```
abstract class InfoLogger extends Logger {}
abstract class ErrorLogger extends Logger {}
There is simple logger.
// non-classes-arguments specified as "unknown"
@inject('unknown', SessionAccessor)
class ConsoleLogger implements Logger {
constructor(public prefix: string, private sessionAccessor: SessionAccessor) {}
`logger.js`
```js
export class Logger {
debug(message) {
throw new Error("Not implemented");
debug(message: string) {
console.log(`[${this.sessionAccessor.getSession()}] ${this.prefix}: ${message}`);
}
}
```
`console-logger.js`
```js
import { Logger } from './logger';
class Service {
constructor(private logger: InfoLogger) {}
export class ConsoleLogger extends Logger {
constructor(prefix) {
super();
this.prefix = prefix;
doSome() {
this.logger.debug('Hello world!');
}
debug(message) {
console.log(`${this.prefix}: ${message}`);
}
}
```
You have the repository consumer. To allow DI container inject dependencies in your consumer class you should
specify `__dependencies` static property. That property should contain constructor array in the order of your
constructor.
`user-service.js`
```js
import { dependenciesSymbol } from 'cheap-di';
import { Logger } from './logger';
import { UserRepository } from './user-repository';
export class UserService {
static [dependenciesSymbol] = [UserRepository, Logger];
constructor(userRepository, logger) {
this.userRepository = userRepository;
this.logger = logger;
}
list() {
this.logger.debug('Access to users list');
return this.userRepository.list();
}
get(userId) {
return this.userRepository.getById(userId);
}
}
```
Dependency registration
`some-config.js`
```js
// somewhere
import { container } from 'cheap-di';
import { ConsoleLogger } from './console-logger';
import { FakeUserRepository } from './fake-user-repository';
import { Logger } from './logger';
import { UserRepository } from './user-repository';
container.registerType(ConsoleLogger).as(Logger).with('most valuable message prefix');
container.registerType(FakeUserRepository).as(UserRepository);
```
const infoPrefix = 'INFO: ';
container.registerType(ConsoleLogger).as(InfoLogger).with(infoPrefix);
To get instance of your service with injected parameters you should call `resolve` method.
const errorPrefix = 'ERROR: ';
container.registerType(ConsoleLogger).as(ErrorLogger).with(errorPrefix);
`some-place.js`
```js
// somewhere in inside your code
// or you may use some middleware to do this, to get rid of Service Locator antipattern
import { container } from 'cheap-di';
import { UserService } from './user-service';
const service = container.resolve(UserService);
const users = service.list();
const service = container.resolve(Service);
service.doSome();
```
Your injection parameter can be placed in middle of constructor params. In this case you should put `undefined`
or `null` in `[dependencies]` with accordance order
```js
import { dependenciesSymbol, container } from 'cheap-di';
// ...
## <a name="registration-variants"></a> Registration variants
export class UserService {
static [dependenciesSymbol] = [UserRepository, undefined, Logger];
#### <a name="register-implementation"></a> registerImplementation
constructor(userRepository, someMessage, logger) {
// ...
}
}
If you would like to specify implementation of your interface:
```ts
import { container } from 'cheap-di';
// ...
abstract class Service {/**/}
class ServiceImpl extends Service {/**/}
container.registerType(UserService).with('my injection string');
container
.registerImplementation(ServiceImpl)
.as(Service);
```
## <a name="type-script"></a> TypeScript
`logger.ts`
Or if you want to inject some parameters to its constructor:
```ts
export abstract class Logger {
abstract debug: (message: string) => void;
}
```
import { container } from 'cheap-di';
`console-logger.ts`
```ts
import { Logger } from './logger';
export class ConsoleLogger extends Logger {
constructor(prefix) {
super();
this.prefix = prefix;
}
debug(message: string) {
console.log(`${this.prefix}: ${message}`);
}
class Some {
constructor(private name: string) {}
}
```
You can use typescript reflection for auto resolve your class dependencies and inject them.
For that you should add lines below to your `tsconfig.json`:
container
.registerImplementation(Service)
.inject('some name');
```
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
```
and add any class-decorator to your class. For example:
`metadata.ts`
Or if you want to have only one instance of the implementation class:
```ts
export const metadata = <T>(constructor: T): T => constructor;
```
import { container } from 'cheap-di';
`service.ts`
```ts
import { metadata } from './metadata';
import { Logger } from './logger';
class Some {}
@metadata
export class Service {
constructor(private logger: Logger) {}
doSome() {
this.logger.debug('Hello world!');
}
}
container
.registerImplementation(Service)
.asSingleton();
```
But you should explicitly register each type like this to resolve his dependencies by container:
And singleton also may be used with interface specification:
```ts
import { container } from 'cheap-di';
import { Service } from './service';
container.registerType(Service);
abstract class Service {/**/}
class ServiceImpl extends Service {/**/}
const service = container.resolve(Service);
service.doSome();
container
.registerImplementation(ServiceImpl)
.asSingleton(Service);
```
<h3>In this scenario you don't need decorators from next section!</h3>
---
## <a name="decorators"></a> Decorators
If you want to use any of next decorators, you should add line below to your `tsconfig.json`:
```
"experimentalDecorators": true,
```
### <a name="dependencies"></a> dependencies
`@dependencies` decorator can be used to simplify dependency syntax
`user-service.ts`
And even with argument injection:
```ts
import { dependencies } from 'cheap-di';
import { Logger } from './logger';
import { UserRepository } from './user-repository';
import { container } from 'cheap-di';
@dependencies(UserRepository, Logger)
export class UserService {
constructor(
private userRepository: UserRepository,
private logger: Logger,
) {}
abstract class Service {/**/}
list() {
this.logger.debug('Access to users list');
return this.userRepository.list();
class ServiceImpl extends Service {
constructor(private name: string) {
super();
}
get(userId) {
return this.userRepository.getById(userId);
}
}
```
### <a name="inject"></a> inject
`@inject` decorator can be used instead of `@dependencies` like below:
```ts
import { inject } from 'cheap-di';
export class UserService {
constructor(
@inject(UserRepository) private userRepository: UserRepository,
@inject(Logger) private logger: Logger,
) {}
}
container
.registerImplementation(ServiceImpl)
.asSingleton(Service)
.inject('some name');
```
This approach allows you to mix dependency with injection params with any order:
#### <a name="register-instance"></a> registerInstance
```ts
class Service {
constructor(
message1: string,
@inject(Repository) public repository: Repository,
message2: string,
@inject(Database) public db: Database,
) {
}
}
If you want to register some instance as interface
const message1 = '123';
const message2 = '456';
container.registerType(Service).with(message1, message2);
```
### <a name="di"></a> di
This decorator uses typescript reflection, so you should add line below to your `tsconfig.json`:
```
"emitDecoratorMetadata": true,
```
`@di` decorator can be used instead of `@dependencies` and `@inject` like below:
```ts
import { di } from 'cheap-di';
import { container } from 'cheap-di';
@di
export class UserService {
constructor(
private userRepository: UserRepository,
private logger: Logger,
) {}
abstract class Database {
abstract get(): Promise<string>;
}
@di
class Service {
constructor(
message1: string,
public repository: UserRepository,
message2: string,
public db: Database,
) {}
}
const db: Database = {
async get() {
return Promise.resolve('name1');
},
};
const message1 = '123';
const message2 = '456';
container.registerType(Service).with(message1, message2);
container.registerInstance(db).as(Database);
```
It automatically adds `@inject` decorators to your service.
### <a name="singleton"></a> singleton
`@singleton` decorator allows you to inject the same instance everywhere.
```ts
import { singleton } from 'cheap-di';
import { Logger } from './logger';
import { UserRepository } from './user-repository';
@singleton
export class UserService {
names: string[];
constructor() {
this.names = [];
}
list() {
return this.names;
}
add(name: string) {
this.names.push(name);
}
}
```
You can see more examples in `cheap-di/src/ContainerImpl.test.ts`
[Changelog](../../CHANGELOG.md)

@@ -1,5 +0,6 @@

import { isSingleton, modifySingleton } from './singleton.js';
import { CircularDependencyError } from './CircularDependencyError.js';
import { findMetadata, findOrCreateMetadata } from './findMetadata.js';
import { isSingleton } from './isSingleton.js';
import { modifyConstructor } from './modifyConstructor.js';
import { Trace } from './Trace.js';
import {

@@ -16,3 +17,3 @@ AbstractConstructor,

} from './types.js';
import { Trace } from './Trace.js';
import { workWithDiSettings } from './workWithDiSettings.js';

@@ -30,5 +31,5 @@ class ContainerImpl implements Container, IHaveSingletons, IHaveInstances, IHaveDependencies, Disposable {

/** register class */
registerType<TInstance>(implementationType: ImplementationType<TInstance>) {
const withInjection = (...injectionParams: any[]) => {
/** register implementation class */
registerImplementation<TInstance>(implementationType: ImplementationType<TInstance>) {
const inject = (...injectionParams: any[]) => {
modifyConstructor(implementationType, (settings) => {

@@ -49,6 +50,6 @@ const metadata = findOrCreateMetadata(settings);

/** add parameters that will be passed to the class constructor */
with: withInjection,
inject,
};
},
/** as singleton (optionally super class). Only if you didn't use @singleton decorator */
/** as singleton (optionally super class) */
asSingleton: <TBase extends Partial<TInstance>>(type?: RegistrationType<TBase>) => {

@@ -61,3 +62,7 @@ if (type) {

if (!isSingleton(implementationType)) {
modifySingleton(implementationType);
workWithDiSettings(implementationType, (settings) => {
const metadata = findOrCreateMetadata(settings);
metadata.modifiedClass = implementationType as TInstance;
metadata.singleton = true;
});
}

@@ -67,7 +72,7 @@

/** add parameters that will be passed to the class constructor */
with: withInjection,
inject,
};
},
/** add parameters that will be passed to the class constructor */
with: withInjection,
inject,
};

@@ -165,7 +170,7 @@ }

if (dependencies?.length) {
const injectableDependencies = dependencies.filter((d) => d);
const length = injectableDependencies.length + injectionParams.length;
const injectableDependencies = dependencies.filter((dependency) => dependency !== 'unknown') as Dependency[];
// const length = injectableDependencies.length + injectionParams.length;
while (true) {
const dependencyType = dependencies[index];
const dependencyType = injectableDependencies[index];
if (dependencyType) {

@@ -192,3 +197,4 @@ trace.addTrace(dependencyType.name);

if (index >= length) {
// if (index >= length) {
if (index >= injectableDependencies.length) {
break;

@@ -195,0 +201,0 @@ }

@@ -0,5 +1,8 @@

export * from './cheapDiSymbol.js';
export * from './CircularDependencyError.js';
export * from './ContainerImpl.js';
export * from './singleton.js';
export * from './CircularDependencyError.js';
export * from './decorators/inject.js';
export * from './findMetadata.js';
export * from './isSingleton.js';
export * from './Trace.js';
export * from './types.js';
export * from './cheapDiSymbol.js';

@@ -1,2 +0,2 @@

import { InheritancePreserver } from './InheritancePreserver.js';
import { findOrCreateMetadata } from './findMetadata.js';
import { Constructor, ImplementationType } from './types.js';

@@ -9,4 +9,8 @@ import { workWithDiSettings } from './workWithDiSettings.js';

) {
workWithDiSettings(constructor, modification);
InheritancePreserver.constructorModified(constructor);
workWithDiSettings(constructor, (settings) => {
const metadata = findOrCreateMetadata(settings);
metadata.modifiedClass = constructor as TClass;
modification(settings);
});
}

@@ -7,2 +7,4 @@ import { cheapDiSymbol } from './cheapDiSymbol.js';

type SomeDependency = Dependency | 'unknown';
type ImplementationType<TClass> = Constructor<TClass> & {

@@ -18,3 +20,3 @@ [Symbol.metadata]: DiMetadataStorage<TClass>;

singleton?: boolean;
dependencies?: Dependency[];
dependencies?: SomeDependency[];
modifiedClass?: TClass;

@@ -28,8 +30,8 @@ injected?: unknown[];

/** add parameters that will be passed to the class constructor */
with: (...injectionParams: any[]) => void;
inject: (...injectionParams: any[]) => void;
}
interface DependencyRegistrator<RegisterTypeExtension = object, RegisterInstanceExtension = object> {
/** register class */
registerType: <TClass>(implementationType: ImplementationType<TClass>) => {
/** register implementation class */
registerImplementation: <TClass>(implementationType: ImplementationType<TClass>) => {
/** as super class */

@@ -93,3 +95,4 @@ as: <TBase extends Partial<TClass>>(type: RegistrationType<TBase>) => WithInjectionParams;

RegistrationType,
SomeDependency,
WithInjectionParams,
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc