@tachybase/module-event-source 1.6.15 → 1.6.17

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.
@@ -18,13 +18,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
18
18
  var constants_exports = {};
19
19
  __export(constants_exports, {
20
20
  EVENT_SOURCE_COLLECTION: () => EVENT_SOURCE_COLLECTION,
21
+ EVENT_SOURCE_QUEUE_COLLECTION: () => EVENT_SOURCE_QUEUE_COLLECTION,
22
+ EVENT_SOURCE_QUEUE_STATUS: () => EVENT_SOURCE_QUEUE_STATUS,
21
23
  EVENT_SOURCE_REALTIME: () => EVENT_SOURCE_REALTIME
22
24
  });
23
25
  module.exports = __toCommonJS(constants_exports);
24
26
  const EVENT_SOURCE_COLLECTION = "webhooks";
25
27
  const EVENT_SOURCE_REALTIME = process.env.EVENT_SOURCE_REALTIME === "0" ? false : true;
28
+ const EVENT_SOURCE_QUEUE_COLLECTION = "eventSourceQueueJobs";
29
+ const EVENT_SOURCE_QUEUE_STATUS = {
30
+ PENDING: "pending",
31
+ PROCESSING: "processing",
32
+ SUCCESS: "success",
33
+ FAILED: "failed",
34
+ DEAD: "dead"
35
+ };
26
36
  // Annotate the CommonJS export names for ESM import in node:
27
37
  0 && (module.exports = {
28
38
  EVENT_SOURCE_COLLECTION,
39
+ EVENT_SOURCE_QUEUE_COLLECTION,
40
+ EVENT_SOURCE_QUEUE_STATUS,
29
41
  EVENT_SOURCE_REALTIME
30
42
  });
@@ -11,6 +11,12 @@ export declare class EventSourceModel extends Model {
11
11
  actionName?: string;
12
12
  eventName?: string;
13
13
  triggerOnAssociation?: boolean;
14
+ useHttpContext?: boolean;
15
+ failurePolicy?: 'ignore' | 'block';
16
+ timeoutMs?: number;
17
+ executionMode?: 'inline' | 'queue';
18
+ maxAttempts?: number;
19
+ retryBackoffMs?: number;
14
20
  sort?: number;
15
21
  };
16
22
  }
