oak-domain 5.1.31 → 5.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,120 +5,756 @@ import { SyncContext } from "../store/SyncRowStore";
5
5
  import { EntityDict, OperateOption } from "../types/Entity";
6
6
  import { EntityShape } from "../types/Entity";
7
7
  import { EntityDict as BaseEntityDict } from '../base-app-domain';
8
+ /**
9
+ * Modi(延时更新)的执行时机
10
+ *
11
+ * 在某些业务场景中,数据修改不是立即生效的,而是先创建一个"修改申请"(Modi),
12
+ * 等待审批通过后才真正应用到数据上。这个类型定义了触发器在这种场景下的执行时机。
13
+ *
14
+ * @example
15
+ * // 场景:员工修改个人信息需要HR审批
16
+ * // 1. 员工提交修改 -> 创建 Modi 记录(create 阶段)
17
+ * // 2. HR 审批通过 -> 应用 Modi 到实际数据(apply 阶段)
18
+ *
19
+ * - 'create': 仅在创建 Modi 时执行触发器(如:发送审批通知)
20
+ * - 'apply': 仅在 Modi 被应用时执行触发器(如:同步数据到其他系统)
21
+ * - 'both': 两个阶段都执行触发器
22
+ *
23
+ * 默认行为:
24
+ * - commit 时机的触发器:默认只在 apply 阶段执行
25
+ * - 非 commit 时机的触发器:默认只在 create 阶段执行
26
+ *
27
+ * @see TriggerExecutor.judgeModiTurn 具体判断逻辑
28
+ */
8
29
  export type ModiTurn = 'create' | 'apply' | 'both';
9
30
  /**
10
- * 优先级越小,越早执行。定义在1~99之间
31
+ * 触发器最小优先级(最先执行)
32
+ * 用于需要最早执行的触发器,如数据预处理
11
33
  */
12
34
  export declare const TRIGGER_MIN_PRIORITY = 1;
35
+ /**
36
+ * 触发器默认优先级
37
+ * 未指定优先级时使用此值
38
+ */
13
39
  export declare const TRIGGER_DEFAULT_PRIORITY = 25;
40
+ /**
41
+ * 触发器最大优先级(在普通触发器中最后执行)
42
+ * 用于依赖其他触发器处理结果的场景
43
+ */
14
44
  export declare const TRIGGER_MAX_PRIORITY = 50;
45
+ /**
46
+ * Checker(检查器)的最大优先级
47
+ * Checker 优先级范围是 51-99,在所有普通触发器之后执行
48
+ * 这确保了数据校验在数据处理完成后进行
49
+ */
15
50
  export declare const CHECKER_MAX_PRIORITY = 99;
16
51
  /**
17
- * logical可能会更改row和data的值,应当最先执行,data和row不能修改相关的值
18
- * 允许logicalData去改data中的值
52
+ * 不同类型 Checker 的默认优先级映射
53
+ *
54
+ * 执行顺序(从先到后):
55
+ * 1. logicalData (31) - 逻辑数据检查,可修改 data 中的值
56
+ * 2. logical (33) - 纯逻辑检查
57
+ * 3. row (51) - 行级检查,检查数据库中已存在的行
58
+ * 4. relation (56) - 关系检查,检查关联数据
59
+ * 5. data (61) - 数据完整性检查
60
+ *
61
+ * @example
62
+ * // logicalData 类型可以自动填充默认值
63
+ * // logical 类型进行业务逻辑校验
64
+ * // row 类型检查当前行状态是否允许操作
19
65
  */
20
66
  export declare const CHECKER_PRIORITY_MAP: Record<CheckerType, number>;
