mana-syringe
Advanced tools
Comparing version 0.1.0 to 0.2.0
import { Container as InversifyContainer } from 'inversify'; | ||
import type { InversifyContext } from './inversify'; | ||
import type { InversifyContext } from './inversify/inversify-protocol'; | ||
import type { Syringe } from './core'; | ||
import type { SyringeModule } from './module'; | ||
export declare class Container implements Syringe.Container, InversifyContext { | ||
static config(option: Syringe.InjectOption<void>): void; | ||
protected loadedModule: number[]; | ||
protected loadedModules: number[]; | ||
container: InversifyContainer; | ||
inversify: boolean; | ||
protected inversify: boolean; | ||
parent?: Container; | ||
constructor(inversifyContainer?: InversifyContainer); | ||
load(module: SyringeModule, force?: boolean): void; | ||
load(module: Syringe.Module, force?: boolean): void; | ||
remove<T>(token: Syringe.Token<T>): void; | ||
@@ -21,3 +20,4 @@ get<T>(token: Syringe.Token<T>): T; | ||
createChild(): Syringe.Container; | ||
register<T = any>(token: Syringe.Token<T> | Syringe.InjectOption<T>, options?: Syringe.InjectOption<T>): void; | ||
register<T = any>(tokenOrOption: Syringe.Token<T> | Syringe.InjectOption<T>): void; | ||
register<T = any>(token: Syringe.Token<T>, options: Syringe.InjectOption<T>): void; | ||
protected resolveContext(): Syringe.Context; | ||
@@ -24,0 +24,0 @@ } |
@@ -1,4 +0,11 @@ | ||
export * from './interface'; | ||
export * from './provider'; | ||
export * from './contribution'; | ||
import * as Protocol from './contribution-protocol'; | ||
import { contributionRegister } from './contribution-register'; | ||
export * from './contribution-protocol'; | ||
export * from './contribution-provider'; | ||
export * from './decorator'; | ||
export declare namespace Contribution { | ||
type Option = Protocol.Option; | ||
type Provider<T extends Record<string, any>> = Protocol.Provider<T>; | ||
const Provider: import("..").Syringe.DefinedToken; | ||
const register: typeof contributionRegister; | ||
} |
@@ -42,2 +42,3 @@ import 'reflect-metadata'; | ||
}; | ||
function isModule(data: Record<any, any> | undefined): data is Module; | ||
type Container = { | ||
@@ -44,0 +45,0 @@ parent?: Container; |
import type { interfaces } from 'inversify'; | ||
import type { InversifyContext } from './inversify'; | ||
import type { InversifyContext } from './inversify/inversify-protocol'; | ||
import { Syringe } from './core'; | ||
@@ -24,3 +24,3 @@ export declare function toRegistryOption<P>(options: Syringe.InjectOption<P>): Syringe.FormattedInjectOption<P>; | ||
protected context: Syringe.Container; | ||
protected mutipleEnable: boolean; | ||
protected mutiple: boolean; | ||
constructor(context: Syringe.Container, token: Syringe.UnionToken<T>, option: Syringe.FormattedInjectOption<T>); | ||
@@ -27,0 +27,0 @@ /** |
{ | ||
"name": "mana-syringe", | ||
"keywords": [], | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"typings": "dist/index.d.ts", | ||
@@ -19,3 +19,3 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"mana-scripts": "^0.1.0" | ||
"mana-scripts": "^0.2.0" | ||
}, | ||
@@ -29,3 +29,3 @@ "scripts": { | ||
}, | ||
"gitHead": "6b8dca016bab39959aecfa75e4e35dc76efcae91" | ||
"gitHead": "6d25a80429a48414e6d8291097c9a335b83e20e9" | ||
} |
197
README.md
@@ -13,5 +13,5 @@ # mana-syringe | ||
## 概念 | ||
## 概念与使用 | ||
1. 注入标识 token | ||
### 注入标识 token | ||
@@ -24,48 +24,12 @@ 注入绑定对象所使用的的标识,可以带有一定的类型约束 | ||
我们可以比较开放的使用标识,考虑到多包的协作,一般会使用 symbol 类型,标识默认支持单绑定,需要使用多绑定时,应该使用 Syringe.defineToken 方法获得,其接受是否可多绑定的设置。 | ||
除 `Syringe.DefinedToken<T>` 默认支持多绑定外,注入标识只支持单一绑定关系。可以使用如下 API 生成 DefinedToken | ||
2. 容器 Container | ||
包含一组绑定标识与注入对象关系描述的上下文成为容器,当我们通过容器获取实例时,容器会根据注入对象及其与标识的关系自动构建所需的其他实例。 | ||
3. 生命期 lifecycle | ||
容器会根据注入对象的生命期描述托管这些对象。 | ||
```typescript | ||
export enum Lifecycle { | ||
singleton = 'singleton', | ||
transient = 'transient', | ||
} | ||
Syringe.defineToken('sample-token'); | ||
``` | ||
## 使用 | ||
### 容器 Container | ||
### 装饰器 | ||
包含一组绑定标识与注入对象关系描述的上下文成为容器,当我们通过容器获取实例时,容器会根据注入对象及其与标识的关系自动构建所需的其他实例。 | ||
我们提供了一组对类与属性的装饰器函数,用来快速完成基于依赖注入的类型描述,并完成基本的绑定关系描述。 | ||
- injectable: 通用装饰器,接受所有绑定描述参数 | ||
- singleton: 单例装饰器,接受除生命期外的描述参数 | ||
- transient: 多例装饰器,接受除生命期外的描述参数 | ||
- inject: 注入,接受注入标识作为参数,并接受类型描述 | ||
```typescript | ||
@singleton() | ||
class Shuriken implements Weapon { | ||
public hit() { | ||
console.log('Shuriken hit'); | ||
} | ||
} | ||
@transient() | ||
class Ninja { | ||
@inject(Weapon) public weapon: Weapon; | ||
public hit() { | ||
this.weapon.hit(); | ||
} | ||
} | ||
``` | ||
### 容器 | ||
用户可以手动创建容器,使用全局默认的容器,或者创建子容器 | ||
@@ -80,4 +44,12 @@ | ||
### 注册 | ||
我们使用 `token` 从容器里获取对象 | ||
```typescript | ||
const ninja = child.get(Ninja); | ||
``` | ||
当我们从子容器中获取对象时,会先从子容器查找绑定关系和缓存信息,如果不存在,则继续向父容器查找。 | ||
### 注册 register | ||
容器上暴露了 register 方法,这个 API 是整个体系的核心。 register 方法有两种签名 | ||
@@ -108,3 +80,2 @@ | ||
- contrib 可以为数组,可用于注册扩展点,也可用于注册 token 别名 | ||
- lifecycle 生命期,参见前文 | ||
- useClass 可以为数组,给出一个或多个类 | ||
@@ -116,4 +87,17 @@ - useToken 可以为数组,根据 token 从容器内动态获取对象 | ||
#### 生命期 lifecycle | ||
容器会根据注入对象的生命期描述托管这些对象,决定是否使用缓存等。 | ||
```typescript | ||
@singleton() | ||
export enum Lifecycle { | ||
singleton = 'singleton', | ||
transient = 'transient', | ||
} | ||
``` | ||
#### 注册类和别名 | ||
```typescript | ||
@singleton({ contrib: Alias }) | ||
class Shuriken implements Weapon { | ||
@@ -125,22 +109,99 @@ public hit() { | ||
GlobalContainer.register(Shuriken); | ||
GlobalContainer.register({ token: Shuriken, useClass: Shuriken }); | ||
GlobalContainer.register(Shuriken, { | ||
useClass: Shuriken, | ||
lifecycle: Syringe.Lifecycle.singleton, | ||
}); | ||
``` | ||
### 模块 | ||
通过 token 注册后,每个 token 的注册关系是独立的,通过他们获取对象可以是不同的值,通过 contrib 注册的是别名关系,他们应该获取到同一个对象。不管是 token 还是 contrib,根据对多绑定的支持情况做处理。 | ||
可以通过用一组注册动作创建一个模块,方便在不同容器上下文间内加载 | ||
```typescript | ||
const Weapon = Symbol('Weapon'); | ||
const WeaponArray = Syringe.defineToken('Weapon'); | ||
@singleton({ contrib: Weapon }) | ||
class Shuriken implements Weapon { | ||
public hit() { | ||
console.log('Shuriken hit'); | ||
} | ||
} | ||
GlobalContainer.register({ token: Weapon, useValue: undefined }); | ||
GlobalContainer.register({ token: WeaponArray, useValue: undefined }); | ||
GlobalContainer.register(Shuriken); | ||
GlobalContainer.get(Weapon); // Shuriken | ||
GlobalContainer.getAll(WeaponArray); // [undefined, Shuriken] | ||
``` | ||
#### 注册值 | ||
```typescript | ||
const module = Module(register => { | ||
register(Shuriken); | ||
const ConstantValue = Symbol('ConstantValue'); | ||
GlobalContainer.register({ token: ConstantValue, useValue: {} }); | ||
``` | ||
#### 注册动态值 | ||
```typescript | ||
const NinjaAlias = Symbol('NinjaAlias'); | ||
GlobalContainer.register({ | ||
token: NinjaAlias, | ||
useDynamic: ctx => ctx.container.get(Ninja), | ||
}); | ||
GlobalContainer.load(module); | ||
``` | ||
### 扩展点 | ||
### 装饰器 | ||
为了方便注册扩展点,容器提供了一组扩展的注册与使用接口。扩展点定义使用的 token 必须为 DefinedToken。 | ||
我们提供了一组对类与属性的装饰器函数,用来快速完成基于依赖注入的类型描述,并完成基本的绑定关系描述。 | ||
- injectable: 通用装饰器,接受所有绑定描述参数 | ||
- singleton: 单例装饰器,接受除生命期外的描述参数 | ||
- transient: 多例装饰器,接受除生命期外的描述参数 | ||
- inject: 注入,接受注入标识作为参数,并接受类型描述 | ||
```typescript | ||
@singleton() | ||
class Shuriken implements Weapon { | ||
public hit() { | ||
console.log('Shuriken hit'); | ||
} | ||
} | ||
@transient() | ||
class Ninja { | ||
@inject(Weapon) public weapon: Weapon; | ||
public hit() { | ||
this.weapon.hit(); | ||
} | ||
} | ||
``` | ||
### 扩展点 Contribution | ||
我们通常将依赖注入的多绑定模式以扩展点的形式使用,为了方便在项目中使用这种模式,我们内置了对扩展点的定义和支持。 | ||
#### 扩展点的定义与注册 | ||
```typescript | ||
const Waepon = Syringe.defineToken('Weapon'); | ||
Contribution.register(GlobalContainer.register, Waepon); | ||
``` | ||
#### 扩展服务 Contribution.Provider | ||
内置了扩展点的管理服务,用户一般直接使用即可,注册扩展点以后,通过如下方式获取扩展服务 | ||
```typescript | ||
@contrib(Weapon) public weaponProvider: Contribution.Provider<Weapon>; | ||
``` | ||
等价于如下写法 | ||
```typescript | ||
@inject(Contribution.Provider) @named(Weapon) public weaponProvider: Contribution.Provider<Weapon>; | ||
``` | ||
#### 扩展点示例 | ||
```typescript | ||
const Waepon = Syringe.defineToken('Weapon'); | ||
Contribution.register(GlobalContainer.register, Waepon); | ||
@singleton({ contrib: Weapon }) | ||
@@ -152,2 +213,10 @@ class Shuriken implements Weapon { | ||
} | ||
@transient() | ||
class Ninja { | ||
@contrib(Weapon) public weaponProvider: Contribution.Provider<Weapon>; | ||
hit() { | ||
const weapons = this.weaponProvider.getContributions(); | ||
weapons.forEach(w => w.hit()); | ||
} | ||
} | ||
const module = Module(register => { | ||
@@ -158,13 +227,17 @@ Contribution.register(register, Weapon); | ||
}); | ||
GlobalContainer.register(Shuriken); | ||
GlobalContainer.register(Ninja); | ||
GlobalContainer.get(Ninja).hit(); // Shuriken hit | ||
``` | ||
@transient() | ||
class Ninja { | ||
@contrib(Weapon) public weapons: Weapon[]; | ||
} | ||
### 模块 | ||
可以通过用一组注册动作创建一个模块,方便在不同容器上下文间内加载, 模块的构建支持注册函数和链式调用两种方式,前面扩展点示例里的模块也可以写成如下形式: | ||
```typescript | ||
const module = Module().contribution(Weapon).register(Shuriken, Ninja); | ||
GlobalContainer.load(module); | ||
``` | ||
## 其他 | ||
1. 相同 module 默认不重复加载。 | ||
2. Contribution 可以通过多种传参形式处理缓存及是否从父组件获取。 | ||
3. 除非通过 defineToken 声明允许多绑定,token 默认单一绑定,重复注册时进行替换操作。 | ||
- 相同 module 默认不重复加载。 |
@@ -1,2 +0,2 @@ | ||
import assert from 'power-assert'; | ||
import assert from 'assert'; | ||
import { register, GlobalContainer, Container } from './container'; | ||
@@ -102,2 +102,8 @@ import { singleton, transient, injectable } from './decorator'; | ||
}); | ||
it('#undefined value', () => { | ||
const TokenSymbol = Symbol('UndefinedToken'); | ||
register({ token: TokenSymbol, useValue: undefined }); | ||
const foo1 = GlobalContainer.get(TokenSymbol); | ||
assert(typeof foo1 === 'undefined'); | ||
}); | ||
it('#get all', () => { | ||
@@ -164,2 +170,18 @@ const token = Syringe.defineToken('token'); | ||
}); | ||
it('#contrib to mono/multi token', () => { | ||
const FooSymbol = Symbol('FooSymbol'); | ||
const BarSymbol = Syringe.defineToken('BarSymbol'); | ||
register({ token: FooSymbol, useValue: FooSymbol }); | ||
register({ token: BarSymbol, useValue: undefined }); | ||
@singleton({ contrib: [FooSymbol, BarSymbol] }) | ||
class FooContribToMonoMulti {} | ||
register(FooContribToMonoMulti); | ||
const obj = GlobalContainer.get(FooContribToMonoMulti); | ||
const objFoo = GlobalContainer.get(FooSymbol); | ||
const barList = GlobalContainer.getAll(BarSymbol); | ||
assert(obj === objFoo); | ||
assert(barList.includes(objFoo)); | ||
assert(barList.includes(undefined)); | ||
assert(barList.length === 2); | ||
}); | ||
it('#remove', () => { | ||
@@ -166,0 +188,0 @@ @singleton() |
import { Container as InversifyContainer } from 'inversify'; | ||
import type { InversifyContext } from './inversify'; | ||
import type { InversifyContext } from './inversify/inversify-protocol'; | ||
import { | ||
@@ -11,3 +11,2 @@ GlobalContainer as InversifyGlobalContainer, | ||
import { Register } from './register'; | ||
import type { SyringeModule } from './module'; | ||
@@ -19,5 +18,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
} | ||
protected loadedModule: number[] = []; | ||
protected loadedModules: number[] = []; | ||
container: InversifyContainer; | ||
inversify: boolean = true; | ||
protected inversify: boolean = true; | ||
parent?: Container; | ||
@@ -31,6 +30,6 @@ constructor(inversifyContainer?: InversifyContainer) { | ||
} | ||
load(module: SyringeModule, force?: boolean): void { | ||
if (force || !this.loadedModule.includes(module.id)) { | ||
load(module: Syringe.Module, force?: boolean): void { | ||
if (force || !this.loadedModules.includes(module.id)) { | ||
module.registry(this.register.bind(this), this.resolveContext()); | ||
this.loadedModule.push(module.id); | ||
this.loadedModules.push(module.id); | ||
} | ||
@@ -68,2 +67,4 @@ } | ||
} | ||
register<T = any>(tokenOrOption: Syringe.Token<T> | Syringe.InjectOption<T>): void; | ||
register<T = any>(token: Syringe.Token<T>, options: Syringe.InjectOption<T>): void; | ||
register<T = any>( | ||
@@ -70,0 +71,0 @@ token: Syringe.Token<T> | Syringe.InjectOption<T>, |
import type { Syringe } from '../core'; | ||
import { Contribution } from './contribution'; | ||
import { inject, named } from '../decorator'; | ||
import { Provider } from './contribution-protocol'; | ||
@@ -14,3 +14,3 @@ export const contrib = | ||
named(token)(target, targetKey, index); | ||
inject(Contribution.Provider)(target, targetKey, index); | ||
inject(Provider)(target, targetKey, index); | ||
}; |
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import 'reflect-metadata'; | ||
import assert from 'power-assert'; | ||
import assert from 'assert'; | ||
import { GlobalContainer } from '../container'; | ||
@@ -9,3 +9,3 @@ import { register } from '../container'; | ||
import { Contribution, contrib } from '../'; | ||
import { DefaultContributionProvider } from './provider'; | ||
import { DefaultContributionProvider } from './contribution-provider'; | ||
import { Syringe } from '../core'; | ||
@@ -12,0 +12,0 @@ |
@@ -1,4 +0,13 @@ | ||
export * from './interface'; | ||
export * from './provider'; | ||
export * from './contribution'; | ||
import * as Protocol from './contribution-protocol'; | ||
import { contributionRegister } from './contribution-register'; | ||
export * from './contribution-protocol'; | ||
export * from './contribution-provider'; | ||
export * from './decorator'; | ||
export namespace Contribution { | ||
export type Option = Protocol.Option; | ||
export type Provider<T extends Record<string, any>> = Protocol.Provider<T>; | ||
export const { Provider } = Protocol; | ||
export const register = contributionRegister; | ||
} |
@@ -65,2 +65,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
}; | ||
export function isModule(data: Record<any, any> | undefined): data is Module { | ||
return !!data && typeof data === 'object' && 'id' in data && 'registry' in data; | ||
} | ||
export type Container = { | ||
@@ -67,0 +72,0 @@ parent?: Container; |
@@ -1,2 +0,2 @@ | ||
import assert from 'power-assert'; | ||
import assert from 'assert'; | ||
import { Syringe } from './core'; | ||
@@ -10,3 +10,3 @@ import { singleton, transient, injectable } from './decorator'; | ||
const option = Reflect.getMetadata(Syringe.ClassOptionSymbol, Foo); | ||
assert((option.target = Foo)); | ||
assert(option.target === Foo); | ||
}); | ||
@@ -13,0 +13,0 @@ it('#injectable with option', () => { |
@@ -1,2 +0,2 @@ | ||
import assert from 'power-assert'; | ||
import assert from 'assert'; | ||
import { injectable } from 'inversify'; | ||
@@ -47,2 +47,11 @@ import { toRegistryOption } from './register'; | ||
}); | ||
it('#undefined value', () => { | ||
const TokenSymbol = Symbol('UndefinedToken'); | ||
const parsed = toRegistryOption({ token: TokenSymbol, useValue: undefined }); | ||
assert(parsed.token.length === 1); | ||
assert(parsed.token.includes(TokenSymbol)); | ||
assert(parsed.useValue === undefined); | ||
assert('useValue' in parsed); | ||
assert(parsed.lifecycle === Syringe.DefaultOption.lifecycle); | ||
}); | ||
}); | ||
@@ -49,0 +58,0 @@ |
import type { interfaces } from 'inversify'; | ||
import type { InversifyContext } from './inversify'; | ||
import type { InversifyContext } from './inversify/inversify-protocol'; | ||
import { | ||
@@ -22,3 +22,2 @@ bindNamed, | ||
const useFactory = Utils.maybeArrayToArray(options.useFactory); | ||
const { useValue } = options; | ||
const contrib = Utils.maybeArrayToArray(options.contrib); | ||
@@ -37,4 +36,6 @@ const lifecycle = options.lifecycle || Syringe.Lifecycle.transient; | ||
useFactory, | ||
useValue, | ||
}; | ||
if ('useValue' in options) { | ||
generalOption.useValue = options.useValue; | ||
} | ||
return generalOption; | ||
@@ -106,3 +107,3 @@ } | ||
parsedOption.useFactory.length === 0 && | ||
parsedOption.useValue === undefined | ||
!('useValue' in parsedOption) | ||
) { | ||
@@ -128,3 +129,3 @@ log('No value to register for token:', parsedOption.token); | ||
protected context: Syringe.Container; | ||
protected mutipleEnable: boolean; | ||
protected mutiple: boolean; | ||
constructor( | ||
@@ -140,3 +141,3 @@ context: Syringe.Container, | ||
this.named = Utils.isNamedToken(token) ? token.named : undefined; | ||
this.mutipleEnable = !!this.named || Utils.isMultipleEnabled(this.rawToken); | ||
this.mutiple = !!this.named || Utils.isMultipleEnabled(this.rawToken); | ||
this.generalToken = this.rawToken; | ||
@@ -153,3 +154,3 @@ } | ||
} | ||
if (this.mutipleEnable) { | ||
if (this.mutiple) { | ||
this.resolveMutilple(context); | ||
@@ -159,5 +160,9 @@ } else { | ||
if (!this.named && this.option.contrib.length > 0) { | ||
this.option.contrib.forEach(contribution => | ||
bindGeneralToken(contribution, context).toService(this.generalToken), | ||
); | ||
this.option.contrib.forEach(contribution => { | ||
if (Utils.isMultipleEnabled(contribution)) { | ||
bindGeneralToken(contribution, context).toService(this.generalToken); | ||
} else { | ||
bindMonoToken(contribution, context).toService(this.generalToken); | ||
} | ||
}); | ||
} | ||
@@ -168,4 +173,4 @@ } | ||
protected resolveMono(context: InversifyContext): interfaces.BindingWhenOnSyntax<T> | undefined { | ||
if (this.option.useValue !== undefined) { | ||
return bindMonoToken(this.generalToken, context).toConstantValue(this.option.useValue); | ||
if ('useValue' in this.option) { | ||
return bindMonoToken(this.generalToken, context).toConstantValue(this.option.useValue!); | ||
} | ||
@@ -210,4 +215,4 @@ if (this.option.useDynamic.length > 0) { | ||
const valueToBind = | ||
this.option.useValue !== undefined | ||
? bindGeneralToken(this.generalToken, context).toConstantValue(this.option.useValue) | ||
'useValue' in this.option | ||
? bindGeneralToken(this.generalToken, context).toConstantValue(this.option.useValue!) | ||
: undefined; | ||
@@ -214,0 +219,0 @@ if (this.named) { |
@@ -1,2 +0,2 @@ | ||
import assert from 'power-assert'; | ||
import assert from 'assert'; | ||
import { register, GlobalContainer } from './container'; | ||
@@ -3,0 +3,0 @@ import { singleton } from './decorator'; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
221542
49
5377
236
2