@expander/mongoose-tracker

中文文档
1.8.2发布于 2026/01/13Apache-2.0TypeScriptESM + CJS近 30 天下载 2105

是一个 mongoose 插件, 它会自动跟踪文档创建、 更新和可选修改部分字段的时间

中文使用文档
文档目录21 节
  1. 目录
  2. 特征
  3. 安装
  4. 使用量
  5. 插件配置
  6. 选项
  7. 使用量
  8. 使用 _changedBy 记录更改
  9. 关键说明
  10. 《公约》的重要性 _display 外地
  11. 示例
  12. 跟踪阵列元素中的嵌入物体
  13. 日志
  14. 使用内建登录
  15. 日志输出示例
  16. 使用自定义记录器
  17. 使用皮诺
  18. 跟踪阵列字段
  19. 有引用的矩阵元素 _display
  20. 捐款
  21. 法律

巨鹅追踪器

巨鹅追踪器 是一个多功能的 Mongoose 插件,可以自动跟踪文档的创建和更新。 它仔细地记录了特定字段的更改,包括嵌入字段,数组,并引用了其他文档,提供了全面的修改历史. 此插件会增强您 MongoDB 收藏中的数据完整性和可审计性 .

受到 动物可追踪 软件包, 巨鹅追踪器 提供更好的功能和定制 与你的Mongoose计划无缝结合.

目录


特征

  • 跟踪您 Mongoose 文档中的字段变化.
  • 支持嵌入对象.
  • 支持数组元素(检测已添加/删除的项目).
  • 支持引用( I)ObjectId)到其他Mongoose文件(如果有的话,将存储“显示”值).
  • 允许忽略某些字段(例如:. _id, (中文(简体) ), __v(见a/c.5/49/70,附件).
  • 保持历史条目可配置的最大长度.

安装

安装 巨鹅追踪器 通过 npm :

npm install @expander/mongoose-tracker

或

yarn add @expander/mongoose-tracker

使用量

插件配置

import mongoose, { Schema } from "mongoose";
import mongooseTracker from "@expander/mongoose-tracker"; // Adjust import based on your actual package name

const YourSchema = new Schema({
  title: String,
  orders: [
    {
      orderId: String,
      timestamp: Date,
      items: [ { name: String, price:Number, .... }, ],
      // ...other fields...
    }
  ],
  user: {
    firstName: String,
    lastName:String,
    // ...other fields...
  }
  // ...other fields...
});

// Apply the plugin with options
YourSchema.plugin(mongooseTracker, {
  name: "history",
  fieldsToTrack: [
    "title",
    "user.firstName",
    "user.lastName",
    "orders.$.items.$.price",
    "orders.$.items.$.name",
    "orders.$.timestamp",
  ],
  fieldsNotToTrack: ["history", "_id", "__v", "createdAt", "updatedAt"],
  limit: 50,
  instanceMongoose: mongoose, //optional.
  logLevel: 'info', // Optional: 'debug' | 'info' | 'warn' | 'error' | 'none' (default: 'none')
});

export default mongoose.model("YourModel", YourSchema);

它会做什么

  1. 添加历史字段: 添加一个调用字段 history (默认)到您的计划, 存储变化的历史 .

  2. 监视文档更改: 监视期间的变化 save 业务和基于特定查询的更新findOneAndUpdate, (中文(简体) ), updateOne, (中文(简体) ), updateMany) (中文(简体) ).

    说明: 当前, 插件最符合 save 用于跟踪变化的方法。 我们正积极努力加强对其他更新钩的支持,以确保在所有更新行动中全面跟踪变化.

  3. 详细更改:每次更改时记录一个条目,存储更改的用户/系统(_changedBy如果提供的话.

选项

选项类型默认说明
namestring'history'存储历史记录的阵列字段名称 .
fieldsToTrackstring[][] (空虚)要跟踪的字段模式列表 。 若为空相, 所有字段 (单位:千美元) fieldsNotToTrack)被跟踪.
fieldsNotToTrackstring[]['history', '_id', '_v', '__v', 'createdAt', 'updatedAt', 'deletedAt', '_display']字段/路径 排除 从跟踪.
limitnumber50要保留在历史阵列中的最大历史项数 .
instanceMongoosemongoose默认导入 mongoose 实例如果你有另外的Mongoose实例的话,就取消.
logLevelstring'none'日志级别 : 'debug', (中文(简体) ), 'info', (中文(简体) ), 'warn', (中文(简体) ), 'error',或 'none'。 。 。 .
loggerLogger默认控制台日志自定义日志实例(如温斯顿,皮诺等).