67
+ /**
68
+ * 所有触发器的基础接口
69
+ *
70
+ * @template ED - 实体字典类型,包含所有实体的定义
71
+ * @template T - 当前触发器所属的实体名称
72
+ */
21
73
  interface TriggerBase<ED extends EntityDict & BaseEntityDict, T extends keyof ED> {
74
+ /**
75
+ * 如果此触发器是由 Checker 转换而来,记录原始 Checker 的类型
76
+ * 内部使用,一般不需要手动设置
77
+ */
22
78
  checkerType?: CheckerType;
79
+ /**
80
+ * 触发器所属的实体名称
81
+ * @example 'user', 'order', 'product'
82
+ */
23
83
  entity: T;
84
+ /**
85
+ * 触发器的唯一名称
86
+ * 用于标识和调试,必须在整个应用中唯一
87
+ * @example '订单创建后发送通知', 'user-create-init-profile'
88
+ */
24
89
  name: string;
90
+ /**
91
+ * 是否以 Root 模式执行
92
+ *
93
+ * 设置为 true 时,触发器内的所有操作将绕过权限检查。
94
+ * 适用于系统级操作,如自动填充审计字段、级联更新等。
95
+ *
96
+ * 注意:谨慎使用,可能导致权限漏洞
97
+ *
98
+ * @example
99
+ * // 自动记录操作日志,需要写入用户无权访问的日志表
100
+ * {
101
+ * name: '记录操作日志',
102
+ * asRoot: true,
103
+ * fn: async (event, context) => {
104
+ * await context.operate('operationLog', { ... });
105
+ * }
106
+ * }
107
+ */
25
108
  asRoot?: true;
109
+ /**
110
+ * 触发器执行优先级
111
+ *
112
+ * 范围:TRIGGER_MIN_PRIORITY (1) 到 CHECKER_MAX_PRIORITY (99)
113
+ * 数值越小,越先执行
114
+ *
115
+ * 建议:
116
+ * - 1-10: 数据预处理(如自动填充字段)
117
+ * - 11-30: 普通业务逻辑
118
+ * - 31-50: 依赖其他触发器结果的逻辑
119
+ * - 51-99: 数据校验(通常由 Checker 使用)
120
+ *
121
+ * @default TRIGGER_DEFAULT_PRIORITY (25)
122
+ */
26
123
  priority?: number;
27
124
  }
125
+ /**
126
+ * Create 触发器的基础接口
127
+ *
128
+ * @template ED - 实体字典类型
129
+ * @template T - 实体名称
130
+ * @template Cxt - 上下文类型(异步或同步)
131
+ *
132
+ * @example
133
+ * // 创建用户时自动初始化用户配置
134
+ * const trigger: CreateTrigger<ED, 'user', Cxt> = {
135
+ * name: '初始化用户配置',
136
+ * entity: 'user',
137
+ * action: 'create',
138
+ * when: 'after',
139
+ * fn: async ({ operation }, context) => {
140
+ * const userId = operation.data.id;
141
+ * await context.operate('userConfig', {
142
+ * action: 'create',
143
+ * data: { id: generateId(), userId, theme: 'default' }
144
+ * });
145
+ * return 1;
146
+ * }
147
+ * };
148
+ */
28
149
  export interface CreateTriggerBase<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends TriggerBase<ED, T> {
150
+ /**
151
+ * 触发动作类型,固定为 'create'
152
+ */
29
153
  action: 'create';
154
+ /**
155
+ * Modi(延时更新)场景下的执行时机
156
+ * @see ModiTurn
157
+ */
30
158
  mt?: ModiTurn;
159
+ /**
160
+ * 前置检查函数
161
+ *
162
+ * 在触发器执行前调用,返回 false 则跳过此触发器。
163
+ * 用于根据操作内容动态决定是否执行。
164
+ *
165
+ * @param operation - 当前的创建操作
166
+ * @returns true 执行触发器,false 跳过
167
+ *
168
+ * @example
169
+ * // 只有创建 VIP 用户时才发送欢迎邮件
170
+ * check: (operation) => operation.data.isVip === true
171
+ */
31
172
  check?: (operation: ED[T]['Create']) => boolean;
32
173
  }
