npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

xpivot

v0.1.5

Published

Extensible dependency injection and plugin-based container framework

Readme

xpivot

可扩展的依赖注入 + 插件化容器框架

xpivot 是一个通用的模块容器框架,提供模块生命周期管理、服务依赖解析、接口式依赖注入和事件驱动分发机制。它不包含任何具体业务逻辑(HTTP、数据库、SSR 等),只负责"加载模块 → 解析依赖 → 实例化服务 → 发布事件"。

设计理念

一切皆插件

HTTP 服务器、路由系统、SSR 渲染、定时任务等,全部以插件形式被加载。核心容器只负责模块的加载、依赖解析和服务分发,不关心业务语义。

事件驱动分发

容器核心不硬编码任何接口名。各功能域插件通过 EventBusService 订阅 service:loaded 事件,按 serviceInterfaceTag 过滤自己关心的接口:

// HTTP 插件监听 IControllerService
eventBus.on('service:loaded', (event) => {
  if (event.serviceInterfaceTag !== 'IControllerService') return
  const routes = event.instance.getRoutes()
  // 注册路由到 Express...
})

// Cron 插件监听 ICronService
eventBus.on('service:loaded', (event) => {
  if (event.serviceInterfaceTag !== 'ICronService') return
  const jobs = event.instance.getCronJobs()
  // 启动定时任务...
})

每新增一种接口类型,只需新增事件订阅,无需修改容器核心代码。

三种依赖引用方式

| 方式 | 格式 | 说明 | |------|------|------| | 接口引用 | 'IEmailService' | 从接口注册表查找实现了该接口的服务,解耦模块间依赖 | | 模块内引用 | 'logger' | 在当前模块内查找,serviceTag 必须在当前模块中已声明 | | 跨模块引用 | 'data.dataProvider' | 从指定模块获取服务实例 |

解析优先级:接口引用 > 跨模块引用 > 模块内引用。

安装

npm install xpivot

快速开始

import { ModuleContainer, EVENT_BUS_INTERFACE_TAG, type IEventBusService } from 'xpivot'

// 1. 创建容器
const container = new ModuleContainer()

// 2. 定义一个监听模块(通过 DI 注入 EventBusService 订阅事件)
const listenerModule = {
  name: 'listener',
  serviceDefs: [
    {
      serviceTag: 'controllerListener',
      serviceImpl: (deps: Record<string, any>) => {
        const eventBus = deps.eventBusService as IEventBusService
        eventBus.on('service:loaded', (event) => {
          if (event.serviceInterfaceTag === 'IControllerService') {
            console.log(`Got routes from ${event.moduleName}`)
          }
        })
        return {}
      },
      dependencies: [EVENT_BUS_INTERFACE_TAG],
    },
  ],
}

// 3. 定义业务模块
const myModule = {
  name: 'myModule',
  serviceDefs: [
    {
      serviceTag: 'logger',
      serviceImpl: class Logger {
        log(msg: string) { console.log(msg) }
      },
    },
    {
      serviceTag: 'cache',
      serviceImpl: class Cache {
        constructor({ logger }: { logger: any }) {}
      },
      dependencies: ['logger'], // 模块内引用
    },
  ],
}

// 4. 加载模块(监听模块必须先于业务模块加载)
await container.loadModules([listenerModule, myModule])

// 5. 获取服务
const cache = container.getService('myModule', 'cache')

核心概念

Service(服务)

服务是容器管理的最小单元,可以是 class 或 factory function:

// class 形式
class UserService {
  constructor({ db }: { db: any }) {
    this.db = db
  }
  getUsers() { /* ... */ }
}

// factory function 形式
function createLogger(deps: { config: any }) {
  return {
    log(msg: string) { console.log(msg) }
  }
}

ServiceDef(服务定义)

描述一个服务的标识、实现和依赖:

{
  serviceTag: 'userService',        // 模块内唯一标识
  serviceImpl: UserService,         // class 或 factory function
  dependencies: ['mdb.db'],        // 可选:依赖的其他服务
  serviceInterfaceTag: 'IUserService', // 可选:实现的接口契约
}

Module(模块)

一组相关服务的集合,可以声明对其他模块的依赖:

{
  name: 'authServer',
  moduleDependencies: ['mdb'],      // 可选:依赖的其他模块
  serviceDefs: [/* ServiceDef[] */],
}

Container(容器)

管理模块生命周期的核心,提供模块加载/卸载、服务获取、事件发布等能力。

EventBusService(事件总线)

事件驱动的核心。容器内置 EventBusService 实例,在服务实例化后自动 emit service:loaded 事件,各功能域插件通过订阅事件按 serviceInterfaceTag 过滤自己关心的服务。

依赖注入

模块内引用

同一模块内的服务可以互相引用:

const module = {
  name: 'app',
  serviceDefs: [
    {
      serviceTag: 'logger',
      serviceImpl: class Logger { /* ... */ },
    },
    {
      serviceTag: 'userService',
      serviceImpl: class UserService {
        constructor({ logger }: { logger: any }) {
          this.logger = logger
        }
      },
      dependencies: ['logger'], // ← 模块内引用
    },
  ],
}

跨模块引用

通过 moduleName.serviceTag 格式引用其他模块的服务:

const authModule = {
  name: 'authServer',
  moduleDependencies: ['mdb'], // ← 声明模块依赖
  serviceDefs: [
    {
      serviceTag: 'authController',
      serviceImpl: class AuthController {
        constructor({ mongoDBService }: { mongoDBService: any }) {
          this.db = mongoDBService
        }
      },
      dependencies: ['IMongoDBService'], // ← 接口引用(推荐)
      // 或:dependencies: ['mdb.mongoDBService'], // ← 跨模块引用
    },
  ],
}

注意:使用跨模块引用时,必须在 moduleDependencies 中声明依赖的模块名,否则加载时会抛出错误。

接口引用

通过接口名引用服务,无需关心服务来自哪个模块:

// mdb 模块注册了 IMongoDBService 接口
const mdbModule = {
  name: 'mdb',
  serviceDefs: [
    {
      serviceTag: 'mongoDBService',
      serviceImpl: MongoDBService,
      serviceInterfaceTag: 'IMongoDBService', // ← 注册接口
    },
  ],
}

// auth 模块通过接口引用 mdb 的服务
const authModule = {
  name: 'authServer',
  moduleDependencies: ['mdb'],
  serviceDefs: [
    {
      serviceTag: 'initService',
      serviceImpl: class InitService {
        constructor({ mongoDBService }: { mongoDBService: any }) {
          this.db = mongoDBService
        }
      },
      dependencies: ['IMongoDBService'], // ← 接口引用
    },
  ],
}

接口引用的优先级最高,框架会先查接口注册表,未命中再按跨模块/模块内引用解析。

依赖注入的执行流程

loadModules([moduleA, moduleB])
  │
  ├── 加载 moduleA
  │     ├── 检查 moduleDependencies 是否已加载
  │     ├── 遍历 serviceDefs,逐个实例化服务
  │     │     ├── 解析 dependencies(接口 → 跨模块 → 模块内)
  │     │     ├── 注入依赖对象 { serviceTag: instance }
  │     │     └── new ServiceImpl(depsRecord) 或 factory(depsRecord)
  │     ├── 注册到接口注册表(如果有 serviceInterfaceTag)
  │     ├── emit service:loaded 事件(EventBusService 通知所有订阅者)
  │     └── 调用服务的 initialize()(如果存在)
  │
  └── 加载 moduleB(同上)

事件驱动分发

工作原理

  1. 订阅阶段:各功能域插件在服务的 initialize() 方法中通过 EventBusService 订阅 service:loaded 事件
  2. 加载阶段:容器实例化服务后,自动 emit service:loaded 事件
  3. 分发阶段:订阅者根据 serviceInterfaceTag 过滤,处理自己关心的服务
eventBus.on('service:loaded', handler)
         │
         ▼
    EventBusService
    ┌──────────────────────────────────┐
    │ Map<event, Set<listener>>         │
    │  'service:loaded' → {fnA, fnB}   │
    │  'module:loaded'  → {fnC}        │
    └──────────────────────────────────┘
         │
         ▼
    loadModules([authModule])
         │
         ▼
    实例化 authController (serviceInterfaceTag: 'IControllerService')
         │
         ▼
    emit 'service:loaded' 事件
         │
         ▼
    handler 过滤 serviceInterfaceTag → 收集路由、注册到 Express