字段图案

  • A 类 点 (单位:千美元).匹配子域.
    • 譬如说. user.address.city 音轨更改 city 内字段 user.address。 。 。 .
  • A 类 美元数 签名( e)$与“任何阵列索引”相匹配
    • 譬如说. contacts.$.phone 音轨更改 phone 字段为 任意 参数 contacts 阵列.

使用量

使用任何 Mongoose 插件 :

const mongoose = require("mongoose");
const mongooseTracker = require("@expander/mongoose-tracker");

const { Schema } = mongoose.Schema;

const CarsSchema = new Schema({
  tags: [String],
  description: String,
  price: { type: Number, default: 0 },
});

CarsSchema.plugin(mongooseTracker, {
  limit: 50,
  name: "metaDescriptions",
  fieldsToTrack: ["price", "description"],
});

module.exports = mongoose.model("Cars", CarsSchema);

使用 _changedBy 记录更改

这个 _changedBy 字段允许跟踪对文档进行特定修改的人.
您可以在更新文档前直接设置此字段.
建议使用 用户标识,但任何字符串值都可以指定.

示例

async function foo() {
  // Create a new document
  const doc = await SomeModel.find({ name: "Initial Name" });
  doc.name = "New Name";
  // Set the user or system responsible for the creation
  doc._changedBy = "creator"; // Replace 'creator' with the user's ID or identifier
  await doc.save();
}

正在生成历史日志

[
  {
    action: "updated",
    at: 1734955271622,
    changedBy: "creator",
    changes: [
      {
        field: "name",
        before: "Initial Name",
        after: "New Name",
      },
    ],
  },
];

关键说明

  • changedBy字段是可选的,但高度推荐问责.

  • 您可以根据当前用户的ID,用户名或其他独特的标识符,动态设置 changedBy.


《公约》的重要性 _display 外地