174
+ /**
175
+ * 事务内 Create 触发器
176
+ *
177
+ * 在事务内执行,与主操作共享同一个事务。
178
+ * 如果触发器执行失败,整个事务会回滚。
179
+ *
180
+ * @example
181
+ * // before: 在数据插入前执行,可以修改要插入的数据
182
+ * const beforeTrigger: CreateTriggerInTxn<ED, 'order', Cxt> = {
183
+ * name: '自动生成订单号',
184
+ * entity: 'order',
185
+ * action: 'create',
186
+ * when: 'before',
187
+ * fn: ({ operation }, context) => {
188
+ * operation.data.orderNo = generateOrderNo();
189
+ * return 0; // 返回修改的行数(此处未实际修改数据库)
190
+ * }
191
+ * };
192
+ *
193
+ * // after: 在数据插入后执行,可以执行关联操作
194
+ * const afterTrigger: CreateTriggerInTxn<ED, 'order', Cxt> = {
195
+ * name: '创建订单日志',
196
+ * entity: 'order',
197
+ * action: 'create',
198
+ * when: 'after',
199
+ * fn: async ({ operation }, context) => {
200
+ * await context.operate('orderLog', {
201
+ * action: 'create',
202
+ * data: { orderId: operation.data.id, action: 'created' }
203
+ * });
204
+ * return 1;
205
+ * }
206
+ * };
207
+ */
33
208
  export interface CreateTriggerInTxn<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends CreateTriggerBase<ED, T, Cxt> {
209
+ /**
210
+ * 执行时机
211
+ * - 'before': 在数据插入前执行,可修改 operation.data
212
+ * - 'after': 在数据插入后执行,数据已存在于数据库中
213
+ */
34
214
  when: 'before' | 'after';
215
+ /**
216
+ * 触发器执行函数
217
+ *
218
+ * @param event.operation - 当前的创建操作,包含 action 和 data
219
+ * @param context - 执行上下文,可用于执行其他数据库操作
220
+ * @param option - 操作选项
221
+ * @returns 返回此触发器影响的行数(用于日志记录)
222
+ */
35
223
  fn: (event: {
36
224
  operation: ED[T]['Create'];
37
225
  }, context: Cxt, option: OperateOption) => Promise<number> | number;
38
226
  }
