761 lines
26 KiB
TypeScript
761 lines
26 KiB
TypeScript
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<CheckerType, number>;
|
||
/**
|
||
* 所有触发器的基础接口
|
||
*
|
||
* @template ED - 实体字典类型,包含所有实体的定义
|
||
* @template T - 当前触发器所属的实体名称
|
||
*/
|
||
interface TriggerBase<ED extends EntityDict & BaseEntityDict, T extends keyof ED> {
|
||
/**
|
||
* 如果此触发器是由 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<ED, 'user', Cxt> = {
|
||
* 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<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends TriggerBase<ED, T> {
|
||
/**
|
||
* 触发动作类型,固定为 '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<ED, 'order', Cxt> = {
|
||
* name: '自动生成订单号',
|
||
* entity: 'order',
|
||
* action: 'create',
|
||
* when: 'before',
|
||
* fn: ({ operation }, context) => {
|
||
* operation.data.orderNo = generateOrderNo();
|
||
* return 0; // 返回修改的行数(此处未实际修改数据库)
|
||
* }
|
||
* };
|
||
*
|
||
* // after: 在数据插入后执行,可以执行关联操作
|
||
* const afterTrigger: CreateTriggerInTxn<ED, 'order', Cxt> = {
|
||
* 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<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends CreateTriggerBase<ED, T, Cxt> {
|
||
/**
|
||
* 执行时机
|
||
* - '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> | number;
|
||
}
|
||
/**
|
||
* 跨事务触发器的通用接口
|
||
*
|
||
* 跨事务触发器在主事务提交后异步执行,适用于:
|
||
* - 不需要与主操作保持原子性的操作
|
||
* - 耗时较长的操作(如发送邮件、调用外部API)
|
||
* - 可以容忍最终一致性的场景
|
||
*
|
||
* 通过 checkpoint 机制保证最终一定会执行成功。
|
||
*/
|
||
interface TriggerCrossTxn<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> {
|
||
/**
|
||
* 执行时机,固定为 '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<any>) | void | ED[T]['Update']['data']>;
|
||
}
|
||
/**
|
||
* 跨事务 Create 触发器
|
||
*
|
||
* 组合了 CreateTriggerBase 和 TriggerCrossTxn 的特性。
|
||
* 在实体创建的事务提交后异步执行。
|
||
*
|
||
* @example
|
||
* const trigger: CreateTriggerCrossTxn<ED, 'user', Cxt> = {
|
||
* 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<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends CreateTriggerBase<ED, T, Cxt>, TriggerCrossTxn<ED, T, Cxt> {
|
||
}
|
||
/**
|
||
* Create 触发器类型联合
|
||
* 包括事务内触发器和跨事务触发器
|
||
*/
|
||
export type CreateTrigger<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> = CreateTriggerInTxn<ED, T, Cxt> | CreateTriggerCrossTxn<ED, T, Cxt>;
|
||
/**
|
||
* Update 触发器的基础接口
|
||
*
|
||
* Update 触发器支持条件过滤(filter),只对满足条件的行触发。
|
||
* 这是与 Create 触发器的主要区别之一。
|
||
*
|
||
* @template ED - 实体字典类型
|
||
* @template T - 实体名称
|
||
* @template Cxt - 上下文类型
|
||
*
|
||
* @example
|
||
* // 订单状态变为"已支付"时触发
|
||
* const trigger: UpdateTrigger<ED, 'order', Cxt> = {
|
||
* name: '订单支付成功处理',
|
||
* entity: 'order',
|
||
* action: 'pay', // 支付动作
|
||
* when: 'after',
|
||
* filter: { status: 'pending' }, // 只对待支付订单触发
|
||
* attributes: ['status'], // 只在 status 字段变化时触发
|
||
* fn: async ({ operation }, context) => {
|
||
* // 处理支付成功逻辑
|
||
* }
|
||
* };
|
||
*/
|
||
export interface UpdateTriggerBase<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends TriggerBase<ED, T> {
|
||
/**
|
||
* 触发动作类型
|
||
*
|
||
* 可以是单个动作或动作数组。
|
||
* 排除了 'create'、'remove'、'select' 等非更新动作。
|
||
*
|
||
* @example
|
||
* action: 'update' // 单个动作
|
||
* action: ['update', 'pay'] // 多个动作
|
||
*/
|
||
action: Exclude<ED[T]['Action'], ExcludeUpdateAction> | Array<Exclude<ED[T]['Action'], ExcludeUpdateAction>>;
|
||
/**
|
||
* 监听的属性列表
|
||
*
|
||
* 只有当这些属性出现在更新数据中时才触发。
|
||
* 用于优化性能,避免不必要的触发器执行。
|
||
*
|
||
* 注意:检查的是 operation.data 中是否包含这些属性,
|
||
* 而不是属性值是否真的发生了变化。
|
||
*
|
||
* @example
|
||
* // 只在 price 或 quantity 变化时重新计算总价
|
||
* attributes: ['price', 'quantity']
|
||
*/
|
||
attributes?: Array<keyof ED[T]['OpSchema']>;
|
||
/**
|
||
* 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<ED[T]['Filter']>);
|
||
}
|
||
/**
|
||
* 事务内 Update 触发器
|
||
*
|
||
* @example
|
||
* // 更新库存时检查是否需要补货提醒
|
||
* const trigger: UpdateTriggerInTxn<ED, 'product', Cxt> = {
|
||
* name: '库存不足提醒',
|
||
* entity: 'product',
|
||
* action: 'update',
|
||
* when: 'after',
|
||
* attributes: ['stock'],
|
||
* filter: { stock: { $lt: 10 } }, // 库存小于10时
|
||
* fn: async ({ operation }, context) => {
|
||
* // 创建补货提醒
|
||
* return 1;
|
||
* }
|
||
* };
|
||
*/
|
||
export interface UpdateTriggerInTxn<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends UpdateTriggerBase<ED, T, Cxt> {
|
||
/**
|
||
* 执行时机
|
||
* - '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> | number;
|
||
}
|
||
/**
|
||
* 跨事务 Update 触发器
|
||
*
|
||
* @example
|
||
* // 订单完成后同步到财务系统
|
||
* const trigger: UpdateTriggerCrossTxn<ED, 'order', Cxt> = {
|
||
* name: '同步订单到财务系统',
|
||
* entity: 'order',
|
||
* action: 'complete',
|
||
* when: 'commit',
|
||
* strict: 'makeSure',
|
||
* filter: { status: 'completed' },
|
||
* fn: async ({ ids }, context) => {
|
||
* await financeSystem.syncOrders(ids);
|
||
* }
|
||
* };
|
||
*/
|
||
export interface UpdateTriggerCrossTxn<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends UpdateTriggerBase<ED, T, Cxt>, TriggerCrossTxn<ED, T, Cxt> {
|
||
}
|
||
/**
|
||
* Update 触发器类型联合
|
||
*/
|
||
export type UpdateTrigger<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> = UpdateTriggerInTxn<ED, T, Cxt> | UpdateTriggerCrossTxn<ED, T, Cxt>;
|
||
/**
|
||
* Remove 触发器的基础接口
|
||
*
|
||
* 与 Update 触发器类似,支持条件过滤。
|
||
*
|
||
* @example
|
||
* // 删除用户时清理关联数据
|
||
* const trigger: RemoveTrigger<ED, 'user', Cxt> = {
|
||
* 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<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends TriggerBase<ED, T> {
|
||
/**
|
||
* 触发动作类型,固定为 '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<ED[T]['Remove']['filter']>);
|
||
}
|
||
/**
|
||
* 事务内 Remove 触发器
|
||
*/
|
||
export interface RemoveTriggerInTxn<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends RemoveTriggerBase<ED, T, Cxt> {
|
||
when: 'before' | 'after';
|
||
fn: (event: {
|
||
operation: ED[T]['Remove'];
|
||
}, context: Cxt, option: OperateOption) => Promise<number> | number;
|
||
}
|
||
/**
|
||
* 跨事务 Remove 触发器
|
||
*/
|
||
export interface RemoveTriggerCrossTxn<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends RemoveTriggerBase<ED, T, Cxt>, TriggerCrossTxn<ED, T, Cxt> {
|
||
}
|
||
/**
|
||
* Remove 触发器类型联合
|
||
*/
|
||
export type RemoveTrigger<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> = RemoveTriggerInTxn<ED, T, Cxt> | RemoveTriggerCrossTxn<ED, T, Cxt>;
|
||
/**
|
||
* Select 触发器的基础接口
|
||
*
|
||
* Select 触发器用于在查询前后进行拦截处理,常见用途:
|
||
* - 查询前:添加额外的过滤条件(如数据权限过滤)
|
||
* - 查询后:对结果进行加工处理(如脱敏、格式化)
|
||
*/
|
||
export interface SelectTriggerBase<ED extends EntityDict & BaseEntityDict, T extends keyof ED> extends TriggerBase<ED, T> {
|
||
/**
|
||
* 触发动作类型,固定为 'select'
|
||
*/
|
||
action: 'select';
|
||
}
|
||
/**
|
||
* 查询前 Select 触发器
|
||
*
|
||
* 在执行查询前触发,可用于:
|
||
* - 修改查询条件(如添加数据权限过滤)
|
||
* - 记录查询日志
|
||
* - 查询频率限制
|
||
*
|
||
* 注意:Select 触发器目前不支持跨事务模式
|
||
*
|
||
* @example
|
||
* // 自动添加系统ID过滤
|
||
* const trigger: SelectTriggerBefore<ED, 'order', Cxt> = {
|
||
* 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<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends SelectTriggerBase<ED, T> {
|
||
/**
|
||
* 执行时机,固定为 'before'(查询执行前)
|
||
*/
|
||
when: 'before';
|
||
/**
|
||
* 触发器执行函数
|
||
*
|
||
* @param event.operation - 当前的查询操作,可直接修改
|
||
* @param context - 执行上下文
|
||
* @param params - 查询选项
|
||
* @returns 影响的行数(通常返回 0,因为只是修改了查询条件)
|
||
*/
|
||
fn: (event: {
|
||
operation: ED[T]['Selection'];
|
||
}, context: Cxt, params?: SelectOption) => Promise<number> | number;
|
||
}
|
||
/**
|
||
* 查询后 Select 触发器
|
||
*
|
||
* 在查询执行后触发,可用于:
|
||
* - 数据脱敏(如隐藏敏感信息)
|
||
* - 数据格式化(如日期格式转换)
|
||
* - 计算派生字段
|
||
* - 权限过滤(移除无权查看的字段)
|
||
*
|
||
* @example
|
||
* // 计算订单的总金额(派生字段)
|
||
* const trigger: SelectTriggerAfter<ED, 'order', Cxt> = {
|
||
* 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<ED, 'employee', Cxt> = {
|
||
* 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<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends SelectTriggerBase<ED, T> {
|
||
/**
|
||
* 执行时机,固定为 'after'(查询执行后)
|
||
*/
|
||
when: 'after';
|
||
/**
|
||
* 触发器执行函数
|
||
*
|
||
* @param event.operation - 当前的查询操作
|
||
* @param event.result - 查询结果数组,可直接修改
|
||
* @param context - 执行上下文
|
||
* @param params - 查询选项
|
||
* @returns 处理的行数
|
||
*/
|
||
fn: (event: {
|
||
operation: ED[T]['Selection'];
|
||
result: Partial<ED[T]['Schema']>[];
|
||
}, context: Cxt, params?: SelectOption) => Promise<number> | number;
|
||
}
|
||
/**
|
||
* Select 触发器类型联合
|
||
*/
|
||
export type SelectTrigger<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> = SelectTriggerBefore<ED, T, Cxt> | SelectTriggerAfter<ED, T, Cxt>;
|
||
/**
|
||
* 所有触发器类型的联合
|
||
*
|
||
* 包含所有 CRUD 操作的触发器类型:
|
||
* - CreateTrigger: 创建触发器
|
||
* - UpdateTrigger: 更新触发器
|
||
* - RemoveTrigger: 删除触发器
|
||
* - SelectTrigger: 查询触发器
|
||
*
|
||
* @example
|
||
* // 注册触发器时使用
|
||
* function registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>) {
|
||
* triggerExecutor.registerTrigger(trigger);
|
||
* }
|
||
*/
|
||
export type Trigger<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> = CreateTrigger<ED, T, Cxt> | UpdateTrigger<ED, T, Cxt> | RemoveTrigger<ED, T, Cxt> | SelectTrigger<ED, T, Cxt>;
|
||
/**
|
||
* 触发器实体的数据形状
|
||
*
|
||
* 用于支持跨事务触发器的实体需要包含这些额外字段。
|
||
* 框架会自动管理这些字段,开发者一般不需要直接操作。
|
||
*
|
||
* @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<ED, T, Cxt>): trigger is VolatileTrigger<ED, T, Cxt> {
|
||
* return trigger.when === 'commit';
|
||
* }
|
||
*/
|
||
export type VolatileTrigger<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> = CreateTriggerCrossTxn<ED, T, Cxt> | UpdateTriggerCrossTxn<ED, T, Cxt> | RemoveTriggerCrossTxn<ED, T, Cxt>;
|
||
export {};
|