这个 _display 字段对于提高历史日志的可读性至关重要。 而不是用数组索引记录原始的字段路径(例如, orders.0.items.1.price,该插件将使用 _display 字段,以显示更有意义的标识符.

如何运作

  1. 是否在场 _display数字 :

    • 确保每个子文件(例如命令内的项目)包括a _display 字段.
    • 此字段应当包含一个唯一标识对象的字符串值,例如名称或可读标签.
  2. 连接机制 :

    • 当跟踪字段更新时(例如, orders.$.items.$.price,插件检索 _display 相应的项目值.
    • 然后它会把这个调好 _display 与已更改的字段名称一起为历史日志构成可读字符串.
    • 示例
      • Raw 字段路径 : orders.0.items.1.price
      • 与 _display数字 : "Test Item 2 price"
  3. 处理对象 :

    • 如果 _display 字段包含一个 ObjectId 引用另一个文档, 插件会通过引用获取 _display 父文档的值.
    • 这种递归解析持续到获得字符串值,确保历史日志保持信息性.

福利

  • 清晰度 : 提供了清晰而简洁的变化表述,使人们更容易理解修改的内容.
  • 可读性 : 避免从数组索引中产生混乱,特别是在有多个嵌入式数组的文档中.
  • 相关性: 重点是在应用程序背景下具有重要意义的有意义的标识符.

示例

  • 考虑以下方案:
interface Item extends Document {
  name: string;
  price: number;
  _display: string;
}

const ItemSchema = new Schema<Item>({
  name: { type: String, required: true },
  price: { type: Number, required: true },
  _display: { type: String, required: true },
});

interface Order extends Document {
  orderNumber: string;
  date: Date;
  items: Item[];
  _display:string;
}

const OrderSchema = new Schema<Order>({
  orderNumber: { type: String, required: true, unique: true },
  date: { type: Date, required: true, default: Date.now },
  items: { type: [ItemSchema], required: true },
  _display: { type: String },
});

interface PurchaseDemand extends Document {
  pdNumber: string;
  orders: Order[];
}

const PurchaseDemandSchema = new Schema<PurchaseDemand>({
  pdNumber: { type: String, required: true, unique: true },
  orders: [OrderSchema],
});

PurchaseDemandSchema.plugin(mongooseTracker, {
  fieldsToTrack: ["orders.$.date", "orders.$.items.$.price"], //The Fields I want to track.
});

const PurchaseDemandModel = mongoose.model<PurchaseDemand>(
  "PurchaseDemand",
  PurchaseDemandSchema
);
const purchaseDemand = new PurchaseDemand({
  pdNumber: "PD-001",
  orders: [
    {
      orderNumber: "ORD-001",
      items: [
        { name: "Test Item 1", price: 100, _display: "Test Item 1" },
        { name: "Test Item 2", price: 200, _display: "Test Item 2" },
      ],
      _display: "Order 1",
    },
  ],
});


// Update an item's price
purchaseDemand._changedBy = 'system';
purchaseDemand.orders[0].items[1].price = 250;
await purchaseDemand.save();

历史日志条目 :

{
  "action": "updated",
  "at": 1734955271622,
  "changedBy": "system",
  "changes": [
    {
      "field": "Test Item 2 price", // instead of "orders.0.items.1.price" 
      "before": 200,
      "after": 250
    }
  ]
}

跟踪阵列元素中的嵌入物体

当跟踪数组元素内的嵌入对象时,插件会智能地解决 _display 从数组元素本身,而不是从嵌入对象。 这对有巢状结构的复杂计划特别有用.

示例:追踪供应商尺寸

const BusinessPartnerSchema = new Schema({
  name: { type: String, required: true },
  _display: { type: String, required: true }
});

const ExpanderProductSchema = new Schema({
  name: { type: String, required: true },
  availableSuppliers: [
    {
      name: { type: String, required: true },
      supplierId: {
        type: Schema.Types.ObjectId,
        ref: 'BusinessPartner',
        required: true
      },
      dimensions: {
        boxUnitLengthSide: { type: Number, default: 0 },
        boxUnitWidthSide: { type: Number, default: 0 },
        boxUnitHeightSide: { type: Number, default: 0 },
        boxUnitWeight: { type: Number, default: 0 }
      },
      cost: { type: Number, default: 0 },
      _display: {
        type: Schema.Types.ObjectId,
        ref: 'BusinessPartner'  // References BusinessPartner
      }
    }
  ]
});

// Track nested dimension fields
ExpanderProductSchema.plugin(mongooseTracker, {
  fieldsToTrack: [
    'availableSuppliers.$.dimensions.boxUnitLengthSide',
    'availableSuppliers.$.dimensions.boxUnitWidthSide',
    'availableSuppliers.$.dimensions.boxUnitHeightSide',
    'availableSuppliers.$.dimensions.boxUnitWeight'
  ]
});

用法 :

const supplier = await BusinessPartner.create({
  name: 'Xinlong Plastic Trunking Co Ltd',
  _display: 'Xinlong Plastic Trunking Co Ltd'
});

const product = await ExpanderProduct.create({
  name: 'Test Product',
  availableSuppliers: [{
    name: supplier.name,
    supplierId: supplier._id,
    dimensions: {
      boxUnitLengthSide: 10,
      boxUnitWidthSide: 20,
      boxUnitHeightSide: 30,
      boxUnitWeight: 5
    },
    cost: 50,
    _display: supplier._id  // ObjectId reference
  }]
});

// Update dimensions
product.availableSuppliers[0].dimensions.boxUnitLengthSide = 15;
product.availableSuppliers[0].dimensions.boxUnitWeight = 7;
await product.save();

历史日志条目 :

{
  "action": "updated",
  "at": 1734955271622,
  "changedBy": null,
  "changes": [
    {
      "field": "Xinlong Plastic Trunking Co Ltd boxUnitLengthSide",  // Resolved from BusinessPartner
      "before": 10,
      "after": 15
    },
    {
      "field": "Xinlong Plastic Trunking Co Ltd boxUnitWeight",  // Resolved from BusinessPartner
      "before": 5,
      "after": 7
    }
  ]
}

说明: 插件解决 _display 从 availableSuppliers[0]._display 而不是来自 availableSuppliers[0].dimensions._display这提供了有意义的背景,表明哪些供应商的维度被修改了.


日志

插件包括内置的日志系统来帮助调试和监测. 您可以使用 logLevel 选项或提供自定义日志.

使用内建登录

YourSchema.plugin(mongooseTracker, {
  name: "history",
  fieldsToTrack: ["title", "status"],
  logLevel: 'info' // Set log level
});

可用的日志级别 :

  • 'debug' - 调试的详细信息(实地变化、阵列操作、参考文献)
  • 'info' - 一般性信息信息(提示、历史更新)
  • 'warn' - 警告信息(丢失文件,无效引用)
  • 'error' - 错误消息(无效模式、搜索失败)
  • 'none' - 禁用所有记录( 默认)

日志输出示例

# Info level
[mongoose-tracker] [INFO] Initializing mongoose-tracker plugin with options: { ... }
[mongoose-tracker] [INFO] Pre-save hook triggered for document 507f1f77bcf86cd799439011

# Debug level (includes all info + detailed tracking)
[mongoose-tracker] [DEBUG] Tracking changes for path: title, displayField: title
[mongoose-tracker] [DEBUG] Primitive value changed for title: "Old Title" => "New Title"
[mongoose-tracker] [DEBUG] Array elements added to orders: 2

# Warn level
[mongoose-tracker] [WARN] Referenced document not found for model: Category, id: 507f1f77bcf86cd799439012

使用自定义记录器

您可以通过执行该程序提供自己的记录器(例如Winston, Pino) Logger 接口 :

import winston from 'winston';

const customLogger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'mongoose-tracker.log' })
  ]
});

