@yss123/cage-kernel 0.0.0-stage → 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +18 -2
- package/dist/brands.d.ts +32 -0
- package/dist/brands.js +35 -0
- package/dist/brands.js.map +1 -0
- package/dist/db/connect.d.ts +28 -0
- package/dist/db/connect.js +57 -0
- package/dist/db/connect.js.map +1 -0
- package/dist/db/ddl.d.ts +62 -0
- package/dist/db/ddl.js +454 -0
- package/dist/db/ddl.js.map +1 -0
- package/dist/db/index.d.ts +5 -0
- package/dist/db/index.js +7 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrate.d.ts +22 -0
- package/dist/db/migrate.js +87 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/rows.d.ts +10 -0
- package/dist/db/rows.js +56 -0
- package/dist/db/rows.js.map +1 -0
- package/dist/db/session.d.ts +4 -0
- package/dist/db/session.js +20 -0
- package/dist/db/session.js.map +1 -0
- package/dist/errors.d.ts +44 -0
- package/dist/errors.js +81 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel.d.ts +97 -0
- package/dist/kernel.js +440 -0
- package/dist/kernel.js.map +1 -0
- package/dist/mask.d.ts +13 -0
- package/dist/mask.js +62 -0
- package/dist/mask.js.map +1 -0
- package/dist/org.d.ts +46 -0
- package/dist/org.js +127 -0
- package/dist/org.js.map +1 -0
- package/dist/pipeline/idempotency.d.ts +10 -0
- package/dist/pipeline/idempotency.js +29 -0
- package/dist/pipeline/idempotency.js.map +1 -0
- package/dist/pipeline/load.d.ts +15 -0
- package/dist/pipeline/load.js +84 -0
- package/dist/pipeline/load.js.map +1 -0
- package/dist/primitives/aggregate.d.ts +75 -0
- package/dist/primitives/aggregate.js +67 -0
- package/dist/primitives/aggregate.js.map +1 -0
- package/dist/primitives/command.d.ts +101 -0
- package/dist/primitives/command.js +49 -0
- package/dist/primitives/command.js.map +1 -0
- package/dist/primitives/constraints.d.ts +36 -0
- package/dist/primitives/constraints.js +41 -0
- package/dist/primitives/constraints.js.map +1 -0
- package/dist/primitives/event.d.ts +31 -0
- package/dist/primitives/event.js +42 -0
- package/dist/primitives/event.js.map +1 -0
- package/dist/primitives/fields.d.ts +107 -0
- package/dist/primitives/fields.js +122 -0
- package/dist/primitives/fields.js.map +1 -0
- package/dist/primitives/flow.d.ts +126 -0
- package/dist/primitives/flow.js +90 -0
- package/dist/primitives/flow.js.map +1 -0
- package/dist/primitives/states.d.ts +19 -0
- package/dist/primitives/states.js +37 -0
- package/dist/primitives/states.js.map +1 -0
- package/dist/read.d.ts +47 -0
- package/dist/read.js +170 -0
- package/dist/read.js.map +1 -0
- package/dist/registry.d.ts +71 -0
- package/dist/registry.js +151 -0
- package/dist/registry.js.map +1 -0
- package/dist/scope/index.d.ts +42 -0
- package/dist/scope/index.js +90 -0
- package/dist/scope/index.js.map +1 -0
- package/dist/telemetry.d.ts +11 -0
- package/dist/telemetry.js +35 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/util/names.d.ts +9 -0
- package/dist/util/names.js +38 -0
- package/dist/util/names.js.map +1 -0
- package/dist/worker/consumers.d.ts +29 -0
- package/dist/worker/consumers.js +79 -0
- package/dist/worker/consumers.js.map +1 -0
- package/dist/worker/index.d.ts +36 -0
- package/dist/worker/index.js +120 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/outbox.d.ts +38 -0
- package/dist/worker/outbox.js +121 -0
- package/dist/worker/outbox.js.map +1 -0
- package/dist/worker/scheduler.d.ts +7 -0
- package/dist/worker/scheduler.js +77 -0
- package/dist/worker/scheduler.js.map +1 -0
- package/package.json +58 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// 链路追踪:流水线每一关一个 span;写 outbox 时把链路上下文带上,worker 处理时接上。
|
|
2
|
+
import { context, propagation, trace } from '@opentelemetry/api';
|
|
3
|
+
const tracer = trace.getTracer('cage');
|
|
4
|
+
export async function step(name, fn) {
|
|
5
|
+
return tracer.startActiveSpan(`cage.${name}`, async (span) => {
|
|
6
|
+
try {
|
|
7
|
+
return await fn();
|
|
8
|
+
}
|
|
9
|
+
catch (e) {
|
|
10
|
+
span.recordException(e);
|
|
11
|
+
throw e;
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
span.end();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function traceCarrier() {
|
|
19
|
+
const carrier = {};
|
|
20
|
+
propagation.inject(context.active(), carrier);
|
|
21
|
+
return carrier;
|
|
22
|
+
}
|
|
23
|
+
export function withCarrier(carrier, fn) {
|
|
24
|
+
if (!carrier)
|
|
25
|
+
return fn();
|
|
26
|
+
const ctx = propagation.extract(context.active(), carrier);
|
|
27
|
+
return context.with(ctx, fn);
|
|
28
|
+
}
|
|
29
|
+
export const consoleLogger = {
|
|
30
|
+
info: (o, m) => console.log(m ?? '', typeof o === 'string' ? o : JSON.stringify(o)),
|
|
31
|
+
warn: (o, m) => console.warn(m ?? '', typeof o === 'string' ? o : JSON.stringify(o)),
|
|
32
|
+
error: (o, m) => console.error(m ?? '', typeof o === 'string' ? o : JSON.stringify(o)),
|
|
33
|
+
};
|
|
34
|
+
export const silentLogger = { info() { }, warn() { }, error() { } };
|
|
35
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEjE,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,IAAI,CAAI,IAAY,EAAE,EAAoB;IAC9D,OAAO,MAAM,CAAC,eAAe,CAAC,QAAQ,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAC3D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,eAAe,CAAC,CAAU,CAAC,CAAC;YACjC,MAAM,CAAC,CAAC;QACV,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;IAC9C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,WAAW,CAAI,OAAkD,EAAE,EAAoB;IACrG,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;IAC3D,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAC/B,CAAC;AASD,MAAM,CAAC,MAAM,aAAa,GAAW;IACnC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACnF,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACpF,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;CACvF,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAW,EAAE,IAAI,KAAI,CAAC,EAAE,IAAI,KAAI,CAAC,EAAE,KAAK,KAAI,CAAC,EAAE,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** 主体种类。系统身份(process/job/external/reaction)只能调用声明了对应 invokedBy 的命令,或自己声明过的命令。 */
|
|
2
|
+
export type ActorKind = 'user' | 'process' | 'job' | 'external' | 'reaction' | 'system' | 'anonymous';
|
|
3
|
+
export interface Actor {
|
|
4
|
+
kind: ActorKind;
|
|
5
|
+
/** 用户 id,或系统身份名,例如 job:expireStaleOrders */
|
|
6
|
+
id: string;
|
|
7
|
+
}
|
|
8
|
+
/** 写入来源,记入审计 */
|
|
9
|
+
export type Source = 'api' | 'process' | 'job' | 'import' | 'external' | 'reaction' | 'system';
|
|
10
|
+
/** 范围组合子:封闭集合,每个都同时有内存语义和 SQL 语义 */
|
|
11
|
+
export type Combinator = 'all' | 'self' | 'dept' | 'deptSubtree' | 'deptSet';
|
|
12
|
+
export declare const COMBINATORS: readonly Combinator[];
|
|
13
|
+
/** 组合子由窄到宽的顺序,用于「不能授出比自己更宽的范围」 */
|
|
14
|
+
export declare const COMBINATOR_WIDTH: Record<Combinator, number>;
|
|
15
|
+
/** 角色在某个聚合上的数据范围授权 */
|
|
16
|
+
export interface ScopeGrant {
|
|
17
|
+
combinator: Combinator;
|
|
18
|
+
/** deptSet 用:角色配置的部门集合 */
|
|
19
|
+
deptIds?: string[];
|
|
20
|
+
}
|
|
21
|
+
/** 能力点参数,例如 { ceiling: 50000 } */
|
|
22
|
+
export type CapabilityParams = Record<string, unknown>;
|
|
23
|
+
/** 当前主体的上下文。HTTP 认证、审批桥接、调度器、反应各自构造它,交给内核。 */
|
|
24
|
+
export interface Ctx {
|
|
25
|
+
actor: Actor | null;
|
|
26
|
+
/** 当前真人用户;系统身份时为 null(真人在 onBehalfOf) */
|
|
27
|
+
userId: string | null;
|
|
28
|
+
/** 系统身份代表的真人,用于职责分离和审计 */
|
|
29
|
+
onBehalfOf?: string | null;
|
|
30
|
+
tenantId: string;
|
|
31
|
+
deptId: string | null;
|
|
32
|
+
/** 当前部门的祖先路径(含自己),例如 /root/purchase/ */
|
|
33
|
+
deptPath: string | null;
|
|
34
|
+
/** 能力集合及参数;多角色取并集,额度取最大值 */
|
|
35
|
+
capabilities: Map<string, CapabilityParams>;
|
|
36
|
+
/** 各聚合上的范围授权,多角色取并集(OR);未配置的聚合按「仅本人」 */
|
|
37
|
+
scopes: Record<string, ScopeGrant[]>;
|
|
38
|
+
/** 可以看明文的「聚合.种类」,例如 PurchaseOrder.phone */
|
|
39
|
+
unmask: string[];
|
|
40
|
+
source: Source;
|
|
41
|
+
requestId?: string;
|
|
42
|
+
idempotencyKey?: string;
|
|
43
|
+
/** 二次认证时间(毫秒时间戳) */
|
|
44
|
+
stepUpAt?: number | null;
|
|
45
|
+
client?: {
|
|
46
|
+
ip?: string;
|
|
47
|
+
userAgent?: string;
|
|
48
|
+
};
|
|
49
|
+
/** onBehalfOf 那个人的能力(额度类约束按他判断);内核会按需加载 */
|
|
50
|
+
onBehalfOfCapabilities?: Map<string, CapabilityParams>;
|
|
51
|
+
}
|
|
52
|
+
export type MaybePromise<T> = T | Promise<T>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAgB1B,MAAM,CAAC,MAAM,WAAW,GAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;AACpG,kCAAkC;AAClC,MAAM,CAAC,MAAM,gBAAgB,GAA+B,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function snake(s: string): string;
|
|
2
|
+
export declare function kebab(s: string): string;
|
|
3
|
+
export declare function camel(s: string): string;
|
|
4
|
+
export declare function lowerFirst(s: string): string;
|
|
5
|
+
export declare function upperFirst(s: string): string;
|
|
6
|
+
/** 32 位哈希(FNV-1a),用于咨询锁编号 */
|
|
7
|
+
export declare function hash32(s: string): number;
|
|
8
|
+
/** 稳定的 JSON 序列化(键排序),用于本体图比较 */
|
|
9
|
+
export declare function stableStringify(v: unknown): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// 命名转换:聚合名大驼峰 → 表名 snake_case、路由 kebab-case;字段名 camelCase ↔ 列名 snake_case。
|
|
2
|
+
export function snake(s) {
|
|
3
|
+
return s
|
|
4
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
5
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
6
|
+
.toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
export function kebab(s) {
|
|
9
|
+
return snake(s).replace(/_/g, '-');
|
|
10
|
+
}
|
|
11
|
+
export function camel(s) {
|
|
12
|
+
return s.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
13
|
+
}
|
|
14
|
+
export function lowerFirst(s) {
|
|
15
|
+
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
16
|
+
}
|
|
17
|
+
export function upperFirst(s) {
|
|
18
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
19
|
+
}
|
|
20
|
+
/** 32 位哈希(FNV-1a),用于咨询锁编号 */
|
|
21
|
+
export function hash32(s) {
|
|
22
|
+
let h = 0x811c9dc5;
|
|
23
|
+
for (let i = 0; i < s.length; i++) {
|
|
24
|
+
h ^= s.charCodeAt(i);
|
|
25
|
+
h = Math.imul(h, 0x01000193);
|
|
26
|
+
}
|
|
27
|
+
return h | 0;
|
|
28
|
+
}
|
|
29
|
+
/** 稳定的 JSON 序列化(键排序),用于本体图比较 */
|
|
30
|
+
export function stableStringify(v) {
|
|
31
|
+
if (v === null || typeof v !== 'object')
|
|
32
|
+
return JSON.stringify(v);
|
|
33
|
+
if (Array.isArray(v))
|
|
34
|
+
return `[${v.map(stableStringify).join(',')}]`;
|
|
35
|
+
const keys = Object.keys(v).filter((k) => v[k] !== undefined).sort();
|
|
36
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(v[k])}`).join(',')}}`;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.js","sourceRoot":"","sources":["../../src/util/names.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,CAAC;SACL,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC;SACzC,WAAW,EAAE,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,6BAA6B;AAC7B,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,IAAI,CAAC,GAAG,UAAU,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,eAAe,CAAC,CAAU;IACxC,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACrE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAE,CAAS,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,eAAe,CAAE,CAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AACpG,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Transaction } from 'kysely';
|
|
2
|
+
import type { Kernel } from '../kernel.js';
|
|
3
|
+
import type { EventConsumer } from './outbox.js';
|
|
4
|
+
/** 审批引擎的最小接口(完整接口见 @yss123/cage-approval)。引擎只给结论,结论由桥接代码转成命令。 */
|
|
5
|
+
export interface ApprovalEngine {
|
|
6
|
+
start(input: {
|
|
7
|
+
process: string;
|
|
8
|
+
businessKey: string;
|
|
9
|
+
tenantId: string;
|
|
10
|
+
/** 只包含声明过的 routable 字段 */
|
|
11
|
+
vars: Record<string, unknown>;
|
|
12
|
+
tx: Transaction<any>;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
instanceId: string;
|
|
15
|
+
}>;
|
|
16
|
+
cancel(input: {
|
|
17
|
+
process: string;
|
|
18
|
+
businessKey: string;
|
|
19
|
+
tenantId: string;
|
|
20
|
+
tx: Transaction<any>;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export declare function reactionConsumers(k: Kernel): EventConsumer[];
|
|
24
|
+
/** 审批启动:只把 routable 字段作为流程变量传给引擎 */
|
|
25
|
+
export declare function approvalConsumers(k: Kernel, engines: Record<string, ApprovalEngine>): EventConsumer[];
|
|
26
|
+
/** 声明对象通过 CONSUMERS 扩展点提供的消费者(例如 @yss123/cage-connectors 的出站映射) */
|
|
27
|
+
export declare function extensionConsumers(k: Kernel): EventConsumer[];
|
|
28
|
+
/** 从 ROUTE 扩展(代码内封闭路由)里找审批引擎 */
|
|
29
|
+
export declare function routeEngines(k: Kernel): Record<string, ApprovalEngine>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CONSUMERS } from '../brands.js';
|
|
2
|
+
import { systemCtx } from '../org.js';
|
|
3
|
+
import { loadScoped } from '../pipeline/load.js';
|
|
4
|
+
function actorUser(row) {
|
|
5
|
+
return row.actor?.userId ?? row.actor?.onBehalfOf ?? null;
|
|
6
|
+
}
|
|
7
|
+
export function reactionConsumers(k) {
|
|
8
|
+
return k.registry.reactions
|
|
9
|
+
.filter(({ r }) => r.tx === 'outbox')
|
|
10
|
+
.map(({ r, name }) => ({
|
|
11
|
+
name: `reaction:${name}`,
|
|
12
|
+
event: r.event.name,
|
|
13
|
+
async handle(row, tx) {
|
|
14
|
+
const input = r.map(row.payload);
|
|
15
|
+
if (input === null || input === undefined)
|
|
16
|
+
return;
|
|
17
|
+
const ctx = systemCtx({
|
|
18
|
+
kind: 'reaction',
|
|
19
|
+
name,
|
|
20
|
+
tenantId: row.tenant_id,
|
|
21
|
+
capabilities: [k.registry.capabilityOf(r.command)],
|
|
22
|
+
onBehalfOf: actorUser(row),
|
|
23
|
+
requestId: row.request_id ?? undefined,
|
|
24
|
+
});
|
|
25
|
+
await k.executeIn(tx, r.command, input, ctx, 0);
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
/** 审批启动:只把 routable 字段作为流程变量传给引擎 */
|
|
30
|
+
export function approvalConsumers(k, engines) {
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const { a, name } of k.registry.approvals) {
|
|
33
|
+
const engine = engines[a.process];
|
|
34
|
+
if (!engine) {
|
|
35
|
+
k.log.warn({ process: a.process }, `审批绑定 ${name} 没有可用的审批引擎,流程不会启动`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
out.push({
|
|
39
|
+
name: `approval:${name}:start`,
|
|
40
|
+
event: a.start.name,
|
|
41
|
+
async handle(row, tx) {
|
|
42
|
+
const vars = await routableVars(k, a, row, tx);
|
|
43
|
+
await engine.start({ process: a.process, businessKey: row.aggregate_id, tenantId: row.tenant_id, vars, tx });
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
for (const ev of a.cancelOn) {
|
|
47
|
+
out.push({
|
|
48
|
+
name: `approval:${name}:cancel:${ev.name}`,
|
|
49
|
+
event: ev.name,
|
|
50
|
+
async handle(row, tx) {
|
|
51
|
+
await engine.cancel({ process: a.process, businessKey: row.aggregate_id, tenantId: row.tenant_id, tx });
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
async function routableVars(k, a, row, tx) {
|
|
59
|
+
const ctx = systemCtx({ kind: 'process', name: a.process, tenantId: row.tenant_id, capabilities: [] });
|
|
60
|
+
const snap = await loadScoped(tx, a.agg, row.aggregate_id, ctx, false);
|
|
61
|
+
const vars = {};
|
|
62
|
+
for (const f of a.routable)
|
|
63
|
+
vars[f] = snap?.[f] ?? row.payload?.[f] ?? null;
|
|
64
|
+
return vars;
|
|
65
|
+
}
|
|
66
|
+
/** 声明对象通过 CONSUMERS 扩展点提供的消费者(例如 @yss123/cage-connectors 的出站映射) */
|
|
67
|
+
export function extensionConsumers(k) {
|
|
68
|
+
return k.registry.consumerProviders().flatMap((p) => p[CONSUMERS](k) ?? []);
|
|
69
|
+
}
|
|
70
|
+
/** 从 ROUTE 扩展(代码内封闭路由)里找审批引擎 */
|
|
71
|
+
export function routeEngines(k) {
|
|
72
|
+
const out = {};
|
|
73
|
+
for (const e of k.registry.extensions) {
|
|
74
|
+
if (e.kind === 'ROUTE' && typeof e.value?.engine === 'function')
|
|
75
|
+
out[e.value.process] = e.value.engine(k);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=consumers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumers.js","sourceRoot":"","sources":["../../src/worker/consumers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type DatabaseUrls } from '../db/connect.js';
|
|
2
|
+
import { type Kernel } from '../kernel.js';
|
|
3
|
+
import { type Logger } from '../telemetry.js';
|
|
4
|
+
import { type ApprovalEngine } from './consumers.js';
|
|
5
|
+
import { type EventConsumer } from './outbox.js';
|
|
6
|
+
export * from './outbox.js';
|
|
7
|
+
export * from './consumers.js';
|
|
8
|
+
export * from './scheduler.js';
|
|
9
|
+
export interface WorkerOptions {
|
|
10
|
+
domain?: object | object[];
|
|
11
|
+
kernel?: Kernel;
|
|
12
|
+
urls?: DatabaseUrls;
|
|
13
|
+
/** 同时处理的事件数 */
|
|
14
|
+
concurrency?: number;
|
|
15
|
+
/** 没收到 NOTIFY 时的轮询间隔 */
|
|
16
|
+
pollIntervalMs?: number;
|
|
17
|
+
maxAttempts?: number;
|
|
18
|
+
/** 外部审批引擎(按流程名);不传时使用代码内封闭路由 */
|
|
19
|
+
approvalEngines?: Record<string, ApprovalEngine>;
|
|
20
|
+
/** 额外的事件消费者 */
|
|
21
|
+
consumers?: EventConsumer[];
|
|
22
|
+
/** 是否运行定时任务,默认 true */
|
|
23
|
+
jobs?: boolean;
|
|
24
|
+
logger?: Logger;
|
|
25
|
+
}
|
|
26
|
+
export interface WorkerHandle {
|
|
27
|
+
kernel: Kernel;
|
|
28
|
+
consumers: EventConsumer[];
|
|
29
|
+
/** 处理到 outbox 为空为止(测试和 cage dev 用) */
|
|
30
|
+
drain(maxRounds?: number): Promise<number>;
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare function buildConsumers(k: Kernel, opts?: Pick<WorkerOptions, 'approvalEngines' | 'consumers'>): EventConsumer[];
|
|
34
|
+
/** 不启动循环,只创建可手动驱动的 worker(测试用) */
|
|
35
|
+
export declare function createWorker(k: Kernel, opts?: WorkerOptions): Omit<WorkerHandle, 'stop'>;
|
|
36
|
+
export declare function startWorker(opts?: WorkerOptions): Promise<WorkerHandle>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// worker 进程:投递 outbox、执行反应、启动审批、跑定时任务、调用第三方。与 api 同一份代码和镜像,只是启动命令不同。
|
|
2
|
+
import { sql } from 'kysely';
|
|
3
|
+
import { dedicatedClient, urlsFromEnv } from '../db/connect.js';
|
|
4
|
+
import { createKernel } from '../kernel.js';
|
|
5
|
+
import { consoleLogger } from '../telemetry.js';
|
|
6
|
+
import { approvalConsumers, extensionConsumers, reactionConsumers, routeEngines, } from './consumers.js';
|
|
7
|
+
import { claimBatch, deliver, MAX_ATTEMPTS, sweepStale } from './outbox.js';
|
|
8
|
+
import { nextRun, tick } from './scheduler.js';
|
|
9
|
+
export * from './outbox.js';
|
|
10
|
+
export * from './consumers.js';
|
|
11
|
+
export * from './scheduler.js';
|
|
12
|
+
export function buildConsumers(k, opts = {}) {
|
|
13
|
+
const engines = { ...routeEngines(k), ...(opts.approvalEngines ?? {}) };
|
|
14
|
+
return [...reactionConsumers(k), ...approvalConsumers(k, engines), ...extensionConsumers(k), ...(opts.consumers ?? [])];
|
|
15
|
+
}
|
|
16
|
+
/** 不启动循环,只创建可手动驱动的 worker(测试用) */
|
|
17
|
+
export function createWorker(k, opts = {}) {
|
|
18
|
+
const consumers = buildConsumers(k, opts);
|
|
19
|
+
const max = opts.maxAttempts ?? MAX_ATTEMPTS;
|
|
20
|
+
return {
|
|
21
|
+
kernel: k,
|
|
22
|
+
consumers,
|
|
23
|
+
async drain(maxRounds = 50) {
|
|
24
|
+
let n = 0;
|
|
25
|
+
for (let i = 0; i < maxRounds; i++) {
|
|
26
|
+
const rows = await claimBatch(k, opts.concurrency ?? 8);
|
|
27
|
+
if (rows.length === 0)
|
|
28
|
+
break;
|
|
29
|
+
await Promise.all(rows.map((r) => deliver(k, r, consumers, max)));
|
|
30
|
+
n += rows.length;
|
|
31
|
+
}
|
|
32
|
+
return n;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export async function startWorker(opts = {}) {
|
|
37
|
+
const log = opts.logger ?? consoleLogger;
|
|
38
|
+
const urls = opts.urls ?? (opts.kernel ? undefined : urlsFromEnv());
|
|
39
|
+
const k = opts.kernel ?? createKernel({ domain: opts.domain ?? {}, urls, logger: log });
|
|
40
|
+
const w = createWorker(k, opts);
|
|
41
|
+
const concurrency = opts.concurrency ?? 8;
|
|
42
|
+
const poll = opts.pollIntervalMs ?? 500;
|
|
43
|
+
let stopped = false;
|
|
44
|
+
let wake = null;
|
|
45
|
+
const timers = [];
|
|
46
|
+
// LISTEN:有新事件时立刻醒来,不用等轮询
|
|
47
|
+
const appUrl = urls?.app ?? urlsFromEnv().app;
|
|
48
|
+
const listener = await dedicatedClient(appUrl, 'cage-worker-listen');
|
|
49
|
+
await listener.query('LISTEN cage_outbox');
|
|
50
|
+
listener.on('notification', () => wake?.());
|
|
51
|
+
listener.on('error', (e) => log.error({ err: String(e) }, 'outbox 监听连接出错'));
|
|
52
|
+
const loop = (async () => {
|
|
53
|
+
while (!stopped) {
|
|
54
|
+
try {
|
|
55
|
+
const rows = await claimBatch(k, concurrency);
|
|
56
|
+
if (rows.length > 0) {
|
|
57
|
+
await Promise.all(rows.map((r) => deliver(k, r, w.consumers, opts.maxAttempts ?? MAX_ATTEMPTS)));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
log.error({ err: String(e) }, 'outbox 领取失败');
|
|
63
|
+
}
|
|
64
|
+
await new Promise((resolve) => {
|
|
65
|
+
const t = setTimeout(resolve, poll);
|
|
66
|
+
wake = () => {
|
|
67
|
+
clearTimeout(t);
|
|
68
|
+
resolve();
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
wake = null;
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
// 清扫与日常维护
|
|
75
|
+
timers.push(setInterval(() => void sweepStale(k).catch(() => { }), 60_000));
|
|
76
|
+
const housekeeping = async () => {
|
|
77
|
+
try {
|
|
78
|
+
await sql `select sys.ensure_audit_partitions(3)`.execute(k.app);
|
|
79
|
+
await k.app.deleteFrom('sys.idempotency').where('expires_at', '<', new Date()).execute();
|
|
80
|
+
await k.app.deleteFrom('sys.session').where('expires_at', '<', new Date()).execute();
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
log.warn({ err: String(e) }, '日常维护失败');
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
void housekeeping();
|
|
87
|
+
timers.push(setInterval(() => void housekeeping(), 3600_000));
|
|
88
|
+
// 定时任务
|
|
89
|
+
let lockConn = null;
|
|
90
|
+
if (opts.jobs !== false && k.registry.jobs.length > 0) {
|
|
91
|
+
lockConn = await dedicatedClient(appUrl, 'cage-scheduler');
|
|
92
|
+
const due = new Map(k.registry.jobs.map((j) => [j.name, nextRun(j.cron, new Date())]));
|
|
93
|
+
timers.push(setInterval(() => {
|
|
94
|
+
const now = new Date();
|
|
95
|
+
for (const j of k.registry.jobs) {
|
|
96
|
+
const at = due.get(j.name);
|
|
97
|
+
if (now < at)
|
|
98
|
+
continue;
|
|
99
|
+
due.set(j.name, nextRun(j.cron, now));
|
|
100
|
+
void tick(k, j, lockConn, at).catch((e) => log.error({ job: j.name, err: String(e) }, '调度失败'));
|
|
101
|
+
}
|
|
102
|
+
}, 5_000));
|
|
103
|
+
}
|
|
104
|
+
log.info({ consumers: w.consumers.map((c) => c.name), jobs: k.registry.jobs.map((j) => j.name) }, 'worker 已启动');
|
|
105
|
+
return {
|
|
106
|
+
...w,
|
|
107
|
+
async stop() {
|
|
108
|
+
stopped = true;
|
|
109
|
+
wake?.();
|
|
110
|
+
for (const t of timers)
|
|
111
|
+
clearInterval(t);
|
|
112
|
+
await loop;
|
|
113
|
+
await listener.end().catch(() => { });
|
|
114
|
+
await lockConn?.end().catch(() => { });
|
|
115
|
+
if (!opts.kernel)
|
|
116
|
+
await k.close();
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Transaction } from 'kysely';
|
|
2
|
+
import type { Kernel } from '../kernel.js';
|
|
3
|
+
export interface OutboxRow {
|
|
4
|
+
id: number;
|
|
5
|
+
tenant_id: string;
|
|
6
|
+
event: string;
|
|
7
|
+
aggregate: string;
|
|
8
|
+
aggregate_id: string;
|
|
9
|
+
payload: Record<string, any>;
|
|
10
|
+
actor: {
|
|
11
|
+
kind?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
userId?: string | null;
|
|
14
|
+
onBehalfOf?: string | null;
|
|
15
|
+
} | null;
|
|
16
|
+
trace: Record<string, string> | null;
|
|
17
|
+
request_id: string | null;
|
|
18
|
+
status: string;
|
|
19
|
+
attempts: number;
|
|
20
|
+
}
|
|
21
|
+
/** 事件消费者:反应、出站映射、审批启动都实现这个接口。登记去重和业务写入在同一个事务里提交。 */
|
|
22
|
+
export interface EventConsumer {
|
|
23
|
+
/** 唯一名,用于 (事件 id, 消费者) 去重 */
|
|
24
|
+
name: string;
|
|
25
|
+
event: string;
|
|
26
|
+
handle(row: OutboxRow, tx: Transaction<any>, kernel: Kernel): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare const MAX_ATTEMPTS = 10;
|
|
29
|
+
/** 指数退避:1 秒、2 秒、4 秒……最多 1 小时 */
|
|
30
|
+
export declare function backoffMs(attempts: number): number;
|
|
31
|
+
export declare function claimBatch(k: Kernel, limit: number): Promise<OutboxRow[]>;
|
|
32
|
+
export declare function deliver(k: Kernel, row: OutboxRow, consumers: EventConsumer[], maxAttempts?: number): Promise<'done' | 'retry' | 'dead'>;
|
|
33
|
+
/** 清扫:worker 崩溃后卡在「处理中」超过 5 分钟的事件重置为待投递 */
|
|
34
|
+
export declare function sweepStale(k: Kernel, olderThanMs?: number): Promise<number>;
|
|
35
|
+
/** 人工重放死信:仍然走内核流水线 */
|
|
36
|
+
export declare function replayDeadLetter(k: Kernel, deadLetterId: number, handledBy: string): Promise<void>;
|
|
37
|
+
/** 人工丢弃死信:标记完成,解除对同一聚合后续事件的阻塞 */
|
|
38
|
+
export declare function discardDeadLetter(k: Kernel, deadLetterId: number, handledBy: string): Promise<void>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// outbox 投递器:多个 worker 实例同时领取,靠 FOR UPDATE SKIP LOCKED 避免重复领取;同一聚合的事件严格按顺序处理。
|
|
2
|
+
import { sql } from 'kysely';
|
|
3
|
+
import { isCageError } from '../errors.js';
|
|
4
|
+
import { withCarrier } from '../telemetry.js';
|
|
5
|
+
export const MAX_ATTEMPTS = 10;
|
|
6
|
+
/** 指数退避:1 秒、2 秒、4 秒……最多 1 小时 */
|
|
7
|
+
export function backoffMs(attempts) {
|
|
8
|
+
return Math.min(3600_000, 1000 * 2 ** Math.max(0, attempts - 1));
|
|
9
|
+
}
|
|
10
|
+
export async function claimBatch(k, limit) {
|
|
11
|
+
return k.app.transaction().execute(async (tx) => {
|
|
12
|
+
const { rows } = await sql `
|
|
13
|
+
select o.* from sys.outbox o
|
|
14
|
+
where o.status = 'pending'
|
|
15
|
+
and o.next_at <= now()
|
|
16
|
+
and not exists (
|
|
17
|
+
select 1 from sys.outbox p
|
|
18
|
+
where p.aggregate_id = o.aggregate_id
|
|
19
|
+
and p.id < o.id and p.status <> 'done')
|
|
20
|
+
order by o.id
|
|
21
|
+
limit ${limit}
|
|
22
|
+
for update skip locked`.execute(tx);
|
|
23
|
+
if (rows.length > 0) {
|
|
24
|
+
await tx
|
|
25
|
+
.updateTable('sys.outbox')
|
|
26
|
+
.set({ status: 'processing', locked_at: sql `now()` })
|
|
27
|
+
.where('id', 'in', rows.map((r) => r.id))
|
|
28
|
+
.execute();
|
|
29
|
+
}
|
|
30
|
+
return rows.map((r) => ({ ...r, id: Number(r.id) }));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** 业务错误、状态不对、越权这类错误重试也不会成功,直接进死信 */
|
|
34
|
+
function isPermanent(err) {
|
|
35
|
+
const e = err;
|
|
36
|
+
if (e?.permanent === true)
|
|
37
|
+
return true;
|
|
38
|
+
if (e?.retryable === true)
|
|
39
|
+
return false;
|
|
40
|
+
if (isCageError(err))
|
|
41
|
+
return !['CONFLICT', 'INTERNAL', 'RATE_LIMITED'].includes(e.code);
|
|
42
|
+
// 带错误码的框架错误(声明不一致、载荷不符合声明等)重试也不会好
|
|
43
|
+
return /\[CAGE-/.test(String(e?.message ?? ''));
|
|
44
|
+
}
|
|
45
|
+
export async function deliver(k, row, consumers, maxAttempts = MAX_ATTEMPTS) {
|
|
46
|
+
const mine = consumers.filter((c) => c.event === row.event);
|
|
47
|
+
try {
|
|
48
|
+
await withCarrier(row.trace, async () => {
|
|
49
|
+
for (const c of mine) {
|
|
50
|
+
await k.app.transaction().execute(async (tx) => {
|
|
51
|
+
const first = await tx
|
|
52
|
+
.insertInto('sys.processed_event')
|
|
53
|
+
.values({ event_id: row.id, consumer: c.name })
|
|
54
|
+
.onConflict((oc) => oc.columns(['event_id', 'consumer']).doNothing())
|
|
55
|
+
.returning('event_id')
|
|
56
|
+
.executeTakeFirst();
|
|
57
|
+
if (!first)
|
|
58
|
+
return; // 处理过了:直接跳过,保证幂等
|
|
59
|
+
await c.handle(row, tx, k);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
await k.app.updateTable('sys.outbox').set({ status: 'done', done_at: sql `now()`, locked_at: null }).where('id', '=', row.id).execute();
|
|
64
|
+
return 'done';
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const attempts = row.attempts + 1;
|
|
68
|
+
const message = isCageError(err) ? JSON.stringify(err.toJSON()) : String(err?.message ?? err);
|
|
69
|
+
if (attempts >= maxAttempts || isPermanent(err)) {
|
|
70
|
+
await k.app.transaction().execute(async (tx) => {
|
|
71
|
+
await tx
|
|
72
|
+
.insertInto('sys.dead_letter')
|
|
73
|
+
.values({ outbox_id: row.id, tenant_id: row.tenant_id, event: row.event, aggregate_id: row.aggregate_id, payload: JSON.stringify(row.payload), error: message, attempts })
|
|
74
|
+
.execute();
|
|
75
|
+
await tx.updateTable('sys.outbox').set({ status: 'dead', attempts, last_error: message, locked_at: null }).where('id', '=', row.id).execute();
|
|
76
|
+
});
|
|
77
|
+
k.log.error({ outboxId: row.id, event: row.event, err: message }, '事件进入死信');
|
|
78
|
+
return 'dead';
|
|
79
|
+
}
|
|
80
|
+
await k.app
|
|
81
|
+
.updateTable('sys.outbox')
|
|
82
|
+
.set({ status: 'pending', attempts, last_error: message, locked_at: null, next_at: new Date(Date.now() + backoffMs(attempts)) })
|
|
83
|
+
.where('id', '=', row.id)
|
|
84
|
+
.execute();
|
|
85
|
+
k.log.warn({ outboxId: row.id, event: row.event, attempts, err: message }, '事件投递失败,稍后重试');
|
|
86
|
+
return 'retry';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** 清扫:worker 崩溃后卡在「处理中」超过 5 分钟的事件重置为待投递 */
|
|
90
|
+
export async function sweepStale(k, olderThanMs = 5 * 60_000) {
|
|
91
|
+
const r = await k.app
|
|
92
|
+
.updateTable('sys.outbox')
|
|
93
|
+
.set({ status: 'pending', locked_at: null })
|
|
94
|
+
.where('status', '=', 'processing')
|
|
95
|
+
.where('locked_at', '<', new Date(Date.now() - olderThanMs))
|
|
96
|
+
.executeTakeFirst();
|
|
97
|
+
return Number(r.numUpdatedRows);
|
|
98
|
+
}
|
|
99
|
+
/** 人工重放死信:仍然走内核流水线 */
|
|
100
|
+
export async function replayDeadLetter(k, deadLetterId, handledBy) {
|
|
101
|
+
await k.app.transaction().execute(async (tx) => {
|
|
102
|
+
const d = await tx.selectFrom('sys.dead_letter').selectAll().where('id', '=', deadLetterId).where('handled_at', 'is', null).forUpdate().executeTakeFirst();
|
|
103
|
+
if (!d)
|
|
104
|
+
throw new Error('[CAGE-DLQ-001] 死信不存在或已处理');
|
|
105
|
+
await tx.updateTable('sys.outbox').set({ status: 'pending', attempts: 0, next_at: sql `now()`, last_error: null }).where('id', '=', d.outbox_id).execute();
|
|
106
|
+
await tx.updateTable('sys.dead_letter').set({ handled_by: handledBy, handled_at: sql `now()`, resolution: 'replayed' }).where('id', '=', deadLetterId).execute();
|
|
107
|
+
await sql `select pg_notify('cage_outbox', '')`.execute(tx);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** 人工丢弃死信:标记完成,解除对同一聚合后续事件的阻塞 */
|
|
111
|
+
export async function discardDeadLetter(k, deadLetterId, handledBy) {
|
|
112
|
+
await k.app.transaction().execute(async (tx) => {
|
|
113
|
+
const d = await tx.selectFrom('sys.dead_letter').selectAll().where('id', '=', deadLetterId).where('handled_at', 'is', null).forUpdate().executeTakeFirst();
|
|
114
|
+
if (!d)
|
|
115
|
+
throw new Error('[CAGE-DLQ-001] 死信不存在或已处理');
|
|
116
|
+
await tx.updateTable('sys.outbox').set({ status: 'done', done_at: sql `now()` }).where('id', '=', d.outbox_id).execute();
|
|
117
|
+
await tx.updateTable('sys.dead_letter').set({ handled_by: handledBy, handled_at: sql `now()`, resolution: 'discarded' }).where('id', '=', deadLetterId).execute();
|
|
118
|
+
await sql `select pg_notify('cage_outbox', '')`.execute(tx);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=outbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../src/worker/outbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type pg from 'pg';
|
|
2
|
+
import type { Kernel } from '../kernel.js';
|
|
3
|
+
import type { Job, JobApi } from '../primitives/flow.js';
|
|
4
|
+
export declare function nextRun(cron: string, after: Date): Date;
|
|
5
|
+
export declare function jobApi(k: Kernel, j: Job, tenantId: string, now: Date): JobApi;
|
|
6
|
+
/** 执行一次任务(拿到锁才执行);返回是否执行了 */
|
|
7
|
+
export declare function tick(k: Kernel, j: Job, lockConn: pg.Client, scheduledAt: Date, force?: boolean): Promise<boolean>;
|