@@ -0,0 +1,31 @@
1
+ import { Application } from '@tego/server';
2
+ type QueueStage = 'beforeResource' | 'afterResource' | 'customAction';
3
+ type QueueJobInput = {
4
+ sourceId: number;
5
+ stage: QueueStage;
6
+ resourceName?: string;
7
+ actionName?: string;
8
+ workflowKey?: string;
9
+ payload: any;
10
+ contextLite?: {
11
+ userId?: number;
12
+ roleName?: string;
13
+ };
14
+ maxAttempts?: number;
15
+ retryBackoffMs?: number;
16
+ };
17
+ export declare class EventSourceQueueWorker {
18
+ private app;
19
+ private timer;
20
+ private running;
21
+ private readonly pollMs;
22
+ private readonly batchSize;
23
+ constructor(app: Application);
24
+ enqueue(input: QueueJobInput): Promise<any>;
25
+ start(): void;
26
+ stop(): void;
27
+ private tick;
28
+ private processJob;
29
+ private createPseudoContext;
30
+ }
31
+ export {};
@@ -0,0 +1,168 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var EventSourceQueueWorker_exports = {};
19
+ __export(EventSourceQueueWorker_exports, {
20
+ EventSourceQueueWorker: () => EventSourceQueueWorker
21
+ });
22
+ module.exports = __toCommonJS(EventSourceQueueWorker_exports);
23
+ var import_constants = require("../constants");
24
+ var import_webhooks = require("../webhooks/webhooks");
25
+ class EventSourceQueueWorker {
26
+ constructor(app) {
27
+ this.app = app;
28
+ this.timer = null;
29
+ this.running = false;
30
+ this.pollMs = Number(process.env.EVENT_SOURCE_QUEUE_POLL_MS || 1e3);
31
+ this.batchSize = Number(process.env.EVENT_SOURCE_QUEUE_BATCH_SIZE || 20);
32
+ }
33
+ async enqueue(input) {
34
+ const repo = this.app.db.getRepository(import_constants.EVENT_SOURCE_QUEUE_COLLECTION);
35
+ return repo.create({
36
+ values: {
37
+ sourceId: input.sourceId,
38
+ stage: input.stage,
39
+ resourceName: input.resourceName,
40
+ actionName: input.actionName,
41
+ workflowKey: input.workflowKey,
42
+ payload: input.payload,
43
+ contextLite: input.contextLite || {},
44
+ status: import_constants.EVENT_SOURCE_QUEUE_STATUS.PENDING,
45
+ attempt: 0,
46
+ maxAttempts: input.maxAttempts ?? 3,
47
+ retryBackoffMs: input.retryBackoffMs ?? 3e3,
48
+ nextRunAt: /* @__PURE__ */ new Date()
49
+ }
50
+ });
51
+ }
52
+ start() {
53
+ if (this.timer) {
54
+ return;
55
+ }
56
+ this.timer = setInterval(() => {
57
+ this.tick().catch((error) => {
58
+ this.app.logger.error("[event-source-queue] tick failed", error);
59
+ });
60
+ }, this.pollMs);
61
+ }
62
+ stop() {
63
+ if (!this.timer) {
64
+ return;
65
+ }
66
+ clearInterval(this.timer);
67
+ this.timer = null;
68
+ }
69
+ async tick() {
70
+ if (this.running) {
71
+ return;
72
+ }
73
+ this.running = true;
74
+ try {
75
+ const repo = this.app.db.getRepository(import_constants.EVENT_SOURCE_QUEUE_COLLECTION);
76
+ const jobs = await repo.find({
77
+ filter: {
78
+ status: {
79
+ $in: [import_constants.EVENT_SOURCE_QUEUE_STATUS.PENDING, import_constants.EVENT_SOURCE_QUEUE_STATUS.FAILED]
80
+ },
81
+ nextRunAt: {
82
+ $lte: /* @__PURE__ */ new Date()
83
+ }
84
+ },
85
+ sort: ["createdAt"],
86
+ limit: this.batchSize
87
+ });
88
+ for (const job of jobs) {
89
+ await this.processJob(job);
90
+ }
91
+ } finally {
92
+ this.running = false;
93
+ }
94
+ }
95
+ async processJob(job) {
96
+ const repo = this.app.db.getRepository(import_constants.EVENT_SOURCE_QUEUE_COLLECTION);
97
+ const attempt = (job.attempt || 0) + 1;
98
+ try {
99
+ await repo.update({
100
+ filter: { id: job.id },
101
+ values: {
102
+ status: import_constants.EVENT_SOURCE_QUEUE_STATUS.PROCESSING,
103
+ lockedAt: /* @__PURE__ */ new Date(),
104
+ lockedBy: this.app.name,
105
+ attempt
106
+ }
107
+ });
108
+ const pseudoCtx = await this.createPseudoContext(job.contextLite);
109
+ const controller = new import_webhooks.WebhookController();
110
+ await controller.triggerWorkflow(
111
+ pseudoCtx,
112
+ {
113
+ workflowKey: job.workflowKey,
114
+ options: {
115
+ useHttpContext: false
116
+ }
117
+ },
118
+ job.payload
119
+ );
120
+ await repo.update({
121
+ filter: { id: job.id },
122
+ values: {
123
+ status: import_constants.EVENT_SOURCE_QUEUE_STATUS.SUCCESS,
124
+ lastError: null
125
+ }
126
+ });
127
+ } catch (error) {
128
+ const maxAttempts = Number(job.maxAttempts || 3);
129
+ const retryBackoffMs = Number(job.retryBackoffMs || 3e3);
130
+ const dead = attempt >= maxAttempts;
131
+ await repo.update({
132
+ filter: { id: job.id },
133
+ values: {
134
+ status: dead ? import_constants.EVENT_SOURCE_QUEUE_STATUS.DEAD : import_constants.EVENT_SOURCE_QUEUE_STATUS.FAILED,
135
+ nextRunAt: dead ? null : new Date(Date.now() + retryBackoffMs),
136
+ lastError: (error == null ? void 0 : error.stack) || `${error}`
137
+ }
138
+ });
139
+ this.app.logger.error(
140
+ `[event-source-queue] process failed, jobId=${job.id}, sourceId=${job.sourceId}, attempt=${attempt}, dead=${dead}`,
141
+ error
142
+ );
143
+ }
144
+ }
145
+ async createPseudoContext(contextLite) {
146
+ var _a;
147
+ let currentUser = null;
148
+ if (contextLite == null ? void 0 : contextLite.userId) {
149
+ const userRepo = this.app.db.getRepository("users");
150
+ currentUser = await userRepo.findOne({
151
+ filter: { id: contextLite.userId }
152
+ });
153
+ currentUser = ((_a = currentUser == null ? void 0 : currentUser.toJSON) == null ? void 0 : _a.call(currentUser)) || currentUser;
154
+ }
155
+ return {
156
+ db: this.app.db,
157
+ tego: this.app,
158
+ state: {
159
+ currentUser,
160
+ currentRole: (contextLite == null ? void 0 : contextLite.roleName) || null
161
+ }
162
+ };
163
+ }
164
+ }
165
+ // Annotate the CommonJS export names for ESM import in node:
166
+ 0 && (module.exports = {
167
+ EventSourceQueueWorker
168
+ });
@@ -3,9 +3,17 @@ import { EventSourceTrigger } from './Trigger';
3
3
  type IAPITriggerConfig = {
4
4
  code: string;
5
5
  workflowKey: string;
6
+ options?: EventSourceModel['options'];
6
7
  };