订阅示例

// 通过 DI 注入 EventBusService 和 IHttpServerService(在构造函数阶段获取)
class ControllerListener {
  constructor({ eventBusService, httpServerService }: {
    eventBusService: IEventBusService
    httpServerService: IHttpServerService
  }) {
    eventBusService.on('service:loaded', (event) => {
      if (event.serviceInterfaceTag !== 'IControllerService') return
      const routes = event.instance.getRoutes()
      httpServerService.addRoutes(routes)
    })
  }
}

const controllerListenerDef = {
  serviceTag: 'controllerListener',
  serviceImpl: ControllerListener,
  dependencies: [EVENT_BUS_INTERFACE_TAG, HTTP_SERVER_INTERFACE_TAG],
}

多播

一个事件可以注册多个监听器,所有匹配的监听器都会被调用:

eventBus.on('service:loaded', (event) => {
  if (event.serviceInterfaceTag !== 'IControllerService') return
  // 监听器 A:收集路由
})

eventBus.on('service:loaded', (event) => {
  if (event.serviceInterfaceTag !== 'IControllerService') return
  // 监听器 B:记录日志
})

模块生命周期

状态流转

                 ┌──────────┐
                 │ Unloaded │
                 └────┬─────┘
                      │ loadModules()
                      ▼
                 ┌──────────┐
         ┌───────│ Loading  │
         │       └────┬─────┘
         │            │
      error          成功
         │            │
         ▼            ▼
    ┌──────────┐  ┌──────────┐
    │  Error   │  │  Loaded  │
    └──────────┘  └────┬─────┘
                       │ unloadModule()
                       ▼
                 ┌───────────┐
                 │ Unloading │
                 └─────┬─────┘
                       │
                       ▼
                 ┌──────────┐
                 │ Unloaded │
                 └──────────┘

生命周期事件

容器在模块生命周期的关键点通过 EventBusService 自动 emit 事件,业务模块可订阅监听:

// 定义一个监听模块,通过 DI 注入 EventBusService
const lifecycleLoggerModule = {
  name: 'lifecycleLogger',
  serviceDefs: [
    {
      serviceTag: 'lifecycleLogger',
      serviceImpl: (deps: Record<string, any>) => {
        const eventBus = deps.eventBusService as IEventBusService
        eventBus.on('module:loaded', (event) => {
          console.log(`Module loaded: ${event.moduleName} (${event.module.services.size} services)`)
        })
        eventBus.on('module:unloaded', (event) => {
          console.log(`Module unloaded: ${event.moduleName}`)
        })
        eventBus.on('module:error', (event) => {
          console.error(`Module ${event.moduleName} error:`, event.error)
        })
        return {}
      },
      dependencies: [EVENT_BUS_INTERFACE_TAG],
    },
  ],
}

// 先加载监听模块,再加载业务模块
await container.loadModules([lifecycleLoggerModule, ...businessModules])

服务初始化

如果服务实例有 initialize() 方法,容器会在模块加载完成后自动调用:

class MongoDBService {
  async initialize() {
    // 连接数据库
    this.client = new MongoClient(uri)
    await this.client.connect()
  }
}

模块卸载

卸载模块时,容器会:

  1. 对每个服务 emit service:unloaded 事件(在清理前发出,订阅者仍可访问实例)
  2. 清理服务实例
  3. 从接口注册表中移除该模块注册的接口
  4. 发出 module:unloaded 事件
await container.unloadModule('authServer')

模块重载

重新加载模块会先卸载再加载,支持传入新的模块定义实现热更新:

// 使用原始模块定义重载
await container.reloadModule('authServer')

// 使用新模块定义重载(热更新)
await container.reloadModule('authServer', newAuthModule)

事件系统

容器提供内置的 EventBusService 事件总线,替代原先的 on/off 方法。EventBusService 在容器构造时自动创建并注册为 IEventBusService 接口,业务模块可通过依赖注入获取:

import { EVENT_BUS_INTERFACE_TAG, type IEventBusService } from 'xpivot'

// 通过 dependencies 接口引用注入
class MyService {
  constructor({ eventBusService }: { eventBusService: IEventBusService }) {
    this.eventBus = eventBusService
  }
  // 在构造函数中订阅事件
}

