@doweljs/core

中文文档
0.5.0发布于 2026/10/27MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 2230

TypeScript 的完全依赖性注射.

中文使用文档
文档目录10 节
  1. 槽数
  2. 服务
  3. 资源和处置
  4. 集装箱
  5. 范围
  6. 处决
  7. 多约束
  8. 回顾
  9. 错误
  10. 页:1

@ 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) 通过a Disposable 或者说 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"。当您想要更宽的返回类型时,请注明返回类型.

版本与发布记录

@doweljs/core 共发布 6 个版本,首次发布于 2026/10/22;过去 12 个月发布了 6 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.5.0latest2026/10/2717 天前
0.4.02026/10/2618 天前
0.3.02026/10/2321 天前
0.2.02026/10/2221 天前
0.1.02026/10/2222 天前
0.0.0-stage2026/10/2222 天前

关于 @doweljs/core 的常见问题

@doweljs/core 是什么?

TypeScript 的完全依赖性注射.

如何安装 @doweljs/core?

在项目目录运行 npm install @doweljs/core,或使用 pnpm add / yarn add / bun add。

@doweljs/core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@doweljs/core 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@doweljs/core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@doweljs/core 可以商用吗?

@doweljs/core 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@doweljs/core 的下载量有多少?

根据 npm 官方下载统计,@doweljs/core 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,230 次,日均约 74 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@doweljs/core 还在维护吗?

最新版本 0.5.0 发布于 2026/10/27(17 天前);至今共发布 6 个版本,其中过去 12 个月发布了 6 个;首次发布于 2026/10/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。