227
+ /**
228
+ * 跨事务触发器的通用接口
229
+ *
230
+ * 跨事务触发器在主事务提交后异步执行,适用于:
231
+ * - 不需要与主操作保持原子性的操作
232
+ * - 耗时较长的操作(如发送邮件、调用外部API)
233
+ * - 可以容忍最终一致性的场景
234
+ *
235
+ * 通过 checkpoint 机制保证最终一定会执行成功。
236
+ */
39
237
  interface TriggerCrossTxn<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> {
238
+ /**
239
+ * 执行时机,固定为 'commit'
240
+ * 表示在主事务提交后执行
241
+ */
40
242
  when: 'commit';
243
+ /**
244
+ * 执行严格程度
245
+ *
246
+ * - 'takeEasy': 尽力执行,失败后不重试
247
+ * 适用于非关键操作,如发送通知
248
+ *
249
+ * - 'makeSure': 必须确保执行成功
250
+ * 会在数据行上标记 $$triggerData$$ 和 $$triggerUuid$$,
251
+ * 通过 checkpoint 机制保证最终执行成功
252
+ * 适用于关键业务操作,如同步数据到外部系统
253
+ *
254
+ * @default 'takeEasy'
255
+ *
256
+ * @example
257
+ * // 发送欢迎邮件 - 失败了也没关系
258
+ * { strict: 'takeEasy' }
259
+ *
260
+ * // 同步订单到ERP - 必须成功
261
+ * { strict: 'makeSure' }
262
+ */
41
263
  strict?: 'takeEasy' | 'makeSure';
264
+ /**
265
+ * 集群敏感标记
266
+ *
267
+ * 设置为 true 时,表示此触发器需要由特定的集群节点处理。
268
+ * 用于需要特殊资源或环境的触发器。
269
+ */
42
270
  cs?: true;
271
+ /**
272
+ * 是否自行清理触发器标记数据
273
+ *
274
+ * 设置为 true 时,框架不会自动清除 $$triggerData$$ 和 $$triggerUuid$$,
275
+ * 需要在 fn 函数中自行处理。
276
+ *
277
+ * 适用于需要自定义清理逻辑的场景。
278
+ */
43
279
  cleanTriggerDataBySelf?: true;
280
+ /**
281
+ * 单例模式标记
282
+ *
283
+ * 设置为 true 时,在集群环境中只有一个进程会执行此触发器。
284
+ * 用于防止重复执行的场景,如定时任务。
285
+ */
44
286
  singleton?: true;
287
+ /**
288
+ * 分组处理标记
289
+ *
290
+ * 设置为 true 时,checkpoint 会将所有未处理的行 ID 一次性传入 fn 函数。
291
+ * 适用于批量处理更高效的场景。
292
+ *
293
+ * - false (默认): 按 triggerUuid 分组,每批单独调用 fn
294
+ * - true: 所有未处理的行一次性传入
295
+ *
296
+ * @example
297
+ * // 批量同步数据到外部系统
298
+ * {
299
+ * grouped: true,
300
+ * fn: async ({ ids }, context) => {
301
+ * await syncToExternalSystem(ids); // 批量处理更高效
302
+ * }
303
+ * }
304
+ */
45
305
  grouped?: true;
306
+ /**
307
+ * 触发器执行函数
308
+ *
309
+ * @param event.ids - 需要处理的实体 ID 数组
310
+ * @param context - 执行上下文
311
+ * @param option - 操作选项
312
+ * @returns 可选的返回值:
313
+ * - void: 无后续操作
314
+ * - 函数: 在清理标记后执行的回调函数
315
+ * - 对象: 需要更新到数据行上的额外数据
316
+ *
317
+ * @example
318
+ * // 基本用法
319
+ * fn: async ({ ids }, context) => {
320
+ * for (const id of ids) {
321
+ * await sendNotification(id);
322
+ * }
323
+ * }
324
+ *
325
+ * // 返回回调函数,在清理完成后执行
326
+ * fn: async ({ ids }, context) => {
327
+ * const results = await processIds(ids);
328
+ * return async (ctx, opt) => {
329
+ * await saveResults(results);
330
+ * };
331
+ * }
332
+ *
333
+ * // 返回需要更新的数据
334
+ * fn: async ({ ids }, context) => {
335
+ * return { syncedAt: Date.now() };
336
+ * }
337
+ */
46
338
  fn: (event: {
47
339
  ids: string[];
48
340
  }, context: Cxt, option: OperateOption) => Promise<((context: Cxt, option: OperateOption) => Promise<any>) | void | ED[T]['Update']['data']>;
49
341
  }
342
+ /**
343
+ * 跨事务 Create 触发器
344
+ *
345
+ * 组合了 CreateTriggerBase 和 TriggerCrossTxn 的特性。
346
+ * 在实体创建的事务提交后异步执行。
347
+ *
348
+ * @example
349
+ * const trigger: CreateTriggerCrossTxn<ED, 'user', Cxt> = {
350
+ * name: '发送欢迎邮件',
351
+ * entity: 'user',
352
+ * action: 'create',
353
+ * when: 'commit',
354
+ * strict: 'makeSure', // 确保发送成功
355
+ * fn: async ({ ids }, context) => {
356
+ * for (const userId of ids) {
357
+ * await emailService.sendWelcome(userId);
358
+ * }
359
+ * }
360
+ * };
361
+ */
50
362
  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> {
51
363
  }
364
+ /**
365
+ * Create 触发器类型联合
366
+ * 包括事务内触发器和跨事务触发器
367
+ */
52
368
  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>;