const myModule = {
  name: 'myModule',
  serviceDefs: [
    {
      serviceTag: 'myService',
      serviceImpl: MyService,
      dependencies: [EVENT_BUS_INTERFACE_TAG], // ← 接口引用注入
    },
  ],
}

容器内置事件

容器在模块生命周期关键点自动 emit 以下事件:

模块级事件

| 事件名 | 载荷 | 说明 | |------|------|------| | module:loaded | { moduleName, module, timestamp } | 模块加载完成 | | module:unloaded | { moduleName, timestamp } | 模块卸载完成 | | module:error | { moduleName, error, timestamp } | 模块加载错误 | | module:state_changed | { moduleName, oldState, newState, timestamp } | 模块状态变化 |

服务级事件

| 事件名 | 载荷 | 说明 | |------|------|------| | service:loaded | { moduleName, serviceTag, serviceInterfaceTag?, instance, loadedModule } | 服务实例化完成 | | service:unloaded | { moduleName, serviceTag, serviceInterfaceTag?, instance, loadedModule } | 服务即将被清理(卸载前发出,订阅者仍可访问实例) |

统一分发机制:service:loaded 事件是容器唯一的服务分发渠道。所有功能域插件(HTTP、Cron、SSR 等)通过 EventBus 订阅此事件,按 serviceInterfaceTag 过滤处理。不再需要 registerServiceHandler() 等注册式分发 API。

订阅示例

// 在服务中订阅容器事件
class LifecycleLogger {
  constructor({ eventBusService }: { eventBusService: IEventBusService }) {
    // 监听模块加载完成
    eventBusService.on('module:loaded', (event) => {
      console.log(`[${event.moduleName}] loaded at ${new Date(event.timestamp)}`)
    })

    // 监听模块错误
    eventBusService.on('module:error', (event) => {
      console.error(`[${event.moduleName}] error:`, event.error)
    })

    // 监听模块卸载
    eventBusService.on('module:unloaded', (event) => {
      console.log(`[${event.moduleName}] unloaded`)
    })

    // 监听状态变化
    eventBusService.on('module:state_changed', (event) => {
      console.log(`[${event.moduleName}] ${event.oldState} → ${event.newState}`)
    })
  }
}

自定义事件

业务模块也可以通过 EventBusService 发布自定义事件,供其他模块消费:

// 发布方
const off = eventBusService.on('my:custom-event', (payload) => {
  console.log('Received:', payload)
})

// 订阅方
await eventBusService.emit('my:custom-event', { key: 'value' })

// 取消订阅
off()
// 或
eventBusService.off('my:custom-event', listener)

API 参考

ModuleContainer

模块操作

| 方法 | 参数 | 返回值 | 说明 | |------|------|--------|------| | loadModules(modules) | IModule[] | Promise<void> | 批量加载模块(跳过已加载的) | | unloadModule(name) | string | Promise<void> | 卸载模块,清理服务和接口注册 | | reloadModule(name, newModule?) | string, IModule? | Promise<void> | 重新加载模块(先卸载再加载) |

查询方法

| 方法 | 参数 | 返回值 | 说明 | |------|------|--------|------| | isModuleLoaded(name) | string | boolean | 检查模块是否已加载 | | getLoadedModule(name) | string | ILoadedModule \| undefined | 获取已加载模块 | | getModuleState(name) | string | ModuleState | 获取模块状态 | | getService(moduleName, serviceTag) | string, string | T | 获取服务实例(不存在则抛错) | | getAllServices() | - | Map<string, IService> | 扁平服务池(活视图,键为 `${moduleName}.${serviceTag}`,含全部已加载模块的服务;随模块加载/卸载实时更新,适合作为请求上下文服务池来源) |

事件监听:通过 DI 注入 IEventBusService(dependencies: [EVENT_BUS_INTERFACE_TAG])获取事件总线实例。

EventBusService

