Archived
M5(3/3): trigger —— 三类触发器与起单幂等
- matchesEvent:事件触发的命中判定(S-FEEL 规则;规则写坏按不命中处置,不让事件流停摆) - assembleInput:字段映射装配;缺必填即起单失败入死信,不静默丢弃 - 幂等:同一事件对同一触发器至多起一单;数据日历型同一行同一日期只触发一次 - 周期两型:日历表达式到点扫描 + 数据日历型(日期字段 + 偏移 + 过滤)逐行起单 - 登记触发:人工起单的统一形态,登记人即来源 - 触发器与流程包平级:配置独立存放,增删启停不改流程包版本 340 个测试通过,门禁五道全绿 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
co-authored by
Claude Opus 5
parent
a8f6eef610
commit
4ad901c452
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* module: trigger —— 对外类型(编码规范 §10.1;语义出处 OS 3.1)。
|
||||
*
|
||||
* 触发器三类,**独立配置存放**,判定与调用接口调起。
|
||||
* 触发器与流程包平级:增删启停不改流程包的任何版本。
|
||||
*/
|
||||
import type { TriggerConfig, TriggerKind } from '@contracts';
|
||||
import type { AssemblyResult, EventScope } from './internal/domain/matching.ts';
|
||||
export type { AssemblyResult, EventScope, TriggerConfig, TriggerKind };
|
||||
export declare class TriggerInputMissingError extends Error {
|
||||
readonly triggerId: string;
|
||||
readonly fields: readonly string[];
|
||||
constructor(triggerId: string, fields: readonly string[]);
|
||||
}
|
||||
export type FireOutcome = {
|
||||
readonly triggerId: string;
|
||||
readonly workflowId?: string;
|
||||
readonly skipped?: 'duplicate' | 'disabled' | 'input-missing';
|
||||
readonly missing?: readonly string[];
|
||||
};
|
||||
export type TriggerService = {
|
||||
list(): Promise<TriggerConfig[]>;
|
||||
upsert(config: TriggerConfig): Promise<TriggerConfig>;
|
||||
setEnabled(id: string, enabled: boolean): Promise<void>;
|
||||
remove(id: string): Promise<void>;
|
||||
/** 事件到达:命中判定 —— 供 `event` 的 `TriggerMatcher` 端口使用 */
|
||||
match(scope: EventScope): Promise<string[]>;
|
||||
/** 命中即起单;同一事件对同一触发器**至多起一单**(事件 ID 为幂等键) */
|
||||
fireByEvent(triggerId: string, eventId: string, scope: EventScope): Promise<FireOutcome>;
|
||||
/** 登记触发:人工起单的统一形态 —— 登记人即来源 */
|
||||
fireByRegistration(triggerId: string, by: string, data: Record<string, unknown>): Promise<FireOutcome>;
|
||||
/** 周期触发的到点扫描(由 pg-boss 定时调起) */
|
||||
sweepPeriodic(now: Date): Promise<FireOutcome[]>;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* module: trigger —— 唯一出口。只做 re-export,不写逻辑(编码规范 §10.1)。
|
||||
*/
|
||||
export type { AssemblyResult, EventScope, FireOutcome, TriggerConfig, TriggerKind, TriggerService } from './contract.ts';
|
||||
export { TriggerInputMissingError } from './contract.ts';
|
||||
export type { Clock, DeadLetterSink, IdGenerator, LedgerScanner, ProcessGateway, Sfeel, TriggerStore } from './port.ts';
|
||||
export type { TriggerServiceDeps } from './internal/usecase/trigger-service.ts';
|
||||
export { createTriggerService } from './internal/usecase/trigger-service.ts';
|
||||
export { applyOffset, assembleInput, dataCalendarKey, matchesEvent, nextCalendarFire, scopeOf, triggerIdempotencyKey, } from './internal/domain/matching.ts';
|
||||
export { createTriggerStore } from './internal/adapter/trigger-store.ts';
|
||||
export { triggerClaimTable, triggerTable } from './internal/adapter/trigger-schema.ts';
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* module: trigger —— 本模块要外界提供的能力(编码规范 §10.1 / §5.3)。
|
||||
*/
|
||||
import type { TriggerConfig } from '@contracts';
|
||||
export type Clock = {
|
||||
now(): Date;
|
||||
};
|
||||
export type IdGenerator = {
|
||||
next(): string;
|
||||
};
|
||||
export type TriggerStore = {
|
||||
list(): Promise<TriggerConfig[]>;
|
||||
get(id: string): Promise<TriggerConfig | undefined>;
|
||||
upsert(config: TriggerConfig): Promise<TriggerConfig>;
|
||||
setEnabled(id: string, enabled: boolean): Promise<void>;
|
||||
remove(id: string): Promise<void>;
|
||||
/** 幂等:同一键至多起一单 */
|
||||
claim(key: string): Promise<boolean>;
|
||||
lastFiredAt(id: string): Promise<Date | undefined>;
|
||||
markFired(id: string, at: Date): Promise<void>;
|
||||
};
|
||||
/** 起单 —— app 注入 `process`(L7 同层?否:trigger 在 process 之上同层,经 app 装配) */
|
||||
export type ProcessGateway = {
|
||||
start(input: {
|
||||
packageId: string;
|
||||
subject: string;
|
||||
source: {
|
||||
kind: 'periodic' | 'event' | 'registration';
|
||||
ref: string;
|
||||
by?: string;
|
||||
};
|
||||
data: Record<string, unknown>;
|
||||
}): Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
/** 调用接口的必填字段 —— 装配缺项即起单失败 */
|
||||
requiredInputs(packageId: string): Promise<string[]>;
|
||||
};
|
||||
export type Sfeel = {
|
||||
condition(expression: string, scope: Readonly<Record<string, unknown>>): boolean;
|
||||
expression(expression: string, scope: Readonly<Record<string, unknown>>): unknown;
|
||||
};
|
||||
/** 死信 —— app 注入 `message`(L2):起单失败入死信 */
|
||||
export type DeadLetterSink = {
|
||||
report(input: {
|
||||
triggerId: string;
|
||||
reason: string;
|
||||
detail: Record<string, unknown>;
|
||||
}): Promise<void>;
|
||||
};
|
||||
/** 数据日历型要读台账 —— app 注入 `ledger`(L1) */
|
||||
export type LedgerScanner = {
|
||||
scan(input: {
|
||||
domain: string;
|
||||
object: string;
|
||||
dateField: string;
|
||||
filter?: string;
|
||||
}): Promise<Array<{
|
||||
key: Record<string, string | number>;
|
||||
date: string;
|
||||
row: Record<string, unknown>;
|
||||
}>>;
|
||||
};
|
||||
Reference in New Issue
Block a user