53
369
  /**
54
- * update trigger如果带有filter,说明只对存在限定条件的行起作用。此时系统在进行相应动作时,
55
- * 会判定当前动作的filter条件和trigger所定义的filter是否有交集(即有同时满足两个条件的行)
56
- * 只要有,就会触发trigger。要注意的是这个条件是exists而不是all
370
+ * Update 触发器的基础接口
371
+ *
372
+ * Update 触发器支持条件过滤(filter),只对满足条件的行触发。
373
+ * 这是与 Create 触发器的主要区别之一。
374
+ *
375
+ * @template ED - 实体字典类型
376
+ * @template T - 实体名称
377
+ * @template Cxt - 上下文类型
378
+ *
379
+ * @example
380
+ * // 订单状态变为"已支付"时触发
381
+ * const trigger: UpdateTrigger<ED, 'order', Cxt> = {
382
+ * name: '订单支付成功处理',
383
+ * entity: 'order',
384
+ * action: 'pay', // 支付动作
385
+ * when: 'after',
386
+ * filter: { status: 'pending' }, // 只对待支付订单触发
387
+ * attributes: ['status'], // 只在 status 字段变化时触发
388
+ * fn: async ({ operation }, context) => {
389
+ * // 处理支付成功逻辑
390
+ * }
391
+ * };
57
392
  */
58
393
  export interface UpdateTriggerBase<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends TriggerBase<ED, T> {
394
+ /**
395
+ * 触发动作类型
396
+ *
397
+ * 可以是单个动作或动作数组。
398
+ * 排除了 'create'、'remove'、'select' 等非更新动作。
399
+ *
400
+ * @example
401
+ * action: 'update' // 单个动作
402
+ * action: ['update', 'pay'] // 多个动作
403
+ */
59
404
  action: Exclude<ED[T]['Action'], ExcludeUpdateAction> | Array<Exclude<ED[T]['Action'], ExcludeUpdateAction>>;
405
+ /**
406
+ * 监听的属性列表
407
+ *
408
+ * 只有当这些属性出现在更新数据中时才触发。
409
+ * 用于优化性能,避免不必要的触发器执行。
410
+ *
411
+ * 注意:检查的是 operation.data 中是否包含这些属性,
412
+ * 而不是属性值是否真的发生了变化。
413
+ *
414
+ * @example
415
+ * // 只在 price 或 quantity 变化时重新计算总价
416
+ * attributes: ['price', 'quantity']
417
+ */
60
418
  attributes?: Array<keyof ED[T]['OpSchema']>;
419
+ /**
420
+ * Modi(延时更新)场景下的执行时机
421
+ * @see ModiTurn
422
+ */
61
423
  mt?: ModiTurn;
424
+ /**
425
+ * 前置检查函数
426
+ *
427
+ * @param operation - 当前的更新操作
428
+ * @returns true 执行触发器,false 跳过
429
+ */
62
430
  check?: (operation: ED[T]['Update']) => boolean;
431
+ /**
432
+ * 条件过滤器
433
+ *
434
+ * 只对满足过滤条件的行触发此触发器。
435
+ * 可以是静态过滤对象,也可以是动态生成过滤条件的函数。
436
+ *
437
+ * 框架会检查操作的 filter 与触发器的 filter 是否有交集,
438
+ * 只要可能存在同时满足两个条件的行,就会触发。
439
+ *
440
+ * @example
441
+ * // 静态过滤:只对状态为 active 的订单触发
442
+ * filter: { status: 'active' }
443
+ *
444
+ * // 动态过滤:根据操作内容决定过滤条件
445
+ * filter: (operation, context) => {
446
+ * if (operation.data.type === 'vip') {
447
+ * return { level: { $gte: 5 } };
448
+ * }
449
+ * return { level: { $gte: 1 } };
450
+ * }
451
+ */
63
452
  filter?: ED[T]['Filter'] | ((operation: ED[T]['Update'], context: Cxt, option: OperateOption) => ED[T]['Filter'] | Promise<ED[T]['Filter']>);
64
453
  }
