@libeilong/model-manager
v0.3.0
Published
一个高性能、内存安全且类型友好的 TypeScript 领域模型管理库。专为复杂的前端应用设计,支持 **自动内存管理 (WeakRef)**、**响应式框架集成 (Vue/MobX)**、**关系自动 hydrate**、**模型家族多态分发** 以及 **精细化的数据合并策略**。
Downloads
274
Readme
Model Manager
一个高性能、内存安全且类型友好的 TypeScript 领域模型管理库。专为复杂的前端应用设计,支持 自动内存管理 (WeakRef)、响应式框架集成 (Vue/MobX)、关系自动 hydrate、模型家族多态分发 以及 精细化的数据合并策略。
✨ 特性
- 🚀 内存安全:基于
WeakRef和FinalizationRegistry实现自动垃圾回收,防止内存泄漏。 - ⚡️ 响应式友好:支持 原地修改 (In-Place Mutation),完美适配 Vue (Reactive/Ref) 和 MobX 等响应式系统,保持引用稳定性。
- 🔄 智能合并:提供
replace、merge、append多种策略处理嵌套关系和数组更新。 - 🔗 关系管理:轻松定义一对一、一对多关系,自动处理循环引用和延迟加载。
- 🧠 多态分发:支持通过
@SubModelOf按原始数据自动落到正确业务子类。 - 🧱 上下文隔离:支持通过
familyRoot建立业务线级缓存隔离与关系传播边界。 - 💎 类型增强:强大的 TypeScript 类型推导,自动生成
create方法的输入类型定义。 - 📦 序列化控制:支持多种序列化策略(仅主键、完整对象、混合模式),灵活应对 API 提交需求。
📦 安装
npm install @libeilong/model-manager
# 或者
yarn add @libeilong/model-manager🚀 快速开始
如果你的场景是 nexuos 这类树节点 / 业务子类分发,建议直接跳到后面的“多态子类分发”章节。
1. 定义模型
你可以传入自定义的基类(如 Vue 的响应式基类),库会自动混入(Mixin)模型管理能力。
import { createBaseModel, Model, PrimaryKey, Relation, PK_PROP } from '@libeilong/model-manager'
// 模拟一个响应式基类 (适配 Vue/MobX 等)
class ReactiveBase {}
// 1. 创建基础模型,混入能力
@Model({ cache: false, serialization: 'hybrid' })
class BaseModel<T = 'id'> extends createBaseModel(ReactiveBase) {
// 声明 PK_PROP 用于类型推导窄化
declare [PK_PROP]: T
@PrimaryKey()
id: number = 0
createdAt: Date = new Date()
}
// 2. 定义 User 模型
@Model({ cache: true, mergeOnCacheHit: true, serialization: 'pk' })
class User extends BaseModel {
name: string = ''
email?: string
// 定义一对多关系,第二个参数 true 表示数组
@Relation(() => Order, true)
orders: Order[] = []
// 普通数组(非领域模型)
posts: Array<{ id: number; title: string }> = []
}
// 3. 定义 Order 模型
@Model({ cache: true, serialization: 'object' })
class Order extends BaseModel<'on'> {
// 自定义主键字段名
@PrimaryKey()
on: string = ''
title: string = ''
price?: number
// 定义一对一关系
@Relation(() => User)
creator!: User
}2. 使用模型
// 创建数据
const user = User.create({
id: 1,
name: 'Alice',
orders: [{ on: 'ORD-001', title: 'Book', price: 100 }],
})
// user.orders[0] 已经是 Order 类的实例
console.log(user.orders[0] instanceof Order) // true
// 再次创建相同 ID 的 User (缓存命中)
const user2 = User.create({
id: 1,
name: 'Alice Updated', // mergeOnCacheHit: true,属性会自动更新
})
console.log(user === user2) // true,引用完全相同
console.log(user.name) // "Alice Updated",实现原地更新📖 API 文档
装饰器
@Model(options: ModelOptions)
配置类的全局行为。
| 选项 | 类型 | 说明 | 默认值 |
| :---------------- | :----------------------------- | :------------------------------------------------------- | :--------- |
| cache | boolean | 是否启用实例缓存 | false |
| cacheStrategy | 'weak' \| 'strong' | 缓存策略。weak 使用弱引用(推荐),strong 为强引用。 | 'weak' |
| serialization | 'hybrid' \| 'pk' \| 'object' | 默认序列化策略。 | 'hybrid' |
| mergeOnCacheHit | boolean | 当 create 命中缓存时,是否自动合并新数据。 | false |
| primaryKey | string | 主键字段名(通常建议使用 @PrimaryKey 装饰器)。 | - |
| familyRoot | Class \| () => Class | 高级模式下指定业务线根类,用于上下文级缓存隔离。 | - |
配置继承规则:
- 普通配置字段使用“子类覆盖父类”的规则。
hooks.beforeCreate会沿继承链按“父类 -> 子类”顺序组合执行。hooks.afterCreate会沿继承链按“父类 -> 子类”顺序组合执行。- 实例上的
afterCreate()会在所有配置级afterCreate执行完成后再执行。
@PrimaryKey()
标记类的属性为主键。库依赖主键进行缓存去重。
@Relation(targetGetter, isArray?, config?)
定义模型间的关联关系。
targetGetter:() => Class。使用函数返回类,解决循环引用问题。isArray:boolean。是否为数组关系。config: 序列化配置等。
@SubModelOf(baseCtor, match)
把当前类注册为某个模型家族下的业务子类。
baseCtor: 作为分发入口的基类或业务线根类。match: 匹配函数,命中后会自动实例化为当前子类。
多态子类分发
默认模式:一个家族内自动落到正确子类
import {
createBaseModel,
Model,
PK_PROP,
PrimaryKey,
Relation,
SubModelOf,
} from '@libeilong/model-manager'
@Model({ cache: true, mergeOnCacheHit: true })
class FsNode extends createBaseModel() {
declare [PK_PROP]: 'id'
@PrimaryKey()
id: string = ''
mimetype = ''
name = ''
@Relation(() => FsNode, true)
children: FsNode[] = []
}
@SubModelOf(FsNode, (raw) => raw?.mimetype === 'scene')
class SceneFsNode extends FsNode {
get assets() {
return this.children.filter((it) => it.mimetype === 'scene-asset')
}
}
const node = FsNode.create({
id: 'scene-1',
mimetype: 'scene',
children: [{ id: 'asset-1', mimetype: 'scene-asset' }],
})
console.log(node instanceof SceneFsNode) // true
console.log(node.assets.length) // 1这个模式下:
- 关系字段仍然可以声明为基类
- hydrate 时会根据原始数据自动落到正确业务子类
- 同一家族下会共享缓存,避免出现同 id 的多个实例
高级模式:按业务线隔离上下文
当同一个基础模型在多个业务域里需要不同语义,或者希望关系树严格留在某个业务上下文内时,可以开启 familyRoot。
@Model({ cache: true, mergeOnCacheHit: true })
class FsNode extends BaseModel {
@Relation(() => FsNode, true)
children: FsNode[] = []
}
@Model({ familyRoot: () => VideoLineFsNode })
class VideoLineFsNode extends FsNode {
@Relation(() => VideoLineFsNode, true)
children: VideoLineFsNode[] = []
}
@SubModelOf(VideoLineFsNode, (raw) => raw?.mimetype === 'scene')
class SceneFsNode extends VideoLineFsNode {}这个模式下:
- 不同
familyRoot会拥有各自的缓存空间 - 相同 id 在不同业务线上不会误命中同一实例
- 关系 hydrate 会沿业务线根类继续传播,而不是回落到通用基类语义
设计边界
@SubModelOf解决的是“创建或 hydrate 时自动落到哪个子类”familyRoot解决的是“缓存和关系传播是否需要按业务线上下文隔离”- 库不会自动为
children反向补parent,如果原始数据里没有该字段,就不会凭空生成
核心方法 (Mixin)
继承 createBaseModel 后,你的类将获得以下静态方法和实例方法。
静态方法
static create(data, options?)创建或获取模型实例。如果缓存中存在且开启了缓存,则返回已有实例。data: 强类型的输入对象(支持递归嵌套)。options.merge: 强制覆盖类配置的合并行为。
static createReference(pk)仅通过主键创建一个“引用状态”的实例(标记为isLoaded: false),常用于延迟加载场景。static find(pk)从缓存中查找实例。static findAll()获取当前模型家族下已缓存的所有实例。static clearCache()清空当前模型家族下的缓存。常用于测试或显式重置场景。
实例方法
merge(data, options?)增量合并数据到当前实例。这是实现响应式更新的核心。user.merge( { name: 'Bob' }, { // 数组合并策略: // 'replace' (默认): 以新数组为准,同步更新内容 // 'merge': 保留本地多余项,合并新项 // 'append': 追加新项 arrayStrategy: 'replace', }, )hooks执行顺序@Model({ hooks: { beforeCreate: (data) => ({ ...data, stage: ['parent-before'] }), }, }) class ParentModel extends BaseModel {} @Model({ hooks: { beforeCreate: (data) => ({ ...data, stage: [...(data.stage || []), 'child-before'], }), afterCreate: (instance) => { instance.stage.push('child-after') }, }, }) class ChildModel extends ParentModel { stage: string[] = [] afterCreate() { this.stage.push('instance-after') } } const model = ChildModel.create({ id: 1 }) // beforeCreate: parent -> child // afterCreate: parent-config -> child-config -> instance-afterCreateserialize(options?)将模型转换为普通 JSON 对象。- 支持处理循环引用(自动降级为主键)。
- 根据
@Model配置决定关系字段是输出 ID 还是完整对象。
load(opts?)触发数据加载。需要用户在类上实现静态_fetchData方法。// 在类定义中实现 static async _fetchData(pk, context) { return await api.getUser(pk); } // 调用 await user.load(); await user.load({ force: true });
💡 核心概念与最佳实践
1. 响应式集成 (Vue/MobX)
本库的设计初衷之一是配合现代前端框架。
- 原地修改 (In-Place Mutation):
merge方法会直接修改对象属性,而不是替换对象引用。 - 基类继承:通过
createBaseModel(Base),你可以传入Vue的类或其他基类,使得所有生成的 Model 实例天然具备响应式能力。
2. 内存管理机制
为了防止随着应用运行 Model 实例无限堆积,默认使用 WeakRef + FinalizationRegistry。
- 当 UI 组件卸载,不再持有 Model 引用时,GC 会自动回收 Model。
- 库会自动监听到回收事件,并从内部缓存 Map 中清理对应的 Key,防止内存泄漏。
3. 数据合并策略 (replace vs merge)
在处理数组关系时(例如更新 user.orders):
replace(默认):视为“同步”。后端返回的列表就是最新状态。本地多余的项会被移除,存在的项会原地更新属性,新的项会追加。merge:视为“合并”。保留本地所有项,并把后端返回的数据更新进去。适用于“加载更多”或“增量推送”场景。