| 方法 | 参数 | 返回值 | 说明 | |------|------|--------|------| | on(event, listener) | string, (payload: any) => void \| Promise<void> | () => void | 订阅事件,返回取消订阅函数 | | once(event, listener) | string, (payload: any) => void \| Promise<void> | () => void | 订阅事件(只触发一次) | | off(event, listener) | string, Function | void | 取消订阅 | | emit(event, payload?) | string, any | Promise<void> | 发布事件 | | clear(event?) | string? | void | 移除指定事件(或所有事件)的监听器 | | listenerCount(event) | string | number | 获取监听器数量 |

工具函数

| 函数 | 参数 | 返回值 | 说明 | |------|------|--------|------| | instantiateService(def, existingServices, loadedModules, interfaceRegistry?) | IServiceDef, Map, Map, IInterfaceRegistry? | IService | 实例化服务,解析依赖 | | parseDependencyRef(ref) | string | { moduleName?: string, serviceTag: string } | 解析依赖引用格式 | | isConstructor(fn) | any | boolean | 判断是否为 class 构造器 | | isInterfaceRef(ref) | string | boolean | 判断是否为接口引用(大写字母开头且不含点号) |

类型定义

IServiceDef

interface IServiceDef {
  serviceTag: string           // 模块内唯一标识
  serviceImpl: IServiceMaker  // class 或 factory function
  dependencies?: string[]      // 依赖的其他服务
  serviceInterfaceTag?: string // 接口契约标签
}

IModule

interface IModule {
  name: string                 // 模块名称(全局唯一)
  moduleDependencies?: string[] // 依赖的其他模块
  serviceDefs: IServiceDef[]   // 服务定义列表
}

ILoadedModule

interface ILoadedModule {
  name: string
  state: ModuleState
  services: Map<string, IService>
  sourceModule?: IModule
  loadedAt?: number
}

IEventBusService

interface IEventBusService {
  on(event: string, listener: (payload: any) => void | Promise<void>): () => void
  once(event: string, listener: (payload: any) => void | Promise<void>): () => void
  off(event: string, listener: (payload: any) => void | Promise<void>): void
  emit(event: string, payload?: any): void | Promise<void>
  clear(event?: string): void
  listenerCount(event: string): number
}

ModuleState

enum ModuleState {
  Unloaded = 'unloaded',
  Loading = 'loading',
  Loaded = 'loaded',
  Error = 'error',
  Unloading = 'unloading',
}

完整示例

import { ModuleContainer, EVENT_BUS_INTERFACE_TAG, type IEventBusService, type IModule, type IServiceDef } from 'xpivot'

// ==================== 定义模块 ====================

// 数据库模块
const mongoDBService: IServiceDef = {
  serviceTag: 'mongoDBService',
  serviceImpl: class MongoDBService {
    async initialize() {
      console.log('[DB] Connecting...')
    }
    getCollection(name: string) { /* ... */ }
  },
  serviceInterfaceTag: 'IMongoDBService',
}

const dbModule: IModule = {
  name: 'db',
  serviceDefs: [mongoDBService],
}

// 日志模块
const loggerService: IServiceDef = {
  serviceTag: 'logger',
  serviceImpl: class Logger {
    log(msg: string) { console.log(`[LOG] ${msg}`) }
  },
  serviceInterfaceTag: 'ILoggerService',
}

const loggerModule: IModule = {
  name: 'logger',
  serviceDefs: [loggerService],
}

// 用户模块(依赖 db 和 logger)
const userService: IServiceDef = {
  serviceTag: 'userService',
  serviceImpl: class UserService {
    constructor({ mongoDBService, logger }: any) {
      this.db = mongoDBService
      this.logger = logger
    }
    async getUsers() {
      this.logger.log('Fetching users...')
      return this.db.getCollection('users')
    }
  },
  dependencies: ['IMongoDBService', 'ILoggerService'], // 接口引用
  serviceInterfaceTag: 'IUserService',
}

const userModule: IModule = {
  name: 'user',
  moduleDependencies: ['db', 'logger'],
  serviceDefs: [userService],
}

// ==================== 使用容器 ====================

const container = new ModuleContainer()

// 按依赖顺序加载模块
await container.loadModules([dbModule, loggerModule, userModule])


// 一般不从外部获取service,但目前提供这样的接口
// 获取服务
const userService = container.getService('user', 'userService')
const db = container.getService('db', 'mongoDBService')

// 使用服务
const users = await userService.getUsers()

// 卸载模块
await container.unloadModule('user')