7
8
  export declare class CustomActionTrigger extends EventSourceTrigger {
8
9
  eventMap: Map<number, IAPITriggerConfig>;
10
+ /** 未设置 failurePolicy 时与 main 一致:工作流失败则 500 */
11
+ private getFailurePolicy;
12
+ private getExecutionMode;
13
+ private getMaxAttempts;
14
+ private getRetryBackoffMs;
15
+ private getTimeoutMs;
16
+ private withTimeout;
9
17
  load(model: EventSourceModel): void;
10
18
  afterCreate(model: EventSourceModel): void;
11
19
  afterUpdate(model: EventSourceModel): void;
@@ -27,6 +27,49 @@ class CustomActionTrigger extends import_Trigger.EventSourceTrigger {
27
27
  super(...arguments);
28
28
  this.eventMap = /* @__PURE__ */ new Map();
29
29
  }
30
+ /** 未设置 failurePolicy 时与 main 一致:工作流失败则 500 */
31
+ getFailurePolicy(config) {
32
+ var _a;
33
+ return ((_a = config == null ? void 0 : config.options) == null ? void 0 : _a.failurePolicy) === "ignore" ? "ignore" : "block";
34
+ }
35
+ getExecutionMode(config) {
36
+ var _a;
37
+ return ((_a = config == null ? void 0 : config.options) == null ? void 0 : _a.executionMode) === "queue" ? "queue" : "inline";
38
+ }
39
+ getMaxAttempts(config) {
40
+ var _a;
41
+ const value = Number(((_a = config == null ? void 0 : config.options) == null ? void 0 : _a.maxAttempts) || 3);
42
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 3;
43
+ }
44
+ getRetryBackoffMs(config) {
45
+ var _a;
46
+ const value = Number(((_a = config == null ? void 0 : config.options) == null ? void 0 : _a.retryBackoffMs) || 3e3);
47
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 3e3;
48
+ }
49
+ getTimeoutMs(config) {
50
+ var _a;
51
+ const timeoutMs = Number(((_a = config == null ? void 0 : config.options) == null ? void 0 : _a.timeoutMs) || 0);
52
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
53
+ return 0;
54
+ }
55
+ return timeoutMs;
56
+ }
57
+ async withTimeout(executor, timeoutMs, errorMessage) {
58
+ if (!timeoutMs) {
59
+ return executor();
60
+ }
61
+ let timer;
62
+ const timeoutPromise = new Promise((_, reject) => {
63
+ timer = setTimeout(() => reject(new Error(errorMessage)), timeoutMs);
64
+ });
65
+ try {
66
+ return await Promise.race([executor(), timeoutPromise]);
67
+ } finally {
68
+ if (timer) {
69
+ clearTimeout(timer);
70
+ }
71
+ }
72
+ }
30
73
  load(model) {
31
74
  if (!model.options) {
32
75
  return;
@@ -38,7 +81,7 @@ class CustomActionTrigger extends import_Trigger.EventSourceTrigger {
38
81
  code,
39
82
  workflowKey
40
83
  } = model;
41
- this.eventMap.set(id, { code, workflowKey });
84
+ this.eventMap.set(id, { code, workflowKey, options: model.options });
42
85
  if (!app.resourcer.isDefined(resourceName)) {
43
86
  app.resourcer.define({ name: resourceName });
44
87
  }
@@ -48,15 +91,62 @@ class CustomActionTrigger extends import_Trigger.EventSourceTrigger {
48
91
  }
49
92
  app.logger.info(`Add ${resourceName}:${actionName} action handler`);
50
93
  app.resourcer.getResource(resourceName).addAction(actionName, async (ctx) => {
94
+ var _a, _b, _c, _d, _e;
51
95
  if (this.realTimeRefresh && !this.workSet.has(id)) {
52
96
  ctx.throw(404, "Not found");
53
97
  }
54
- const { code: code2, workflowKey: workflowKey2 } = this.eventMap.get(id);
55
- const body = await new import_webhooks.WebhookController().action(ctx, { code: code2 });
56
- const res = await new import_webhooks.WebhookController().triggerWorkflow(ctx, { workflowKey: workflowKey2 }, body);
57
- const lastSavedJob = res.lastSavedJob;
58
- if (lastSavedJob.get("status") < 0) {
59
- ctx.throw(500, lastSavedJob.get("result"));
98
+ const config = this.eventMap.get(id);
99
+ if (!config) {
100
+ ctx.throw(404, "Not found");
101
+ return;
102
+ }
103
+ const failurePolicy = this.getFailurePolicy(config);
104
+ const executionMode = this.getExecutionMode(config);
105
+ const timeoutMs = this.getTimeoutMs(config);
106
+ try {
107
+ if (executionMode === "queue") {
108
+ const body = await new import_webhooks.WebhookController().action(ctx, { code: config.code });
109
+ const queueWorker = this.app.eventSourceQueueWorker;
110
+ if (!queueWorker) {
111
+ throw new Error("eventSourceQueueWorker is not available");
112
+ }
113
+ await queueWorker.enqueue({
114
+ sourceId: id,
115
+ stage: "customAction",
116
+ resourceName: (_a = ctx == null ? void 0 : ctx.action) == null ? void 0 : _a.resourceName,
117
+ actionName: (_b = ctx == null ? void 0 : ctx.action) == null ? void 0 : _b.actionName,
118
+ workflowKey: config.workflowKey,
119
+ payload: body,
120
+ contextLite: {
121
+ userId: (_d = (_c = ctx == null ? void 0 : ctx.state) == null ? void 0 : _c.currentUser) == null ? void 0 : _d.id,
122
+ roleName: (_e = ctx == null ? void 0 : ctx.state) == null ? void 0 : _e.currentRole
123
+ },
124
+ maxAttempts: this.getMaxAttempts(config),
125
+ retryBackoffMs: this.getRetryBackoffMs(config)
126
+ });
127
+ ctx.withoutDataWrapping = true;
128
+ ctx.body = { queued: true };
129
+ return;
130
+ }
131
+ const res = await this.withTimeout(
132
+ async () => {
133
+ const body = await new import_webhooks.WebhookController().action(ctx, { code: config.code });
134
+ return await new import_webhooks.WebhookController().triggerWorkflow(ctx, config, body);
135
+ },
136
+ timeoutMs,
137
+ `[event-source] custom action timeout, sourceId=${id}, timeoutMs=${timeoutMs}`
138
+ );
139
+ const lastSavedJob = res == null ? void 0 : res.lastSavedJob;
140
+ if ((lastSavedJob == null ? void 0 : lastSavedJob.get("status")) < 0) {
141
+ throw new Error(`${lastSavedJob.get("result")}`);
142
+ }
143
+ } catch (error) {
144
+ app.logger.error(
145
+ `[event-source] custom action execution failed. policy=${failurePolicy}, sourceId=${id}, workflowKey=${config == null ? void 0 : config.workflowKey}, error=${(error == null ? void 0 : error.stack) || error}`
146
+ );
147
+ if (failurePolicy === "block") {
148
+ ctx.throw(500, (error == null ? void 0 : error.message) || "Custom action execution failed");
149
+ }
60
150
  }
61
151
  });
62
152
  app.acl.allow(resourceName, actionName, "loggedIn");
@@ -66,11 +156,11 @@ class CustomActionTrigger extends import_Trigger.EventSourceTrigger {
66
156
  }
67
157
  // TODO 很难修改和删除,目前实时刷新的时候间接修改
68
158
  afterUpdate(model) {
69
- const { enabled, code, workflowKey, id } = model;
159
+ const { enabled, code, workflowKey, id, options } = model;
70
160
  if (enabled && !this.workSet.has(id)) {
71
161
  this.load(model);
72
162
  } else {
73
- this.eventMap.set(id, { code, workflowKey });
163
+ this.eventMap.set(id, { code, workflowKey, options });
74
164
  }
75
165
  }
76
166
  }
@@ -5,6 +5,20 @@ export declare class ResourceEventTrigger extends EventSourceTrigger {
5
5
  private afterList;
6
6
  load(model: EventSourceModel): void;
7
7
  afterAllLoad(): void;
8
+ /**
9
+ * failurePolicy 未设置:与 main 分支一致(遗留行为)
10
+ * - beforeResource:工作流 ERROR 时阻断(原 400)
11
+ * - afterResource:不因工作流 ERROR 状态阻断(原逻辑未校验)
12
+ * 显式 ignore:失败不阻断主请求
13
+ * 显式 block:任意失败均阻断
14
+ */
15
+ private getFailurePolicy;
16
+ private getExecutionMode;
17
+ private getMaxAttempts;
18
+ private getRetryBackoffMs;
19
+ private getTimeoutMs;
20
+ private withTimeout;
21
+ private executeByPolicy;
8
22
  private getMatchList;
9
23
  afterCreate(model: EventSourceModel): void;
10
24
  afterUpdate(model: EventSourceModel): void;
@@ -55,21 +55,133 @@ class ResourceEventTrigger extends import_Trigger.EventSourceTrigger {
55
55
  return next();
56
56
  }
57
57
  for (const model of matchBefore) {
58
- const body = await new import_webhooks.WebhookController().action(ctx, model);
59
- const result = await new import_webhooks.WebhookController().triggerWorkflow(ctx, model, body);
60
- if (result && result.lastSavedJob.status === import_module_workflow.JOB_STATUS.ERROR) {
61
- ctx.throw(400, result.lastSavedJob.result);
62
- }
58
+ await this.executeByPolicy(ctx, model, "beforeResource");
63
59
  }
64
60
  await next();
65
61
  for (const model of matchAfter) {
66
- const body = await new import_webhooks.WebhookController().action(ctx, model);
67
- await new import_webhooks.WebhookController().triggerWorkflow(ctx, model, body);
62
+ await this.executeByPolicy(ctx, model, "afterResource");
68
63
  }
69
64
  },
70
65
  { tag: "event-source-resource" }
71
66
  );
72
67
  }
