@nocobase/plugin-workflow 2.1.30 → 2.1.32

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.
@@ -53,6 +53,7 @@ var import_RunningExecutionRegistry = __toESM(require("./RunningExecutionRegistr
53
53
  var import_actions = __toESM(require("./actions"));
54
54
  var import_nodes = require("./actions/nodes");
55
55
  var import_workflows = require("./actions/workflows");
56
+ var import_constants = require("./constants");
56
57
  var import_functions = __toESM(require("./functions"));
57
58
  var import_CollectionTrigger = __toESM(require("./triggers/CollectionTrigger"));
58
59
  var import_ScheduleTrigger = __toESM(require("./triggers/ScheduleTrigger"));
@@ -170,16 +171,16 @@ class PluginWorkflowServer extends import_server.Plugin {
170
171
  }
171
172
  }
172
173
  this.checker = setInterval(() => {
173
- this.dispatcher.dispatch();
174
+ this.dispatcher.recover({ gracePeriod: 6e4 });
174
175
  }, 3e5);
175
176
  await this.timeoutManager.load();
176
- this.app.on("workflow:dispatch", () => {
177
- this.app.logger.info("workflow:dispatch");
178
- this.dispatcher.dispatch();
177
+ this.app.on("workflow:recover", () => {
178
+ this.app.logger.info("workflow:recover");
179
+ this.dispatcher.recover();
179
180
  });
180
181
  this.dispatcher.setReady(true);
181
182
  this.getLogger("dispatcher").info("(starting) check for queueing executions");
182
- this.dispatcher.dispatch();
183
+ this.dispatcher.recover();
183
184
  };
184
185
  onBeforeStop = async () => {
185
186
  if (this.checker) {
@@ -191,7 +192,6 @@ class PluginWorkflowServer extends import_server.Plugin {
191
192
  for (const workflow of this.enabledCache.values()) {
192
193
  this.toggle(workflow, false, { silent: true });
193
194
  }
194
- this.app.eventQueue.unsubscribe(this.channelPendingExecution);
195
195
  this.loggerCache.clear();
196
196
  };
197
197
  async handleSyncMessage(message) {
@@ -401,7 +401,7 @@ class PluginWorkflowServer extends import_server.Plugin {
401
401
  this.app.on("beforeStop", this.onBeforeStop);
402
402
  this.app.eventQueue.subscribe(this.channelPendingExecution, {
403
403
  idle: () => this.serving() && this.dispatcher.idle,
404
- process: this.dispatcher.onQueueExecution
404
+ process: this.dispatcher.onQueueTask
405
405
  });
406
406
  }
407
407
  toggle(workflow, enable, { silent, transaction } = {}) {
@@ -448,25 +448,70 @@ class PluginWorkflowServer extends import_server.Plugin {
448
448
  trigger(workflow, context, options = {}) {
449
449
  return this.dispatcher.trigger(workflow, context, options);
450
450
  }
451
- async run(pending) {
452
- return this.dispatcher.run(pending);
451
+ async rerun(execution, rerun) {
452
+ return this.dispatcher.enqueue({ executionId: this.getModelId(execution), rerun: rerun ?? {} });
453
453
  }
454
- dispatch() {
455
- return this.dispatcher.dispatch();
454
+ recover() {
455
+ return this.dispatcher.recover();
456
456
  }
457
+ /**
458
+ * Persist the current job state and publish a poke that evaluates that state.
459
+ *
460
+ * PENDING resume calls are valid for instructions such as delay and sequential approval.
461
+ * Callers that aggregate multiple user actions must only call this method when their
462
+ * atomic update changes the job from PENDING to a terminal status.
463
+ * Persistence and queue publication failures are logged and rethrown to awaiting callers.
464
+ */
457
465
  async resume(job) {
458
- return this.dispatcher.resume(job);
466
+ var _a;
467
+ const executionId = job.executionId ?? ((_a = job.execution) == null ? void 0 : _a.id);
468
+ if (executionId == null) {
469
+ this.getLogger("dispatcher").warn(`execution id of job (${job.id}) not found, resume ignored`);
470
+ return;
471
+ }
472
+ try {
473
+ if (job.changed()) {
474
+ await job.save();
475
+ }
476
+ } catch (error) {
477
+ this.getLogger("dispatcher").error(
478
+ `persisting job (${job.id}) before resuming execution (${executionId}) failed`,
479
+ { error, executionId, jobId: job.id }
480
+ );
481
+ throw error;
482
+ }
483
+ try {
484
+ await this.dispatcher.enqueue({ executionId, jobId: job.id });
485
+ this.getLogger("dispatcher").info(`execution (${executionId}) resuming from job (${job.id}) published to queue`);
486
+ } catch (error) {
487
+ this.getLogger("dispatcher").error(
488
+ `publishing resume task for execution (${executionId}) from job (${job.id}) failed`,
489
+ { error, executionId, jobId: job.id }
490
+ );
491
+ throw error;
492
+ }
459
493
  }
460
494
  /**
461
495
  * Start a deferred execution
462
496
  * @experimental
463
497
  */
464
498
  async start(execution) {
465
- return this.dispatcher.start(execution);
499
+ if (typeof execution !== "string" && typeof execution !== "number") {
500
+ if (execution.status !== import_constants.EXECUTION_STATUS.QUEUEING && execution.status !== import_constants.EXECUTION_STATUS.STARTED && execution.status != null) {
501
+ return;
502
+ }
503
+ if (execution.status === import_constants.EXECUTION_STATUS.STARTED && execution.startedAt) {
504
+ return;
505
+ }
506
+ }
507
+ return this.dispatcher.enqueue({ executionId: this.getModelId(execution) });
466
508
  }
467
509
  createProcessor(execution, options = {}) {
468
510
  return new import_Processor.default(execution, { ...options, plugin: this });
469
511
  }
512
+ getModelId(input) {
513
+ return typeof input === "string" || typeof input === "number" ? input : input.id;
514
+ }
470
515
  async execute(workflow, values, options = {}) {
471
516
  const trigger = this.triggers.get(workflow.type);
472
517
  if (!trigger) {
@@ -94,13 +94,7 @@ export default class Processor {
94
94
  prepare(): Promise<void>;
95
95
  start(): Promise<any>;
96
96
  resume(job: JobModel): Promise<void>;
97
- resolveRerun(options?: ProcessorRerunOptions): {
98
- node: FlowNodeModel;
99
- input: JobModel | {
100
- result: any;
101
- };
102
- targetJob: JobModel;
103
- };
97
+ private resolveRerun;
104
98
  rerun(options?: ProcessorRerunOptions): Promise<any>;
105
99
  private getRerunNode;
106
100
  private getRerunInput;
@@ -98,12 +98,9 @@ async function rerun(context, next) {
98
98
  if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
99
99
  return context.throw(400, "Only started executions can be rerun");
100
100
  }
101
- await workflowPlugin.run({
102
- execution,
103
- rerun: {
104
- nodeId,
105
- overwrite: overwrite === true
106
- }
101
+ await workflowPlugin.rerun(execution, {
102
+ nodeId,
103
+ overwrite: overwrite === true
107
104
  });
108
105
  context.body = execution;
109
106
  context.status = 202;
@@ -82,8 +82,7 @@ async function resume(context, next) {
82
82
  context.body = job;
83
83
  context.status = 202;
84
84
  await next();
85
- job.execution = execution;
86
- workflowPlugin.resume(job);
85
+ await workflowPlugin.resume(job);
87
86
  }
88
87
  // Annotate the CommonJS export names for ESM import in node:
89
88
  0 && (module.exports = {
@@ -12,11 +12,13 @@ import WorkflowModel from './Workflow';
12
12
  export default class ExecutionModel extends Model {
13
13
  static readonly database: Database;
14
14
  id: number;
15
+ workflowId: number;
15
16
  title: string;
16
17
  context: any;
17
18
  status: number;
18
19
  reason?: string | null;
19
20
  dispatched: boolean;
21
+ manually?: boolean | null;
20
22
  parentExecutionId?: number | null;
21
23
  stack?: Array<number | string>;
22
24
  startedAt?: Date | null;
@@ -8,8 +8,11 @@
8
8
  */
9
9
  import { BelongsToGetAssociationMixin, Model } from '@nocobase/database';
10
10
  import FlowNodeModel from './FlowNode';
11
+ import ExecutionModel from './Execution';
11
12
  export default class JobModel extends Model {
12
13
  id: number;
14
+ executionId: number;
15
+ workflowId?: number;
13
16
  status: number;
14
17
  result?: any;
15
18
  meta?: any;
@@ -20,4 +23,6 @@ export default class JobModel extends Model {
20
23
  nodeId: number;
21
24
  node?: FlowNodeModel;
22
25
  getNode: BelongsToGetAssociationMixin<FlowNodeModel>;
26
+ execution?: ExecutionModel;
27
+ getExecution: BelongsToGetAssociationMixin<ExecutionModel>;
23
28
  }
@@ -16,6 +16,7 @@ type AbortOptions = Transactionable & {
16
16
  reason?: (typeof EXECUTION_REASON)[keyof typeof EXECUTION_REASON];
17
17
  };
18
18
  export declare function getExecutionLockKey(executionId: number | string): string;
19
+ export declare function getJobLockKey(jobId: number | string): string;
19
20
  export declare function isLockAcquireError(error: unknown): boolean;
20
21
  export declare function validateCollectionField(collection: string, dataSourceManager: DataSourceManager): Record<string, string> | null;
21
22
  export declare function getExecutionStatusName(status: number | null | undefined): string;
@@ -29,6 +29,7 @@ __export(utils_exports, {
29
29
  abortExecution: () => abortExecution,
30
30
  getExecutionLockKey: () => getExecutionLockKey,
31
31
  getExecutionStatusName: () => getExecutionStatusName,
32
+ getJobLockKey: () => getJobLockKey,
32
33
  getWorkflowExecutionLogMeta: () => getWorkflowExecutionLogMeta,
33
34
  isLockAcquireError: () => isLockAcquireError,
34
35
  toJSON: () => toJSON,
@@ -41,6 +42,9 @@ var import_constants = require("./constants");
41
42
  function getExecutionLockKey(executionId) {
42
43
  return `workflow:execution:${executionId}`;
43
44
  }
45
+ function getJobLockKey(jobId) {
46
+ return `workflow:job:${jobId}`;
47
+ }
44
48
  function isLockAcquireError(error) {
45
49
  return error instanceof Error && error.constructor.name === "LockAcquireError";
46
50
  }
@@ -197,6 +201,7 @@ function toJSON(data) {
197
201
  abortExecution,
198
202
  getExecutionLockKey,
199
203
  getExecutionStatusName,
204
+ getJobLockKey,
200
205
  getWorkflowExecutionLogMeta,
201
206
  isLockAcquireError,
202
207
  toJSON,
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "A powerful BPM tool that provides foundational support for business automation, with the capability to extend unlimited triggers and nodes.",
7
7
  "description.zh-CN": "一个强大的 BPM 工具,为业务自动化提供基础支持,并且可任意扩展更多的触发器和节点。",
8
8
  "description.ru-RU": "Мощный инструмент BPM, обеспечивающий базовую поддержку автоматизации бизнес-процессов с возможностью неограниченного расширения триггеров и узлов.",
9
- "version": "2.1.30",
9
+ "version": "2.1.32",
10
10
  "license": "Apache-2.0",
11
11
  "main": "./dist/server/index.js",
12
12
  "homepage": "https://docs.nocobase.com/handbook/workflow",
@@ -49,7 +49,7 @@
49
49
  "@nocobase/test": "2.x",
50
50
  "@nocobase/utils": "2.x"
51
51
  },
52
- "gitHead": "c1a4bff0fd3c8b30e06fc6f50ad33e054b04061a",
52
+ "gitHead": "737a1324f650d7bc66594203b68eb0180553a551",
53
53
  "keywords": [
54
54
  "Workflow"
55
55
  ]