454
+ /**
455
+ * 事务内 Update 触发器
456
+ *
457
+ * @example
458
+ * // 更新库存时检查是否需要补货提醒
459
+ * const trigger: UpdateTriggerInTxn<ED, 'product', Cxt> = {
460
+ * name: '库存不足提醒',
461
+ * entity: 'product',
462
+ * action: 'update',
463
+ * when: 'after',
464
+ * attributes: ['stock'],
465
+ * filter: { stock: { $lt: 10 } }, // 库存小于10时
466
+ * fn: async ({ operation }, context) => {
467
+ * // 创建补货提醒
468
+ * return 1;
469
+ * }
470
+ * };
471
+ */
65
472
  export interface UpdateTriggerInTxn<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends UpdateTriggerBase<ED, T, Cxt> {
473
+ /**
474
+ * 执行时机
475
+ * - 'before': 在数据更新前执行,可修改 operation.data
476
+ * - 'after': 在数据更新后执行
477
+ */
66
478
  when: 'before' | 'after';
479
+ /**
480
+ * 触发器执行函数
481
+ *
482
+ * @param event.operation - 当前的更新操作
483
+ * @param context - 执行上下文
484
+ * @param option - 操作选项
485
+ * @returns 影响的行数
486
+ */
67
487
  fn: (event: {
68
488
  operation: ED[T]['Update'];
69
489
  }, context: Cxt, option: OperateOption) => Promise<number> | number;
70
490
  }
491
+ /**
492
+ * 跨事务 Update 触发器
493
+ *
494
+ * @example
495
+ * // 订单完成后同步到财务系统
496
+ * const trigger: UpdateTriggerCrossTxn<ED, 'order', Cxt> = {
497
+ * name: '同步订单到财务系统',
498
+ * entity: 'order',
499
+ * action: 'complete',
500
+ * when: 'commit',
501
+ * strict: 'makeSure',
502
+ * filter: { status: 'completed' },
503
+ * fn: async ({ ids }, context) => {
504
+ * await financeSystem.syncOrders(ids);
505
+ * }
506
+ * };
507
+ */
71
508
  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> {
72
509
  }
510
+ /**
511
+ * Update 触发器类型联合
512
+ */
73
513
  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>;
74
514
  /**
75
- * 同update trigger一样,remove trigger如果带有filter,说明只对存在限定条件的行起作用。此时系统在进行相应动作时,
76
- * 会判定当前动作的filter条件和trigger所定义的filter是否有交集(即有同时满足两个条件的行)
77
- * 只要有,就会触发trigger。要注意的是这个条件是exists而不是all
515
+ * Remove 触发器的基础接口
516
+ *
517
+ * 与 Update 触发器类似,支持条件过滤。
518
+ *
519
+ * @example
520
+ * // 删除用户时清理关联数据
521
+ * const trigger: RemoveTrigger<ED, 'user', Cxt> = {
522
+ * name: '清理用户关联数据',
523
+ * entity: 'user',
524
+ * action: 'remove',
525
+ * when: 'before',
526
+ * fn: async ({ operation }, context) => {
527
+ * const { filter } = operation;
528
+ * // 删除用户的所有订单
529
+ * await context.operate('order', {
530
+ * id: await generateNewIdAsync(),
531
+ * action: 'remove',
532
+ * filter: { user: filter }
533
+ * });
534
+ * return 1;
535
+ * }
536
+ * };
78
537
  */
79
538
  export interface RemoveTriggerBase<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends TriggerBase<ED, T> {
539
+ /**
540
+ * 触发动作类型,固定为 'remove'
541
+ */
80
542
  action: 'remove';
543
+ /**
544
+ * Modi(延时更新)场景下的执行时机
545
+ */
81
546
  mt?: ModiTurn;
547
+ /**
548
+ * 前置检查函数
549
+ */
82
550
  check?: (operation: ED[T]['Remove']) => boolean;
551
+ /**
552
+ * 条件过滤器
553
+ * 只对满足条件的行触发
554
+ */
83
555
  filter?: ED[T]['Remove']['filter'] | ((operation: ED[T]['Remove'], context: Cxt, option: OperateOption) => ED[T]['Remove']['filter'] | Promise<ED[T]['Remove']['filter']>);
84
556
  }