68
+ /**
69
+ * failurePolicy 未设置:与 main 分支一致(遗留行为)
70
+ * - beforeResource:工作流 ERROR 时阻断(原 400)
71
+ * - afterResource:不因工作流 ERROR 状态阻断(原逻辑未校验)
72
+ * 显式 ignore:失败不阻断主请求
73
+ * 显式 block:任意失败均阻断
74
+ */
75
+ getFailurePolicy(model) {
76
+ var _a;
77
+ const fp = (_a = model == null ? void 0 : model.options) == null ? void 0 : _a.failurePolicy;
78
+ if (fp === "ignore") {
79
+ return "ignore";
80
+ }
81
+ if (fp === "block") {
82
+ return "block";
83
+ }
84
+ return "legacy";
85
+ }
86
+ getExecutionMode(model) {
87
+ var _a;
88
+ return ((_a = model == null ? void 0 : model.options) == null ? void 0 : _a.executionMode) === "queue" ? "queue" : "inline";
89
+ }
90
+ getMaxAttempts(model) {
91
+ var _a;
92
+ const value = Number(((_a = model == null ? void 0 : model.options) == null ? void 0 : _a.maxAttempts) || 3);
93
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 3;
94
+ }
95
+ getRetryBackoffMs(model) {
96
+ var _a;
97
+ const value = Number(((_a = model == null ? void 0 : model.options) == null ? void 0 : _a.retryBackoffMs) || 3e3);
98
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 3e3;
99
+ }
100
+ getTimeoutMs(model) {
101
+ var _a;
102
+ const timeoutMs = Number(((_a = model == null ? void 0 : model.options) == null ? void 0 : _a.timeoutMs) || 0);
103
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
104
+ return 0;
105
+ }
106
+ return timeoutMs;
107
+ }
108
+ async withTimeout(executor, timeoutMs, errorMessage) {
109
+ if (!timeoutMs) {
110
+ return executor();
111
+ }
112
+ let timer;
113
+ const timeoutPromise = new Promise((_, reject) => {
114
+ timer = setTimeout(() => reject(new Error(errorMessage)), timeoutMs);
115
+ });
116
+ try {
117
+ return await Promise.race([executor(), timeoutPromise]);
118
+ } finally {
119
+ if (timer) {
120
+ clearTimeout(timer);
121
+ }
122
+ }
123
+ }
124
+ async executeByPolicy(ctx, model, stage) {
125
+ var _a, _b, _c, _d, _e;
126
+ const failurePolicy = this.getFailurePolicy(model);
127
+ const executionMode = this.getExecutionMode(model);
128
+ const timeoutMs = this.getTimeoutMs(model);
129
+ try {
130
+ if (executionMode === "queue") {
131
+ const body = await new import_webhooks.WebhookController().action(ctx, model);
132
+ const queueWorker = this.app.eventSourceQueueWorker;
133
+ if (!queueWorker) {
134
+ throw new Error("eventSourceQueueWorker is not available");
135
+ }
136
+ await queueWorker.enqueue({
137
+ sourceId: model.id,
138
+ stage,
139
+ resourceName: (_a = model == null ? void 0 : model.options) == null ? void 0 : _a.resourceName,
140
+ actionName: (_b = model == null ? void 0 : model.options) == null ? void 0 : _b.actionName,
141
+ workflowKey: model.workflowKey,
142
+ payload: body,
143
+ contextLite: {
144
+ userId: (_d = (_c = ctx == null ? void 0 : ctx.state) == null ? void 0 : _c.currentUser) == null ? void 0 : _d.id,
145
+ roleName: (_e = ctx == null ? void 0 : ctx.state) == null ? void 0 : _e.currentRole
146
+ },
147
+ maxAttempts: this.getMaxAttempts(model),
148
+ retryBackoffMs: this.getRetryBackoffMs(model)
149
+ });
150
+ return;
151
+ }
152
+ const result = await this.withTimeout(
153
+ async () => {
154
+ const body = await new import_webhooks.WebhookController().action(ctx, model);
155
+ return await new import_webhooks.WebhookController().triggerWorkflow(ctx, model, body);
156
+ },
157
+ timeoutMs,
158
+ `[event-source] ${stage} timeout, sourceId=${model.id}, timeoutMs=${timeoutMs}`
159
+ );
160
+ if (result && result.lastSavedJob.status === import_module_workflow.JOB_STATUS.ERROR) {
161
+ if (failurePolicy === "ignore") {
162
+ return;
163
+ }
164
+ if (failurePolicy === "block") {
165
+ throw new Error(`${result.lastSavedJob.result}`);
166
+ }
167
+ if (stage === "beforeResource") {
168
+ ctx.throw(400, result.lastSavedJob.result);
169
+ }
170
+ return;
171
+ }
172
+ } catch (error) {
173
+ this.app.logger.error(
174
+ `[event-source] ${stage} execution failed. policy=${failurePolicy}, sourceId=${model.id}, workflowKey=${model.workflowKey}, error=${(error == null ? void 0 : error.stack) || error}`
175
+ );
176
+ if (failurePolicy === "ignore") {
177
+ return;
178
+ }
179
+ if (failurePolicy === "block") {
180
+ throw error;
181
+ }
182
+ throw error;
183
+ }
184
+ }
73
185
  getMatchList(list, resourceName, actionName) {
74
186
  const matchList = [];
75
187
  list.sort((a, b) => {
@@ -1,8 +1,10 @@
1
1
  import { Plugin, Registry, WSServer } from '@tego/server';
2
+ import { EventSourceQueueWorker } from '../queue/EventSourceQueueWorker';
2
3
  import { EventSourceTrigger } from '../trigger/Trigger';
3
4
  export declare class PluginWebhook extends Plugin {
4
5
  triggers: Registry<EventSourceTrigger>;
5
6
  ws: WSServer;
7
+ queueWorker: EventSourceQueueWorker;
6
8
  refreshRealTime: boolean;
7
9
  changed: boolean;
8
10
  beforeLoad(): Promise<void>;
@@ -24,6 +24,7 @@ var import_server = require("@tego/server");
24
24
  var import_constants = require("../constants");
25
25
  var import_EventSourceModel = require("../model/EventSourceModel");
26
26
  var import_WebhookCategories = require("../model/WebhookCategories");
27
+ var import_EventSourceQueueWorker = require("../queue/EventSourceQueueWorker");
27
28
  var import_AppEventTrigger = require("../trigger/AppEventTrigger");
28
29
  var import_CustomctionTrigger = require("../trigger/CustomctionTrigger");
29
30
  var import_DatabaseEventTrigger = require("../trigger/DatabaseEventTrigger");
@@ -80,6 +81,13 @@ class PluginWebhook extends import_server.Plugin {
80
81
  },
81
82
  { tag: "webhooks-show-effect" }
82
83
  );
84
+ this.queueWorker = new import_EventSourceQueueWorker.EventSourceQueueWorker(this.app);
85
+ this.app.eventSourceQueueWorker = this.queueWorker;
86
+ this.queueWorker.start();
87
+ this.app.on("beforeStop", async () => {
88
+ var _a;
89
+ (_a = this.queueWorker) == null ? void 0 : _a.stop();
90
+ });
83
91
  this.triggers.register("resource", new import_CustomctionTrigger.CustomActionTrigger(this.app, this.refreshRealTime));
84
92
  this.triggers.register("beforeResource", new import_ResourceEventTrigger.ResourceEventTrigger(this.app, this.refreshRealTime));
85
93
  this.triggers.register("afterResource", new import_ResourceEventTrigger.ResourceEventTrigger(this.app, this.refreshRealTime));
@@ -253,19 +253,23 @@ class WebhookController {
253
253
  }
254
254
  }
255
255
  async triggerWorkflow(ctx, action, body) {
256
- const { currentUser, currentRole } = ctx.state;
257
- const { model: UserModel } = ctx.db.getCollection("users");
256
+ var _a;
257
+ const { currentUser, currentRole } = (ctx == null ? void 0 : ctx.state) || {};
258
+ const userCollection = ctx.db.getCollection("users");
259
+ const { model: UserModel } = userCollection;
258
260
  if (!action.workflowKey) {
259
261
  return;
260
262
  }
261
263
  const userInfo = {
262
- user: UserModel.build(currentUser).desensitize(),
264
+ user: currentUser ? UserModel.build(currentUser).desensitize() : null,
263
265
  roleName: currentRole
264
266
  };
265
267
  const pluginWorkflow = ctx.tego.getPlugin(import_module_workflow.PluginWorkflow);
266
268
  const wfRepo = ctx.db.getRepository("workflows");
267
269
  const wf = await wfRepo.findOne({ filter: { key: action.workflowKey, enabled: true } });
268
- return await pluginWorkflow.trigger(wf, { data: body, ...userInfo }, { httpContext: ctx });
270
+ const useHttpContext = ((_a = action == null ? void 0 : action.options) == null ? void 0 : _a.useHttpContext) !== false;
271
+ const triggerOptions = useHttpContext ? { httpContext: ctx } : void 0;
272
+ return await pluginWorkflow.trigger(wf, { data: body, ...userInfo }, triggerOptions);
269
273
  }
270
274
  }
271
275
  // Annotate the CommonJS export names for ESM import in node:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tachybase/module-event-source",
3
3
  "displayName": "Event Source",
4
- "version": "1.6.15",
4
+ "version": "1.6.17",
5
5
  "description": "Registering and managing custom event sources.",
6
6
  "keywords": [
7
7
  "System management"
@@ -29,8 +29,8 @@
29
29
  "dayjs": "1.11.13",
30
30
  "lodash": "4.17.21",
31
31
  "sequelize": "6.37.5",
32
- "@tachybase/client": "1.6.15",
33
- "@tachybase/module-workflow": "1.6.15"
32
+ "@tachybase/client": "1.6.17",
33
+ "@tachybase/module-workflow": "1.6.17"
34
34
  },
35
35
  "description.zh-CN": "注册和管理自定义事件源",
36
36
  "displayName.zh-CN": "事件源",