// Adapt Winston to mongoose-tracker Logger interface
const loggerAdapter = {
  debug: (message, ...args) => customLogger.debug(message, args),
  info: (message, ...args) => customLogger.info(message, args),
  warn: (message, ...args) => customLogger.warn(message, args),
  error: (message, ...args) => customLogger.error(message, args)
};

YourSchema.plugin(mongooseTracker, {
  name: "history",
  fieldsToTrack: ["title"],
  logger: loggerAdapter
});

使用皮诺

import pino from 'pino';

const pinoLogger = pino({
  level: 'debug',
  transport: {
    target: 'pino-pretty'
  }
});

const loggerAdapter = {
  debug: (message, ...args) => pinoLogger.debug({ args }, message),
  info: (message, ...args) => pinoLogger.info({ args }, message),
  warn: (message, ...args) => pinoLogger.warn({ args }, message),
  error: (message, ...args) => pinoLogger.error({ args }, message)
};

YourSchema.plugin(mongooseTracker, {
  name: "history",
  logger: loggerAdapter
});

跟踪阵列字段

当指定字段 ToTrack 中的数组字段时, 如“ 顺序 ”, 巨鹅追踪器 将监视该数组内的任何增删。 这意味着:

  • 增加数: 当新元素被添加到数组中时, 插件会记录历史数组中的这个变化 .
  • 删除: 当一个已有元素从数组中去除时, 插件会记录历史数组中的这个去除 .

有引用的矩阵元素 _display

巨鹅追踪器 也支持跟踪数组 _display 字段是一个 ObjectId 参考另一种模式。 插件将自动解析引用并使用引用文档的 _display 历史日志中的值.

示例:跟踪供应商阵列

const BusinessPartnerSchema = new Schema({
  name: { type: String, required: true },
  type: { type: String },
  _display: { type: String, required: true }
});

const ExpanderProductSchema = new Schema({
  name: { type: String, required: true },
  availableSuppliers: [
    {
      name: { type: String, required: true },
      supplierId: {
        type: Schema.Types.ObjectId,
        ref: 'BusinessPartner',
        required: true
      },
      cost: { type: Number, default: 0 },
      // _display references the BusinessPartner document
      _display: {
        type: Schema.Types.ObjectId,
        ref: 'BusinessPartner'
      }
    }
  ]
});