557
+ /**
558
+ * 事务内 Remove 触发器
559
+ */
85
560
  export interface RemoveTriggerInTxn<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends RemoveTriggerBase<ED, T, Cxt> {
86
561
  when: 'before' | 'after';
87
562
  fn: (event: {
88
563
  operation: ED[T]['Remove'];
89
564
  }, context: Cxt, option: OperateOption) => Promise<number> | number;
90
565
  }
566
+ /**
567
+ * 跨事务 Remove 触发器
568
+ */
91
569
  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> {
92
570
  }
571
+ /**
572
+ * Remove 触发器类型联合
573
+ */
93
574
  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>;
575
+ /**
576
+ * Select 触发器的基础接口
577
+ *
578
+ * Select 触发器用于在查询前后进行拦截处理,常见用途:
579
+ * - 查询前:添加额外的过滤条件(如数据权限过滤)
580
+ * - 查询后:对结果进行加工处理(如脱敏、格式化)
581
+ */
94
582
  export interface SelectTriggerBase<ED extends EntityDict & BaseEntityDict, T extends keyof ED> extends TriggerBase<ED, T> {
583
+ /**
584
+ * 触发动作类型,固定为 'select'
585
+ */
95
586
  action: 'select';
96
587
  }
97
588
  /**
98
- * selection似乎不需要支持跨事务?没想清楚
99
- * todo by Xc
589
+ * 查询前 Select 触发器
590
+ *
591
+ * 在执行查询前触发,可用于:
592
+ * - 修改查询条件(如添加数据权限过滤)
593
+ * - 记录查询日志
594
+ * - 查询频率限制
595
+ *
596
+ * 注意:Select 触发器目前不支持跨事务模式
597
+ *
598
+ * @example
599
+ * // 自动添加系统ID过滤
600
+ * const trigger: SelectTriggerBefore<ED, 'order', Cxt> = {
601
+ * name: '修改filter,添加systemId',
602
+ * entity: 'order',
603
+ * action: 'select',
604
+ * when: 'before',
605
+ * fn: ({ operation }, context) => {
606
+ * const systemId = context.getSystemId();
607
+ * // 修改查询条件,添加租户过滤
608
+ * operation.filter = {
609
+ * ...operation.filter,
610
+ * systemId,
611
+ * };
612
+ * return 0;
613
+ * }
614
+ * };
100
615
  */
101
616
  export interface SelectTriggerBefore<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends SelectTriggerBase<ED, T> {
617
+ /**
618
+ * 执行时机,固定为 'before'(查询执行前)
619
+ */
102
620
  when: 'before';
621
+ /**
622
+ * 触发器执行函数
623
+ *
624
+ * @param event.operation - 当前的查询操作,可直接修改
625
+ * @param context - 执行上下文
626
+ * @param params - 查询选项
627
+ * @returns 影响的行数(通常返回 0,因为只是修改了查询条件)
628
+ */
103
629
  fn: (event: {
104
630
  operation: ED[T]['Selection'];
105
631
  }, context: Cxt, params?: SelectOption) => Promise<number> | number;
106
632
  }
