npm package: @libeilong/model-manager
Description: 一个高性能、内存安全且类型友好的 TypeScript 领域模型管理库。专为复杂的前端应用设计，支持 **自动内存管理 (WeakRef)**、**响应式框架集成 (Vue/MobX)**、**关系自动 hydrate**、**模型家族多态分发** 以及 **精细化的数据合并策略**。
Author: lblblong
Latest version: 0.2.1

# Model Manager

一个高性能、内存安全且类型友好的 TypeScript 领域模型管理库。专为复杂的前端应用设计，支持 **自动内存管理 (WeakRef)**、**响应式框架集成 (Vue/MobX)**、**关系自动 hydrate**、**模型家族多态分发** 以及 **精细化的数据合并策略**。

## ✨ 特性

- **🚀 内存安全**：基于 `WeakRef` 和 `FinalizationRegistry` 实现自动垃圾回收，防止内存泄漏。
- **⚡️ 响应式友好**：支持 **原地修改 (In-Place Mutation)**，完美适配 Vue (Reactive/Ref) 和 MobX 等响应式系统，保持引用稳定性。
- **🔄 智能合并**：提供 `replace`、`merge`、`append` 多种策略处理嵌套关系和数组更新。
- **🔗 关系管理**：轻松定义一对一、一对多关系，自动处理循环引用和延迟加载。
- **🧠 多态分发**：支持通过 `@SubModelOf` 按原始数据自动落到正确业务子类。
- **🧱 上下文隔离**：支持通过 `familyRoot` 建立业务线级缓存隔离与关系传播边界。
- **💎 类型增强**：强大的 TypeScript 类型推导，自动生成 `create` 方法的输入类型定义。
- **📦 序列化控制**：支持多种序列化策略（仅主键、完整对象、混合模式），灵活应对 API 提交需求。

## 📦 安装

```bash
npm install @libeilong/model-manager
# 或者
yarn add @libeilong/model-manager
```

## 🚀 快速开始

如果你的场景是 nexuos 这类树节点 / 业务子类分发，建议直接跳到后面的“多态子类分发”章节。

### 1\. 定义模型

你可以传入自定义的基类（如 Vue 的响应式基类），库会自动混入（Mixin）模型管理能力。

```typescript
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\. 使用模型

```typescript
// 创建数据
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`: 匹配函数，命中后会自动实例化为当前子类。

---

## 多态子类分发

### 默认模式：一个家族内自动落到正确子类

```typescript
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
```

这个模式下：

1. 关系字段仍然可以声明为基类
2. hydrate 时会根据原始数据自动落到正确业务子类
3. 同一家族下会共享缓存，避免出现同 id 的多个实例

### 高级模式：按业务线隔离上下文

当同一个基础模型在多个业务域里需要不同语义，或者希望关系树严格留在某个业务上下文内时，可以开启 `familyRoot`。

```typescript
@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 {}
```

这个模式下：

1. 不同 `familyRoot` 会拥有各自的缓存空间
2. 相同 id 在不同业务线上不会误命中同一实例
3. 关系 hydrate 会沿业务线根类继续传播，而不是回落到通用基类语义

## 设计边界

1. `@SubModelOf` 解决的是“创建或 hydrate 时自动落到哪个子类”
2. `familyRoot` 解决的是“缓存和关系传播是否需要按业务线上下文隔离”
3. 库不会自动为 `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?)`**
  增量合并数据到当前实例。**这是实现响应式更新的核心。**

  ```typescript
  user.merge(
    { name: 'Bob' },
    {
      // 数组合并策略:
      // 'replace' (默认): 以新数组为准，同步更新内容
      // 'merge': 保留本地多余项，合并新项
      // 'append': 追加新项
      arrayStrategy: 'replace',
    },
  )
  ```

- **`hooks` 执行顺序**

  ```typescript
  @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-afterCreate
  ```

- **`serialize(options?)`**
  将模型转换为普通 JSON 对象。

  - 支持处理循环引用（自动降级为主键）。
  - 根据 `@Model` 配置决定关系字段是输出 ID 还是完整对象。

- **`load(opts?)`**
  触发数据加载。需要用户在类上实现静态 `_fetchData` 方法。

  ```typescript
  // 在类定义中实现
  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`**：视为“合并”。保留本地所有项，并把后端返回的数据更新进去。适用于“加载更多”或“增量推送”场景。