import { SelectOption, CheckerType } from "."; import { ExcludeUpdateAction } from "../actions/action"; import { AsyncContext } from "../store/AsyncRowStore"; import { SyncContext } from "../store/SyncRowStore"; import { EntityDict, OperateOption } from "../types/Entity"; import { EntityShape } from "../types/Entity"; import { EntityDict as BaseEntityDict } from '../base-app-domain'; /** * Modi(延时更新)的执行时机 * * 在某些业务场景中,数据修改不是立即生效的,而是先创建一个"修改申请"(Modi), * 等待审批通过后才真正应用到数据上。这个类型定义了触发器在这种场景下的执行时机。 * * @example * // 场景:员工修改个人信息需要HR审批 * // 1. 员工提交修改 -> 创建 Modi 记录(create 阶段) * // 2. HR 审批通过 -> 应用 Modi 到实际数据(apply 阶段) * * - 'create': 仅在创建 Modi 时执行触发器(如:发送审批通知) * - 'apply': 仅在 Modi 被应用时执行触发器(如:同步数据到其他系统) * - 'both': 两个阶段都执行触发器 * * 默认行为: * - commit 时机的触发器:默认只在 apply 阶段执行 * - 非 commit 时机的触发器:默认只在 create 阶段执行 * * @see TriggerExecutor.judgeModiTurn 具体判断逻辑 */ export type ModiTurn = 'create' | 'apply' | 'both'; /** * 触发器最小优先级(最先执行) * 用于需要最早执行的触发器,如数据预处理 */ export declare const TRIGGER_MIN_PRIORITY = 1; /** * 触发器默认优先级 * 未指定优先级时使用此值 */ export declare const TRIGGER_DEFAULT_PRIORITY = 25; /** * 触发器最大优先级(在普通触发器中最后执行) * 用于依赖其他触发器处理结果的场景 */ export declare const TRIGGER_MAX_PRIORITY = 50; /** * Checker(检查器)的最大优先级 * Checker 优先级范围是 51-99,在所有普通触发器之后执行 * 这确保了数据校验在数据处理完成后进行 */ export declare const CHECKER_MAX_PRIORITY = 99; /** * 不同类型 Checker 的默认优先级映射 * * 执行顺序(从先到后): * 1. logicalData (31) - 逻辑数据检查,可修改 data 中的值 * 2. logical (33) - 纯逻辑检查 * 3. row (51) - 行级检查,检查数据库中已存在的行 * 4. relation (56) - 关系检查,检查关联数据 * 5. data (61) - 数据完整性检查 * * @example * // logicalData 类型可以自动填充默认值 * // logical 类型进行业务逻辑校验 * // row 类型检查当前行状态是否允许操作 */ export declare const CHECKER_PRIORITY_MAP: Record; /** * 所有触发器的基础接口 * * @template ED - 实体字典类型,包含所有实体的定义 * @template T - 当前触发器所属的实体名称 */ interface TriggerBase { /** * 如果此触发器是由 Checker 转换而来,记录原始 Checker 的类型 * 内部使用,一般不需要手动设置 */ checkerType?: CheckerType; /** * 触发器所属的实体名称 * @example 'user', 'order', 'product' */ entity: T; /** * 触发器的唯一名称 * 用于标识和调试,必须在整个应用中唯一 * @example '订单创建后发送通知', 'user-create-init-profile' */ name: string; /** * 是否以 Root 模式执行 * * 设置为 true 时,触发器内的所有操作将绕过权限检查。 * 适用于系统级操作,如自动填充审计字段、级联更新等。 * * 注意:谨慎使用,可能导致权限漏洞 * * @example * // 自动记录操作日志,需要写入用户无权访问的日志表 * { * name: '记录操作日志', * asRoot: true, * fn: async (event, context) => { * await context.operate('operationLog', { ... }); * } * } */ asRoot?: true; /** * 触发器执行优先级 * * 范围:TRIGGER_MIN_PRIORITY (1) 到 CHECKER_MAX_PRIORITY (99) * 数值越小,越先执行 * * 建议: * - 1-10: 数据预处理(如自动填充字段) * - 11-30: 普通业务逻辑 * - 31-50: 依赖其他触发器结果的逻辑 * - 51-99: 数据校验(通常由 Checker 使用) * * @default TRIGGER_DEFAULT_PRIORITY (25) */ priority?: number; } /** * Create 触发器的基础接口 * * @template ED - 实体字典类型 * @template T - 实体名称 * @template Cxt - 上下文类型(异步或同步) * * @example * // 创建用户时自动初始化用户配置 * const trigger: CreateTrigger = { * name: '初始化用户配置', * entity: 'user', * action: 'create', * when: 'after', * fn: async ({ operation }, context) => { * const userId = operation.data.id; * await context.operate('userConfig', { * action: 'create', * data: { id: generateId(), userId, theme: 'default' } * }); * return 1; * } * }; */ export interface CreateTriggerBase | SyncContext> extends TriggerBase { /** * 触发动作类型,固定为 'create' */ action: 'create'; /** * Modi(延时更新)场景下的执行时机 * @see ModiTurn */ mt?: ModiTurn; /** * 前置检查函数 * * 在触发器执行前调用,返回 false 则跳过此触发器。 * 用于根据操作内容动态决定是否执行。 * * @param operation - 当前的创建操作 * @returns true 执行触发器,false 跳过 * * @example * // 只有创建 VIP 用户时才发送欢迎邮件 * check: (operation) => operation.data.isVip === true */ check?: (operation: ED[T]['Create']) => boolean; } /** * 事务内 Create 触发器 * * 在事务内执行,与主操作共享同一个事务。 * 如果触发器执行失败,整个事务会回滚。 * * @example * // before: 在数据插入前执行,可以修改要插入的数据 * const beforeTrigger: CreateTriggerInTxn = { * name: '自动生成订单号', * entity: 'order', * action: 'create', * when: 'before', * fn: ({ operation }, context) => { * operation.data.orderNo = generateOrderNo(); * return 0; // 返回修改的行数(此处未实际修改数据库) * } * }; * * // after: 在数据插入后执行,可以执行关联操作 * const afterTrigger: CreateTriggerInTxn = { * name: '创建订单日志', * entity: 'order', * action: 'create', * when: 'after', * fn: async ({ operation }, context) => { * await context.operate('orderLog', { * action: 'create', * data: { orderId: operation.data.id, action: 'created' } * }); * return 1; * } * }; */ export interface CreateTriggerInTxn | SyncContext> extends CreateTriggerBase { /** * 执行时机 * - 'before': 在数据插入前执行,可修改 operation.data * - 'after': 在数据插入后执行,数据已存在于数据库中 */ when: 'before' | 'after'; /** * 触发器执行函数 * * @param event.operation - 当前的创建操作,包含 action 和 data * @param context - 执行上下文,可用于执行其他数据库操作 * @param option - 操作选项 * @returns 返回此触发器影响的行数(用于日志记录) */ fn: (event: { operation: ED[T]['Create']; }, context: Cxt, option: OperateOption) => Promise | number; } /** * 跨事务触发器的通用接口 * * 跨事务触发器在主事务提交后异步执行,适用于: * - 不需要与主操作保持原子性的操作 * - 耗时较长的操作(如发送邮件、调用外部API) * - 可以容忍最终一致性的场景 * * 通过 checkpoint 机制保证最终一定会执行成功。 */ interface TriggerCrossTxn | SyncContext> { /** * 执行时机,固定为 'commit' * 表示在主事务提交后执行 */ when: 'commit'; /** * 执行严格程度 * * - 'takeEasy': 尽力执行,失败后不重试 * 适用于非关键操作,如发送通知 * * - 'makeSure': 必须确保执行成功 * 会在数据行上标记 $$triggerData$$ 和 $$triggerUuid$$, * 通过 checkpoint 机制保证最终执行成功 * 适用于关键业务操作,如同步数据到外部系统 * * @default 'takeEasy' * * @example * // 发送欢迎邮件 - 失败了也没关系 * { strict: 'takeEasy' } * * // 同步订单到ERP - 必须成功 * { strict: 'makeSure' } */ strict?: 'takeEasy' | 'makeSure'; /** * 集群敏感标记 * * 设置为 true 时,表示此触发器需要由特定的集群节点处理。 * 用于需要特殊资源或环境的触发器。 */ cs?: true; /** * 是否自行清理触发器标记数据 * * 设置为 true 时,框架不会自动清除 $$triggerData$$ 和 $$triggerUuid$$, * 需要在 fn 函数中自行处理。 * * 适用于需要自定义清理逻辑的场景。 */ cleanTriggerDataBySelf?: true; /** * 单例模式标记 * * 设置为 true 时,在集群环境中只有一个进程会执行此触发器。 * 用于防止重复执行的场景,如定时任务。 */ singleton?: true; /** * 分组处理标记 * * 设置为 true 时,checkpoint 会将所有未处理的行 ID 一次性传入 fn 函数。 * 适用于批量处理更高效的场景。 * * - false (默认): 按 triggerUuid 分组,每批单独调用 fn * - true: 所有未处理的行一次性传入 * * @example * // 批量同步数据到外部系统 * { * grouped: true, * fn: async ({ ids }, context) => { * await syncToExternalSystem(ids); // 批量处理更高效 * } * } */ grouped?: true; /** * 触发器执行函数 * * @param event.ids - 需要处理的实体 ID 数组 * @param context - 执行上下文 * @param option - 操作选项 * @returns 可选的返回值: * - void: 无后续操作 * - 函数: 在清理标记后执行的回调函数 * - 对象: 需要更新到数据行上的额外数据 * * @example * // 基本用法 * fn: async ({ ids }, context) => { * for (const id of ids) { * await sendNotification(id); * } * } * * // 返回回调函数,在清理完成后执行 * fn: async ({ ids }, context) => { * const results = await processIds(ids); * return async (ctx, opt) => { * await saveResults(results); * }; * } * * // 返回需要更新的数据 * fn: async ({ ids }, context) => { * return { syncedAt: Date.now() }; * } */ fn: (event: { ids: string[]; }, context: Cxt, option: OperateOption) => Promise<((context: Cxt, option: OperateOption) => Promise) | void | ED[T]['Update']['data']>; } /** * 跨事务 Create 触发器 * * 组合了 CreateTriggerBase 和 TriggerCrossTxn 的特性。 * 在实体创建的事务提交后异步执行。 * * @example * const trigger: CreateTriggerCrossTxn = { * name: '发送欢迎邮件', * entity: 'user', * action: 'create', * when: 'commit', * strict: 'makeSure', // 确保发送成功 * fn: async ({ ids }, context) => { * for (const userId of ids) { * await emailService.sendWelcome(userId); * } * } * }; */ export interface CreateTriggerCrossTxn | SyncContext> extends CreateTriggerBase, TriggerCrossTxn { } /** * Create 触发器类型联合 * 包括事务内触发器和跨事务触发器 */ export type CreateTrigger | SyncContext> = CreateTriggerInTxn | CreateTriggerCrossTxn; /** * Update 触发器的基础接口 * * Update 触发器支持条件过滤(filter),只对满足条件的行触发。 * 这是与 Create 触发器的主要区别之一。 * * @template ED - 实体字典类型 * @template T - 实体名称 * @template Cxt - 上下文类型 * * @example * // 订单状态变为"已支付"时触发 * const trigger: UpdateTrigger = { * name: '订单支付成功处理', * entity: 'order', * action: 'pay', // 支付动作 * when: 'after', * filter: { status: 'pending' }, // 只对待支付订单触发 * attributes: ['status'], // 只在 status 字段变化时触发 * fn: async ({ operation }, context) => { * // 处理支付成功逻辑 * } * }; */ export interface UpdateTriggerBase | SyncContext> extends TriggerBase { /** * 触发动作类型 * * 可以是单个动作或动作数组。 * 排除了 'create'、'remove'、'select' 等非更新动作。 * * @example * action: 'update' // 单个动作 * action: ['update', 'pay'] // 多个动作 */ action: Exclude | Array>; /** * 监听的属性列表 * * 只有当这些属性出现在更新数据中时才触发。 * 用于优化性能,避免不必要的触发器执行。 * * 注意:检查的是 operation.data 中是否包含这些属性, * 而不是属性值是否真的发生了变化。 * * @example * // 只在 price 或 quantity 变化时重新计算总价 * attributes: ['price', 'quantity'] */ attributes?: Array; /** * Modi(延时更新)场景下的执行时机 * @see ModiTurn */ mt?: ModiTurn; /** * 前置检查函数 * * @param operation - 当前的更新操作 * @returns true 执行触发器,false 跳过 */ check?: (operation: ED[T]['Update']) => boolean; /** * 条件过滤器 * * 只对满足过滤条件的行触发此触发器。 * 可以是静态过滤对象,也可以是动态生成过滤条件的函数。 * * 框架会检查操作的 filter 与触发器的 filter 是否有交集, * 只要可能存在同时满足两个条件的行,就会触发。 * * @example * // 静态过滤:只对状态为 active 的订单触发 * filter: { status: 'active' } * * // 动态过滤:根据操作内容决定过滤条件 * filter: (operation, context) => { * if (operation.data.type === 'vip') { * return { level: { $gte: 5 } }; * } * return { level: { $gte: 1 } }; * } */ filter?: ED[T]['Filter'] | ((operation: ED[T]['Update'], context: Cxt, option: OperateOption) => ED[T]['Filter'] | Promise); } /** * 事务内 Update 触发器 * * @example * // 更新库存时检查是否需要补货提醒 * const trigger: UpdateTriggerInTxn = { * name: '库存不足提醒', * entity: 'product', * action: 'update', * when: 'after', * attributes: ['stock'], * filter: { stock: { $lt: 10 } }, // 库存小于10时 * fn: async ({ operation }, context) => { * // 创建补货提醒 * return 1; * } * }; */ export interface UpdateTriggerInTxn | SyncContext> extends UpdateTriggerBase { /** * 执行时机 * - 'before': 在数据更新前执行,可修改 operation.data * - 'after': 在数据更新后执行 */ when: 'before' | 'after'; /** * 触发器执行函数 * * @param event.operation - 当前的更新操作 * @param context - 执行上下文 * @param option - 操作选项 * @returns 影响的行数 */ fn: (event: { operation: ED[T]['Update']; }, context: Cxt, option: OperateOption) => Promise | number; } /** * 跨事务 Update 触发器 * * @example * // 订单完成后同步到财务系统 * const trigger: UpdateTriggerCrossTxn = { * name: '同步订单到财务系统', * entity: 'order', * action: 'complete', * when: 'commit', * strict: 'makeSure', * filter: { status: 'completed' }, * fn: async ({ ids }, context) => { * await financeSystem.syncOrders(ids); * } * }; */ export interface UpdateTriggerCrossTxn | SyncContext> extends UpdateTriggerBase, TriggerCrossTxn { } /** * Update 触发器类型联合 */ export type UpdateTrigger | SyncContext> = UpdateTriggerInTxn | UpdateTriggerCrossTxn; /** * Remove 触发器的基础接口 * * 与 Update 触发器类似,支持条件过滤。 * * @example * // 删除用户时清理关联数据 * const trigger: RemoveTrigger = { * name: '清理用户关联数据', * entity: 'user', * action: 'remove', * when: 'before', * fn: async ({ operation }, context) => { * const { filter } = operation; * // 删除用户的所有订单 * await context.operate('order', { * id: await generateNewIdAsync(), * action: 'remove', * filter: { user: filter } * }); * return 1; * } * }; */ export interface RemoveTriggerBase | SyncContext> extends TriggerBase { /** * 触发动作类型,固定为 'remove' */ action: 'remove'; /** * Modi(延时更新)场景下的执行时机 */ mt?: ModiTurn; /** * 前置检查函数 */ check?: (operation: ED[T]['Remove']) => boolean; /** * 条件过滤器 * 只对满足条件的行触发 */ filter?: ED[T]['Remove']['filter'] | ((operation: ED[T]['Remove'], context: Cxt, option: OperateOption) => ED[T]['Remove']['filter'] | Promise); } /** * 事务内 Remove 触发器 */ export interface RemoveTriggerInTxn | SyncContext> extends RemoveTriggerBase { when: 'before' | 'after'; fn: (event: { operation: ED[T]['Remove']; }, context: Cxt, option: OperateOption) => Promise | number; } /** * 跨事务 Remove 触发器 */ export interface RemoveTriggerCrossTxn | SyncContext> extends RemoveTriggerBase, TriggerCrossTxn { } /** * Remove 触发器类型联合 */ export type RemoveTrigger | SyncContext> = RemoveTriggerInTxn | RemoveTriggerCrossTxn; /** * Select 触发器的基础接口 * * Select 触发器用于在查询前后进行拦截处理,常见用途: * - 查询前:添加额外的过滤条件(如数据权限过滤) * - 查询后:对结果进行加工处理(如脱敏、格式化) */ export interface SelectTriggerBase extends TriggerBase { /** * 触发动作类型,固定为 'select' */ action: 'select'; } /** * 查询前 Select 触发器 * * 在执行查询前触发,可用于: * - 修改查询条件(如添加数据权限过滤) * - 记录查询日志 * - 查询频率限制 * * 注意:Select 触发器目前不支持跨事务模式 * * @example * // 自动添加系统ID过滤 * const trigger: SelectTriggerBefore = { * name: '修改filter,添加systemId', * entity: 'order', * action: 'select', * when: 'before', * fn: ({ operation }, context) => { * const systemId = context.getSystemId(); * // 修改查询条件,添加租户过滤 * operation.filter = { * ...operation.filter, * systemId, * }; * return 0; * } * }; */ export interface SelectTriggerBefore | SyncContext> extends SelectTriggerBase { /** * 执行时机,固定为 'before'(查询执行前) */ when: 'before'; /** * 触发器执行函数 * * @param event.operation - 当前的查询操作,可直接修改 * @param context - 执行上下文 * @param params - 查询选项 * @returns 影响的行数(通常返回 0,因为只是修改了查询条件) */ fn: (event: { operation: ED[T]['Selection']; }, context: Cxt, params?: SelectOption) => Promise | number; } /** * 查询后 Select 触发器 * * 在查询执行后触发,可用于: * - 数据脱敏(如隐藏敏感信息) * - 数据格式化(如日期格式转换) * - 计算派生字段 * - 权限过滤(移除无权查看的字段) * * @example * // 计算订单的总金额(派生字段) * const trigger: SelectTriggerAfter = { * name: '计算订单总金额', * entity: 'order', * action: 'select', * when: 'after', * fn: ({ operation, result }, context) => { * result.forEach(order => { * if (order.items) { * order.totalAmount = order.items.reduce( * (sum, item) => sum + item.price * item.quantity, 0 * ); * } * }); * return result.length; * } * }; * * // 根据权限隐藏敏感字段 * const sensitiveDataTrigger: SelectTriggerAfter = { * name: '隐藏员工敏感信息', * entity: 'employee', * action: 'select', * when: 'after', * fn: ({ result }, context) => { * const isRoot = context.isRoot(); * if (!isRoot) { * result.forEach(emp => { * delete emp.salary; * delete emp.idCard; * }); * } * return result.length; * } * }; */ export interface SelectTriggerAfter | SyncContext> extends SelectTriggerBase { /** * 执行时机,固定为 'after'(查询执行后) */ when: 'after'; /** * 触发器执行函数 * * @param event.operation - 当前的查询操作 * @param event.result - 查询结果数组,可直接修改 * @param context - 执行上下文 * @param params - 查询选项 * @returns 处理的行数 */ fn: (event: { operation: ED[T]['Selection']; result: Partial[]; }, context: Cxt, params?: SelectOption) => Promise | number; } /** * Select 触发器类型联合 */ export type SelectTrigger | SyncContext> = SelectTriggerBefore | SelectTriggerAfter; /** * 所有触发器类型的联合 * * 包含所有 CRUD 操作的触发器类型: * - CreateTrigger: 创建触发器 * - UpdateTrigger: 更新触发器 * - RemoveTrigger: 删除触发器 * - SelectTrigger: 查询触发器 * * @example * // 注册触发器时使用 * function registerTrigger(trigger: Trigger) { * triggerExecutor.registerTrigger(trigger); * } */ export type Trigger | SyncContext> = CreateTrigger | UpdateTrigger | RemoveTrigger | SelectTrigger; /** * 触发器实体的数据形状 * * 用于支持跨事务触发器的实体需要包含这些额外字段。 * 框架会自动管理这些字段,开发者一般不需要直接操作。 * * @deprecated 建议使用框架定义的 TriggerDataAttribute 和 TriggerUuidAttribute 常量 */ export interface TriggerEntityShape extends EntityShape { /** * 跨事务触发器的执行数据 * 存储触发器名称和原始操作信息,用于断点恢复 */ $$triggerData$$?: { name: string; operation: object; }; /** * 跨事务触发器的时间戳 * @deprecated 已被 $$triggerUuid$$ 替代 */ $$triggerTimestamp$$?: number; } /** * 跨事务(Volatile)触发器类型联合 * * 这是所有 when: 'commit' 触发器的类型联合。 * 这些触发器在事务提交后异步执行,通过 checkpoint 机制保证最终执行。 * * 特点: * - 不与主操作共享事务 * - 可能延迟执行 * - 通过标记机制保证最终一致性 * - 需要考虑幂等性(可能被多次调用) * * @example * // 判断一个触发器是否是跨事务触发器 * function isVolatileTrigger(trigger: Trigger): trigger is VolatileTrigger { * return trigger.when === 'commit'; * } */ export type VolatileTrigger | SyncContext> = CreateTriggerCrossTxn | UpdateTriggerCrossTxn | RemoveTriggerCrossTxn; export {};