633
+ /**
634
+ * 查询后 Select 触发器
635
+ *
636
+ * 在查询执行后触发,可用于:
637
+ * - 数据脱敏(如隐藏敏感信息)
638
+ * - 数据格式化(如日期格式转换)
639
+ * - 计算派生字段
640
+ * - 权限过滤(移除无权查看的字段)
641
+ *
642
+ * @example
643
+ * // 计算订单的总金额(派生字段)
644
+ * const trigger: SelectTriggerAfter<ED, 'order', Cxt> = {
645
+ * name: '计算订单总金额',
646
+ * entity: 'order',
647
+ * action: 'select',
648
+ * when: 'after',
649
+ * fn: ({ operation, result }, context) => {
650
+ * result.forEach(order => {
651
+ * if (order.items) {
652
+ * order.totalAmount = order.items.reduce(
653
+ * (sum, item) => sum + item.price * item.quantity, 0
654
+ * );
655
+ * }
656
+ * });
657
+ * return result.length;
658
+ * }
659
+ * };
660
+ *
661
+ * // 根据权限隐藏敏感字段
662
+ * const sensitiveDataTrigger: SelectTriggerAfter<ED, 'employee', Cxt> = {
663
+ * name: '隐藏员工敏感信息',
664
+ * entity: 'employee',
665
+ * action: 'select',
666
+ * when: 'after',
667
+ * fn: ({ result }, context) => {
668
+ * const isRoot = context.isRoot();
669
+ * if (!isRoot) {
670
+ * result.forEach(emp => {
671
+ * delete emp.salary;
672
+ * delete emp.idCard;
673
+ * });
674
+ * }
675
+ * return result.length;
676
+ * }
677
+ * };
678
+ */
107
679
  export interface SelectTriggerAfter<ED extends EntityDict & BaseEntityDict, T extends keyof ED, Cxt extends AsyncContext<ED> | SyncContext<ED>> extends SelectTriggerBase<ED, T> {
680
+ /**
681
+ * 执行时机,固定为 'after'(查询执行后)
682
+ */
108
683
  when: 'after';
684
+ /**
685
+ * 触发器执行函数
686
+ *
687
+ * @param event.operation - 当前的查询操作
688
+ * @param event.result - 查询结果数组,可直接修改
689
+ * @param context - 执行上下文
690
+ * @param params - 查询选项
691
+ * @returns 处理的行数
692
+ */
109
693
  fn: (event: {
110
694
  operation: ED[T]['Selection'];
111
695
  result: Partial<ED[T]['Schema']>[];
112
696
  }, context: Cxt, params?: SelectOption) => Promise<number> | number;
113
697
  }
698
+ /**
699
+ * Select 触发器类型联合
700
+ */
114
701
  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>;
702
+ /**
703
+ * 所有触发器类型的联合
704
+ *
705
+ * 包含所有 CRUD 操作的触发器类型:
706
+ * - CreateTrigger: 创建触发器
707
+ * - UpdateTrigger: 更新触发器
708
+ * - RemoveTrigger: 删除触发器
709
+ * - SelectTrigger: 查询触发器
710
+ *
711
+ * @example
712
+ * // 注册触发器时使用
713
+ * function registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>) {
714
+ * triggerExecutor.registerTrigger(trigger);
715
+ * }
716
+ */
115
717
  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>;
718
+ /**
719
+ * 触发器实体的数据形状
720
+ *
721
+ * 用于支持跨事务触发器的实体需要包含这些额外字段。
722
+ * 框架会自动管理这些字段,开发者一般不需要直接操作。
723
+ *
724
+ * @deprecated 建议使用框架定义的 TriggerDataAttribute 和 TriggerUuidAttribute 常量
725
+ */
116
726
  export interface TriggerEntityShape extends EntityShape {
727
+ /**
728
+ * 跨事务触发器的执行数据
729
+ * 存储触发器名称和原始操作信息,用于断点恢复
730
+ */
117
731
  $$triggerData$$?: {
118
732
  name: string;
119
733
  operation: object;
120
734
  };
735
+ /**
736
+ * 跨事务触发器的时间戳
737
+ * @deprecated 已被 $$triggerUuid$$ 替代
738
+ */
121
739
  $$triggerTimestamp$$?: number;
122
740
  }
741
+ /**
742
+ * 跨事务(Volatile)触发器类型联合
743
+ *
744
+ * 这是所有 when: 'commit' 触发器的类型联合。
745
+ * 这些触发器在事务提交后异步执行,通过 checkpoint 机制保证最终执行。
746
+ *
747
+ * 特点:
748
+ * - 不与主操作共享事务
749
+ * - 可能延迟执行
750
+ * - 通过标记机制保证最终一致性
751
+ * - 需要考虑幂等性(可能被多次调用)
752
+ *
753
+ * @example
754
+ * // 判断一个触发器是否是跨事务触发器
755
+ * function isVolatileTrigger(trigger: Trigger<ED, T, Cxt>): trigger is VolatileTrigger<ED, T, Cxt> {
756
+ * return trigger.when === 'commit';
757
+ * }
758
+ */
123
759
  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>;
124
760
  export {};