// Track changes to availableSuppliers array
ExpanderProductSchema.plugin(mongooseTracker, {
  fieldsToTrack: ['availableSuppliers']
});

const BusinessPartner = mongoose.model('BusinessPartner', BusinessPartnerSchema);
const ExpanderProduct = mongoose.model('ExpanderProduct', ExpanderProductSchema);

添加供应商 :

const supplier = await BusinessPartner.create({
  name: 'Supplier A',
  type: 'manufacturer',
  _display: 'Supplier A Display'
});

const product = await ExpanderProduct.findOne({ name: 'My Product' });

product.availableSuppliers.push({
  name: supplier.name,
  supplierId: supplier._id,
  cost: 100,
  _display: supplier._id  // ObjectId reference
});

await product.save();

历史日志条目 :

{
  "action": "added",
  "at": 1734955271622,
  "changedBy": null,
  "changes": [
    {
      "field": "availableSuppliers",
      "before": null,
      "after": "Supplier A Display"  // Resolved from BusinessPartner._display
    }
  ]
}

删除供应商 :

product.availableSuppliers = product.availableSuppliers.filter(
  s => s.supplierId.toString() !== supplier._id.toString()
);

await product.save();

历史日志条目 :

{
  "action": "removed",
  "at": 1734955271622,
  "changedBy": null,
  "changes": [
    {
      "field": "availableSuppliers",
      "before": "Supplier A Display",  // Resolved from BusinessPartner._display
      "after": null
    }
  ]
}

操作 :

添加元素( 命令) :


PurchaseDemandSchema.plugin(mongooseTracker, {
  fieldsToTrack: ["orders"],
});


const purchaseDemand = await PurchaseDemandModel.create({
  pdNumber: "PD-TEST-002",
  orders: [],
});

// Adding a new order
purchaseDemand.orders.push({
  orderNumber: "ORD-TEST-002",
  date: new Date(),
  items: [{ name: "Test Item 3", price: 300, _display: "Test Item 3" }],
  _display: "ORD-TEST-002",
});

await purchaseDemand.save();

历史日志条目添加后 :

{
  "action": "added",
  "at": 1734955271622,
  "changedBy": null,
  "changes": [
    {
      "field": "orders",
      "before": null,
      "after": 'ORD-TEST-002' // the name of _display.
    }
  ]
}

删除元素( 命令) :

purchaseDemand.orders.pop(); // we remove the last element that insert in orders. (ORD-TEST-002)
await purchaseDemand.save();

删除后的历史日志条目 :

{
  "action": "removed",
  "at": 1734955271622,
  "changedBy": null,
  "changes": [
    {
      "field": "orders",
      "before": 'ORD-TEST-002'
      "after": null
    }
  ]
}

捐款

  • 使用 Eslint 插入您的代码 .
  • 为任何新的或已更改的功能添加测试 .
  • 如果添加或更改任何功能,则用示例更新读取器.
  • 作者:罗尼·杰克·维图利
  • 许可证: Apache-2.0

版本与发布记录

@expander/mongoose-tracker 共发布 20 个版本,首次发布于 2024/12/09;过去 12 个月发布了 3 个版本。

版本发布日期距今
1.8.2latest2026/01/139 个月前
1.8.12026/01/1310 个月前
1.8.02026/01/1210 个月前
1.7.12025/05/131.5 年前
1.7.02025/05/131.5 年前
1.6.92025/05/131.5 年前

关于 @expander/mongoose-tracker 的常见问题

@expander/mongoose-tracker 是什么?

是一个 mongoose 插件, 它会自动跟踪文档创建、 更新和可选修改部分字段的时间

如何安装 @expander/mongoose-tracker?

在项目目录运行 npm install @expander/mongoose-tracker,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:mongoose(^6.1.5)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@expander/mongoose-tracker 支持 TypeScript 吗?

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

@expander/mongoose-tracker 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@expander/mongoose-tracker 需要什么版本的 Node.js?

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

@expander/mongoose-tracker 可以商用吗?

@expander/mongoose-tracker 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@expander/mongoose-tracker 的下载量有多少?

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

@expander/mongoose-tracker 还在维护吗?

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