@ doweljs/ 分数
TypeScript 的完全依赖性注射.
- 编译器检查完整性 . 一个容器不能用一个不捆绑的插槽来建造,打开时没有其范围值,或者要求一个它看不到的节点. 类型错误 :.
- 只在边上发作.
build()财务报告和审定财务报表open()返回承诺;get()从来没有。 独立自动同步服务同时启动. - 范围是推定的. 取决于请求范围槽和每个请求的服务寿命. 在更长寿的服务中捕捉更短寿值是一个编译错误.
- 所有权是明确的. 服务处理它所采纳的内容,资源有一个所有者.
- 童工是可以注射的. 一个 执行 操作一个打字的跑道;每跑道得到自己的短寿命容器.
- 没有装饰,反射或代理. 大约9个KB被精密和被克齐克;
get()大约需要40纳秒.
import { container, service, slot } from '@doweljs/core'
const Logger = slot('Logger')<{ info(message: string): void }>()
const Config = service('Config', () => ({ dbUrl: process.env.DATABASE_URL! }))
const Db = service('Db', { config: Config }, async ({ config }, ctx) =>
ctx.adopt(await connect(config.dbUrl), (client) => client.close()),
)
const Users = service('Users', { db: Db, log: Logger }, ({ db, log }) => ({
list: () => {
log.info('listing users')
return db.query('select * from users')
},
}))
const app = await container().provide(Users).bind(Logger, { info: console.log }).build()
app.get(Users).list()
await app.dispose()
删除 .bind(Logger, …) 财务报告和审定财务报表 build() 停止调用: 类型变为 MissingBindings<{ Logger: "Users" }>。 。 。 .
槽数
插槽是容器所填充的已命名并打入的空隙. 槽是图中唯一的开出点;其他的都是自动接线.
// Bound once per container.
const Logger = slot('Logger')<LoggerApi>()
// Supplied each time a container for PerRequest opens.
const PerRequest = scope('request')
const Request = slot('Request', { scope: PerRequest })<IncomingRequest>()
// Collects contributions into a readonly array.
const Middleware = slot('Middleware', { many: true })<Middleware>()
名称在一个容器内是独一无二的:类型系统用名称来识别节点.
服务
一个服务将其依赖性列为值 。 其要求、范围和是否同步都来自这些要求.
const Users = service(
'Users',
{ db: Db, log: Logger, metrics: optional(Metrics) },
({ db, log, metrics }, ctx) => {
ctx.defer(() => log.info('Users disposed'))
return {
list() {
metrics?.increment('users.list')
return db.query('select * from users')
},
}
},
)
缓冲 Users 来查看图中的编译器理由 :
Service<UsersApi, {
name: 'Users'; scope: 'root'; above: never
graph: {
Users: { kind: 'service'; scope: 'root'; deps: 'Db' | 'Logger' | 'Metrics'; optional: 'Metrics'; … }
Db: { kind: 'service'; scope: 'root'; deps: 'Config'; … }
Config: { kind: 'service'; scope: 'root'; deps: never; … }
Logger: { kind: 'slot'; scope: 'root'; … }
Metrics: { kind: 'slot'; scope: 'root'; … }
}
}>
服务每个集装箱都有一次服务。 唯一的选择, scope,将一个服务置于比其依赖性更深处,例如每个请求一个工作单位,尽管它只需要数据库;更浅是编译错误.
资源和处置
工厂的背景镜 AsyncDisposableStack数字 :
ctx.use(resource)通过aDisposable或者说AsyncDisposable并归还它.ctx.adopt(value, dispose)随其处置者收养其他任何东西.ctx.defer(fn)运行回调,随时可以使用.ctx.signal当实例处理开始时中止, 即使它的工厂仍在运行。 通过它来慢化启动工作 :connect(url, { signal: ctx.signal })。 。 。 .
只有工厂采用的方法被处理掉;采用另一种服务方法是一个错误。 处置是最后的先发制人 内部瞄准镜在外边 等待工厂仍在运行.
集装箱
const app = await container()
.provide(Users, Mailer) // services; their dependencies are discovered
.bind(Logger, PinoLogger) // a slot to a service…
.bind(Config, { port: 3000 }) // …or to a value
.contribute(Middleware, Cors) // one item of a multi-binding
.with(infrastructure) // apply a composition function
.override(Db, fakeDb) // replace a node in the graph (tests)
.build()
provide() 将根起名来。 他们在行刑之外接触到的东西是在 build();只有执行所达到的才由需要它的第一个运行所创造(参见 处决) (中文(简体) ). 约束只有在能够达到的东西取决于它的槽时才生效.
如果工厂失败, build() 处置它创造和拒绝的东西 AggregateError。 。 。 .
构建者是坚持不懈的:每一次呼叫都会返回一个新的构建者. 写入状态下通用的可重复使用成分, 因此它们应用在一个链中的任何位置 :
const infrastructure = <S extends ContainerState>(b: ContainerBuilder<S>) =>
b.bind(Logger, ConsoleLogger).bind(Database, SqlDatabase)
container().with(infrastructure).bind(Config, config).provide(Checkout).build()
get() 以容器所创造的东西打入:
app.get(Users) // UsersApi
app.get(Mailer) // error: NotProvided<"Mailer">
app.get(Current) // error: NotVisible<"Current", "request">
app.get(Smtp) // error: CreatedOnDemand<"Smtp">, reached only through an execution
覆盖精确: 覆盖 Db 假的,没有别的 Db 还需要更多的资金.
范围
一个范围是比应用程序的寿命短,例如一个请求. open() 为它创建一个子容器,其范围槽被绑定在值上;一个缺失的值是一个编译错误来命名槽和需要它的东西.
await using request = await app.open(PerRequest, bind(Request, req))
request.get(CurrentUser) // created once per opened container
request.get(Users) // root-scope services are shared
总是处理什么 open() 返回时,与 await using 或在一个 finally容器将打开的东西保存到被处理为止,所以一个被丢弃的容器将一直停留到其母体被处理为止.
当容器打开时,就会产生一个范围中最深处的根部,也就是保持 get() 同步. 保持放靶工厂的便宜.
范围巢:
export const Session = scope('session')
export const Request = scope('request', Session)
const Sess = slot('Sess', { scope: Session })<SessionData>()
const Req = slot('Req', { scope: Request })<IncomingRequest>()
const Cart = service('Cart', { sess: Sess }, …) // lives in 'session'
const Handler = service('Handler', { req: Req, cart: Cart }, …) // lives in 'request'
await using session = await app.open(Session, bind(Sess, data))
await using request = await session.open(Request, bind(Req, req))
request.get(Handler) // sees request, session and root
session.get(Handler) // error: NotVisible<"Handler", "request">
app.open(Request, …) // error: NotChildScope<"request", "root">
一项服务在其附属单位中处于最深的范畴;在申报时,对不同分支的依赖被拒绝。 宣布每个范围一次,并在使用地点导入: 共享一个名称的两个范围无论在哪里相会都会被拒绝.
处决
工作服务本身开始和完成,如工作或信息处理器,是一种执行:范围,该范围或以上的条目,每个输入都运行供应。 受抚养人得到一个跑者.
const Job = scope('job')
const JobInput = slot('JobInput', { scope: Job })<{ id: string }>()
const JobRunner = service('JobRunner', { input: JobInput, db: Db }, ({ input, db }) => ({
run: () => db.process(input.id),
}))
const RunJob = execution('RunJob', { scope: Job, entry: JobRunner, inputs: { job: JobInput } })
const Scheduler = service('Scheduler', { runJob: RunJob }, ({ runJob }) => ({
schedule: (id: string) => runJob({ job: { id } }, (runner) => runner.run()),
}))
每次运行都会用给定的输入物打开放入范围的一个容器,将输入物交给尸体,一旦尸体安顿下来,便处置容器. 没有投入的处决只取出身体. build() 如果输入不包含条目需要的内容,则不可调用: IncompleteExecutions<{ RunJob: "Other" }>。 。 。 .
该条目在运行范围上达到的,且未提供达到的,由需要它的第一个运行创建,与后一个运行共享,并处置其范围;一个失败的创建在下一个需求下进行再审. get() 拒绝提供此类服务 CREATED_ON_DEMAND;提供它与容器一起创建.
货箱的跑道被解决了 处理该容器中止 signal 尸体收到作为第二个论据,等待尸体解决,然后处理跑道。 在等待之前,每跑道都会被中止 所以等待对方的跑道不会陷入僵局 车主在尸体被丢弃前被丢弃 DISPOSED其身体成功但处置失败的奔跑者以 DisposalError (单位:千美元)DISPOSAL_FAILED:身体的结果在 value 和处置失败 cause,所以一个不得重复承诺的工作的呼叫者可以登录并继续进行.
呼叫者取消运行 { signal } 尸体之后,就像 runJob({ job }, body, { signal: AbortSignal.timeout(5_000) })。在身体开始前中止,运行处理它所创造的东西,并以信号的理由拒绝;在身体运行时中止, 身体的运行 signal 以这个理由中止 运行会像尸体那样稳定 被取消的跑步从不运行它的身体.
多约束
const Middleware = slot('Middleware', { many: true })<Middleware>()
const Server = service('Server', { middleware: Middleware }, ({ middleware }) =>
createServer(middleware),
)
container().provide(Server).contribute(Middleware, Auth).contribute(Middleware, Cors).build()
对只读数组的贡献决心, 依次; 无, 改为 []。 。 。 . override() 以多节制取代整个集.
回顾
inspect() 描述每个节点:其范围、依赖性、约束性和实例状态.
错误
运行时间错误 DowelError有马厩的 code,例如 UNBOUND_SLOT, (中文(简体) ), CAPTIVE_DEPENDENCY, (中文(简体) ), CYCLE 或者说 DISPOSED,并命名依赖路径:
Slot 'Logger' is not bound. Needed by Api → Users → Logger.
类型良好的代码很少到达它们;它们是安全网 any 和动态组成.
页:1
- 周期数 只能来自约束性、超载性或执行性,其条目到达运行该条目的服务。 全部在建造时都被拒绝;前两个也按类型拒绝.
- 类型脚本 5.9 或以后 需要时间.
- 字形类型. TypeScript 在推论下保留嵌入函数的字面返回类型 :
service('S', () => ({ kind: () => 'a' }))推断() => "a"。当您想要更宽的返回类型时,请注明返回类型.