mana-syringe
Advanced tools
Comparing version
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
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
5.69%49
8.89%5377
5.78%236
44.79%2
-33.33%