@nocobase/plugin-workflow 2.3.0-alpha.1 → 3.0.0-alpha.2

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.
Files changed (55) hide show
  1. package/dist/client/438.8bd46a04ca080a4e.js +10 -0
  2. package/dist/client/{509.88792f5bd054b2af.js → 509.3a4ac27f10833c75.js} +1 -1
  3. package/dist/client/{67.56a3d15a55ab7d5c.js → 67.5eb8767a709f27d9.js} +1 -1
  4. package/dist/client/828.d03ecfc4ac66e0f8.js +10 -0
  5. package/dist/client/87.b62b1123d4d560fb.js +10 -0
  6. package/dist/client/WorkflowTasks.d.ts +26 -3
  7. package/dist/client/index.d.ts +1 -1
  8. package/dist/client/index.js +1 -1
  9. package/dist/client-v2/{387.5e61d648d16dbf1b.js → 387.e002f8eb34aa77e3.js} +1 -1
  10. package/dist/client-v2/438.1459f1a7cd19ea4f.js +10 -0
  11. package/dist/client-v2/513.18e257b3025d7692.js +10 -0
  12. package/dist/client-v2/{677.7382807126d9621a.js → 677.7007f4a6a4640165.js} +1 -1
  13. package/dist/client-v2/canvas/WorkflowVariableInput.d.ts +8 -1
  14. package/dist/client-v2/canvas/WorkflowVariableTextArea.d.ts +5 -0
  15. package/dist/client-v2/constants.d.ts +4 -4
  16. package/dist/client-v2/index.js +1 -1
  17. package/dist/client-v2/legacySettingsRedirect.d.ts +15 -0
  18. package/dist/client-v2/plugin.d.ts +1 -1
  19. package/dist/common/collections/userWorkflowTaskStats.d.ts +54 -0
  20. package/dist/common/collections/userWorkflowTaskStats.js +87 -0
  21. package/dist/externalVersion.js +13 -13
  22. package/dist/locale/en-US.json +4 -0
  23. package/dist/locale/zh-CN.json +4 -0
  24. package/dist/node_modules/cron-parser/package.json +1 -1
  25. package/dist/node_modules/joi/package.json +1 -1
  26. package/dist/node_modules/lru-cache/package.json +1 -1
  27. package/dist/node_modules/nodejs-snowflake/package.json +1 -1
  28. package/dist/server/Dispatcher.d.ts +24 -13
  29. package/dist/server/Dispatcher.js +160 -143
  30. package/dist/server/Plugin.d.ts +57 -11
  31. package/dist/server/Plugin.js +355 -15
  32. package/dist/server/Processor.d.ts +1 -7
  33. package/dist/server/Processor.js +11 -7
  34. package/dist/server/actions/executions.js +3 -6
  35. package/dist/server/actions/index.js +3 -1
  36. package/dist/server/actions/jobs.js +1 -2
  37. package/dist/server/{migrations/20260713000000-remove-workflow-validation.d.ts → actions/userWorkflowTaskStats.d.ts} +2 -5
  38. package/dist/server/actions/userWorkflowTaskStats.js +112 -0
  39. package/dist/server/actions/workflows.js +17 -0
  40. package/dist/server/collections/userWorkflowTaskStats.d.ts +11 -0
  41. package/dist/server/collections/userWorkflowTaskStats.js +43 -0
  42. package/dist/server/index.d.ts +1 -0
  43. package/dist/server/types/Execution.d.ts +2 -0
  44. package/dist/server/types/Job.d.ts +5 -0
  45. package/dist/server/utils.d.ts +1 -0
  46. package/dist/server/utils.js +5 -0
  47. package/dist/swagger/index.d.ts +68 -1
  48. package/dist/swagger/index.js +61 -1
  49. package/package.json +2 -2
  50. package/dist/client/438.6c6409d821d8c353.js +0 -10
  51. package/dist/client/828.6880ab21513b1968.js +0 -10
  52. package/dist/client/87.083467b89aaf3e53.js +0 -10
  53. package/dist/client-v2/438.b7df0416b66f8f64.js +0 -10
  54. package/dist/client-v2/513.617697f74783a611.js +0 -10
  55. package/dist/server/migrations/20260713000000-remove-workflow-validation.js +0 -56
@@ -35,30 +35,52 @@ var import_constants = require("./constants");
35
35
  var import_Plugin = require("./Plugin");
36
36
  var import_utils = require("./utils");
37
37
  const EXECUTION_ACQUIRE_MAX_ATTEMPTS = 5;
38
- const PENDING_DISPATCH_MAX_ATTEMPTS = 3;
38
+ const QUEUE_TASK_MAX_ATTEMPTS = 3;
39
+ const RECOVERY_BATCH_SIZE = 100;
40
+ const RECOVERY_LOCK_TTL = 3e5;
39
41
  class Dispatcher {
40
42
  constructor(plugin) {
41
43
  this.plugin = plugin;
42
44
  }
43
45
  ready = false;
44
46
  executing = null;
47
+ recovering = null;
45
48
  saving = null;
46
- pending = [];
47
49
  events = [];
48
50
  eventsCount = 0;
49
51
  get idle() {
50
- return this.ready && !this.executing && !this.saving && !this.pending.length && !this.events.length;
52
+ return this.ready && !this.executing && !this.saving && !this.events.length;
51
53
  }
52
- onQueueExecution = async (event) => {
53
- const ExecutionRepo = this.plugin.db.getRepository("executions");
54
- const execution = await ExecutionRepo.findOne({
55
- filterByTk: event.executionId
56
- });
57
- if (!execution || execution.dispatched || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
58
- return;
54
+ onQueueTask = async (event) => {
55
+ var _a;
56
+ const task = event;
57
+ let next = null;
58
+ const previous = this.executing;
59
+ const executing = (async () => {
60
+ await (previous == null ? void 0 : previous.catch(() => void 0));
61
+ this.plugin.getLogger("dispatcher").info(`workflow queue task for execution (${task.executionId}) received from queue`);
62
+ next = await this.resolveTask(task);
63
+ if (!next) {
64
+ return;
65
+ }
66
+ this.plugin.getLogger(next[0].workflowId).info(`queued execution (${next[0].id}) ready to process`);
67
+ await this.process(next[0], next[1], { rerun: next[2] });
68
+ })();
69
+ this.executing = executing;
70
+ try {
71
+ await executing;
72
+ } catch (error) {
73
+ const workflowId = ((_a = next == null ? void 0 : next[0]) == null ? void 0 : _a.workflowId) ?? task.executionId;
74
+ this.plugin.getLogger(workflowId).error(`workflow queue task failed`, { error });
75
+ throw error;
76
+ } finally {
77
+ if (this.executing === executing) {
78
+ this.executing = null;
79
+ }
80
+ if (this.events.length) {
81
+ this.saveEvent();
82
+ }
59
83
  }
60
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) received from queue, adding to pending list`);
61
- await this.run({ execution });
62
84
  };
63
85
  setReady(ready) {
64
86
  this.ready = ready;
@@ -118,20 +140,12 @@ class Dispatcher {
118
140
  try {
119
141
  const execution = await this.createExecution(...event);
120
142
  if (!execution.dispatched) {
121
- if (this.plugin.serving() && !this.executing && !this.pending.length) {
122
- logger.info(`local pending list is empty, adding execution (${execution.id}) to pending list`);
123
- this.pending.push({ execution });
124
- } else {
125
- logger.info(
126
- `instance is not serving as worker or local pending list is not empty, sending execution (${execution.id}) to queue`
127
- );
128
- try {
129
- await this.plugin.app.eventQueue.publish(this.plugin.channelPendingExecution, {
130
- executionId: execution.id
131
- });
132
- } catch (qErr) {
133
- logger.error(`publishing execution (${execution.id}) to queue failed:`, { error: qErr });
134
- }
143
+ try {
144
+ await this.enqueue({
145
+ executionId: execution.id
146
+ });
147
+ } catch (qErr) {
148
+ logger.error(`publishing execution (${execution.id}) to queue failed:`, { error: qErr });
135
149
  }
136
150
  }
137
151
  } catch (error) {
@@ -142,31 +156,17 @@ class Dispatcher {
142
156
  this.saving = null;
143
157
  if (this.events.length) {
144
158
  this.saveEvent();
145
- } else {
146
- this.dispatch();
147
159
  }
148
160
  }
149
161
  })();
150
162
  }
151
- async resume(job) {
152
- let { execution } = job;
153
- if (!execution) {
154
- execution = await job.getExecution();
155
- }
156
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) resuming from job (${job.id}) added to pending list`);
157
- await this.run({ execution, job });
158
- }
159
- async start(execution) {
160
- if (execution.status) {
161
- return;
162
- }
163
- this.plugin.getLogger(execution.workflowId).info(`starting deferred execution (${execution.id})`);
164
- await this.run({ execution });
165
- }
166
163
  async beforeStop() {
167
164
  this.ready = false;
168
165
  this.plugin.getLogger("dispatcher").info("app is stopping, draining local queues...");
169
- while (this.saving || this.executing || this.events.length || this.pending.length) {
166
+ if (this.recovering) {
167
+ await this.recovering;
168
+ }
169
+ while (this.saving || this.executing || this.events.length) {
170
170
  if (this.saving) {
171
171
  await this.saving;
172
172
  }
@@ -176,85 +176,131 @@ class Dispatcher {
176
176
  if (this.events.length && !this.saving) {
177
177
  this.saveEvent();
178
178
  }
179
- if (this.pending.length && !this.executing) {
180
- this.dispatch();
181
- }
182
179
  await new Promise((resolve) => setImmediate(resolve));
183
180
  }
184
181
  this.plugin.getLogger("dispatcher").info("local queues drained");
185
182
  }
186
- dispatch() {
187
- if (!this.ready && !this.pending.length && !this.events.length) {
183
+ async recover(options = {}) {
184
+ if (!this.ready) {
188
185
  return;
189
186
  }
190
- if (this.executing) {
191
- this.plugin.getLogger("dispatcher").warn(`workflow executing is not finished, new dispatching will be ignored`);
187
+ if (this.recovering) {
188
+ await this.recovering;
192
189
  return;
193
190
  }
194
- if (this.events.length) {
195
- this.saveEvent();
196
- return;
191
+ const recovering = this.recoverQueueTasks(options.gracePeriod ?? 0);
192
+ this.recovering = recovering;
193
+ try {
194
+ await recovering;
195
+ } finally {
196
+ if (this.recovering === recovering) {
197
+ this.recovering = null;
198
+ }
197
199
  }
198
- this.executing = (async () => {
199
- let next = null;
200
- let pending = null;
200
+ }
201
+ async recoverQueueTasks(gracePeriod) {
202
+ const logger = this.plugin.getLogger("dispatcher");
203
+ try {
204
+ if (!this.plugin.serving()) {
205
+ logger.warn(
206
+ `${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance, workflow queue recovery will be ignored`
207
+ );
208
+ return;
209
+ }
210
+ let lock;
201
211
  try {
202
- pending = this.pending.shift() ?? null;
203
- if (pending || this.ready && this.plugin.serving()) {
204
- const execution = await this.prepare((pending == null ? void 0 : pending.execution) ?? null, {
205
- immediate: pending == null ? void 0 : pending.immediate
206
- });
207
- if (execution) {
208
- next = [execution, pending == null ? void 0 : pending.job, pending == null ? void 0 : pending.rerun];
209
- }
210
- if (pending && next) {
211
- this.plugin.getLogger(next[0].workflowId).info(`pending execution (${next[0].id}) ready to process`);
212
- }
213
- } else {
214
- this.plugin.getLogger("dispatcher").warn(
215
- `${import_Plugin.WORKER_JOB_WORKFLOW_PROCESS} is not serving on this instance or app not ready, new dispatching will be ignored`
216
- );
212
+ lock = await this.plugin.app.lockManager.tryAcquire(`workflow:recover:${this.plugin.app.name}`);
213
+ } catch (error) {
214
+ if ((0, import_utils.isLockAcquireError)(error)) {
215
+ logger.debug(`workflow queue recovery is already running on another instance`);
216
+ return;
217
217
  }
218
- if (next) {
219
- try {
220
- await this.process(next[0], next[1], { rerun: next[2] });
221
- } catch (error) {
222
- this.plugin.getLogger(next[0].workflowId).error(`execution (${next[0].id}) process failed`, { error });
223
- if (pending && (0, import_utils.isLockAcquireError)(error)) {
224
- this.pending.unshift({ ...pending, execution: next[0], immediate: true });
225
- }
218
+ throw error;
219
+ }
220
+ await lock.runExclusive(async () => {
221
+ let executionCursor = null;
222
+ while (this.ready) {
223
+ const executions = await this.findQueueingExecutions(executionCursor, gracePeriod);
224
+ if (!executions.length) {
225
+ break;
226
226
  }
227
- }
228
- } catch (error) {
229
- this.plugin.getLogger("dispatcher").error(`workflow dispatch failed`, { error });
230
- if (pending) {
231
- const dispatchAttempts = (pending.dispatchAttempts ?? 0) + 1;
232
- if (dispatchAttempts < PENDING_DISPATCH_MAX_ATTEMPTS) {
233
- this.pending.push({ ...pending, dispatchAttempts });
234
- } else {
235
- this.plugin.getLogger(pending.execution.workflowId).error(`pending execution (${pending.execution.id}) dispatch failed, local retry limit reached`, {
236
- error,
237
- dispatchAttempts
238
- });
227
+ for (const execution of executions) {
228
+ if (!this.ready) {
229
+ return;
230
+ }
231
+ await this.enqueue({ executionId: execution.id });
239
232
  }
233
+ executionCursor = executions[executions.length - 1].id;
240
234
  }
241
- } finally {
242
- setImmediate(() => {
243
- this.executing = null;
244
- if (next || this.pending.length) {
245
- this.plugin.getLogger("dispatcher").debug(`last process finished, will do another dispatch`);
246
- this.dispatch();
247
- }
248
- });
249
- }
250
- })();
235
+ }, RECOVERY_LOCK_TTL);
236
+ } catch (error) {
237
+ logger.error(`workflow queue recovery failed`, { error });
238
+ }
239
+ }
240
+ async enqueue(task) {
241
+ await this.plugin.app.eventQueue.publish(this.plugin.channelPendingExecution, task, {
242
+ maxRetries: QUEUE_TASK_MAX_ATTEMPTS - 1
243
+ });
251
244
  }
252
- async run(pending) {
253
- this.pending.push({
254
- ...pending,
255
- immediate: !this.executing && !this.pending.length && !this.saving && !this.events.length
245
+ async loadJob(jobId) {
246
+ if (jobId == null) {
247
+ return null;
248
+ }
249
+ return this.plugin.db.getRepository("jobs").findOne({
250
+ filterByTk: jobId
256
251
  });
257
- this.dispatch();
252
+ }
253
+ async findQueueingExecutions(afterId, gracePeriod) {
254
+ const executions = await this.plugin.db.getRepository("executions").find({
255
+ filter: {
256
+ ...afterId == null ? {} : { id: { $gt: afterId } },
257
+ ...gracePeriod > 0 ? { createdAt: { $lt: new Date(Date.now() - gracePeriod) } } : {},
258
+ dispatched: false,
259
+ status: import_constants.EXECUTION_STATUS.QUEUEING,
260
+ startedAt: null,
261
+ "workflow.enabled": true
262
+ },
263
+ sort: "id",
264
+ limit: RECOVERY_BATCH_SIZE
265
+ });
266
+ for (const execution of executions) {
267
+ this.plugin.getLogger(execution.workflowId).info(`queueing execution (${execution.id}) found, publishing task`);
268
+ }
269
+ return executions;
270
+ }
271
+ async resolveTask(task) {
272
+ const executionInput = await this.plugin.db.getRepository("executions").findOne({
273
+ filterByTk: task.executionId
274
+ });
275
+ if (!executionInput) {
276
+ this.plugin.getLogger("dispatcher").warn(`execution (${task.executionId}) not found, queue task ignored`);
277
+ return null;
278
+ }
279
+ if (task.jobId == null && task.rerun == null && executionInput.status === import_constants.EXECUTION_STATUS.STARTED && executionInput.startedAt) {
280
+ this.plugin.getLogger(executionInput.workflowId).warn(`execution (${executionInput.id}) has already started, start task ignored`);
281
+ return null;
282
+ }
283
+ let job = null;
284
+ if (task.jobId != null) {
285
+ job = await this.loadJob(task.jobId);
286
+ if (!job) {
287
+ this.plugin.getLogger(executionInput.workflowId).warn(`job (${task.jobId}) not found, resume ignored`);
288
+ return null;
289
+ }
290
+ if (String(job.executionId) !== String(executionInput.id)) {
291
+ this.plugin.getLogger(executionInput.workflowId).warn(`job (${job.id}) does not belong to execution (${executionInput.id}), resume ignored`);
292
+ return null;
293
+ }
294
+ }
295
+ const execution = await this.prepare(executionInput);
296
+ if (!execution) {
297
+ return null;
298
+ }
299
+ if (job) {
300
+ job.execution = execution;
301
+ return [execution, job];
302
+ }
303
+ return [execution, void 0, task.rerun];
258
304
  }
259
305
  async triggerSync(workflow, context, { deferred, ...options } = {}) {
260
306
  let execution;
@@ -367,7 +413,7 @@ class Dispatcher {
367
413
  return execution;
368
414
  }
369
415
  async prepare(input, options = {}) {
370
- const logger = input ? this.plugin.getLogger(input.workflowId) : this.plugin.getLogger("dispatcher");
416
+ const logger = this.plugin.getLogger(input.workflowId);
371
417
  if (options.transaction) {
372
418
  try {
373
419
  return await this.acquireExecution(input, options, options.transaction);
@@ -402,8 +448,8 @@ class Dispatcher {
402
448
  },
403
449
  {
404
450
  logger,
405
- conflictMessage: input ? `acquiring pending execution (${input.id}) conflicted with another worker, retrying` : `acquiring execution conflicted with another worker, retrying`,
406
- maxAttemptsMessage: input ? `acquiring pending execution (${input.id}) reached max retry attempts` : `acquiring execution reached max retry attempts, will retry on next dispatch`
451
+ conflictMessage: `acquiring queue task execution (${input.id}) conflicted with another worker, retrying`,
452
+ maxAttemptsMessage: `acquiring queue task execution (${input.id}) reached max retry attempts`
407
453
  }
408
454
  );
409
455
  } catch (error) {
@@ -415,39 +461,10 @@ class Dispatcher {
415
461
  return result;
416
462
  }
417
463
  async acquireExecution(input, options, transaction) {
418
- let execution = input;
419
- if (execution) {
420
- if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
421
- await execution.reload({ transaction });
422
- }
423
- } else {
424
- const workflowIds = [...this.plugin.enabledCache.keys()];
425
- if (!workflowIds.length) {
426
- this.plugin.getLogger("dispatcher").debug(`no enabled workflow to process`);
427
- return null;
428
- }
429
- execution = await this.plugin.db.getRepository("executions").findOne({
430
- filter: {
431
- dispatched: false,
432
- status: import_constants.EXECUTION_STATUS.QUEUEING,
433
- startedAt: null,
434
- workflowId: workflowIds
435
- },
436
- sort: "id",
437
- transaction,
438
- lock: transaction.LOCK.UPDATE,
439
- skipLocked: true
440
- });
441
- if (execution) {
442
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
443
- } else {
444
- this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
445
- }
446
- }
447
- if (!execution) {
448
- return null;
464
+ if (!options.immediate || input.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
465
+ await input.reload({ transaction });
449
466
  }
450
- return this.enter(execution, transaction);
467
+ return this.enter(input, transaction);
451
468
  }
452
469
  async acquireWithRetry(acquire, options) {
453
470
  for (let attempt = 1; attempt <= EXECUTION_ACQUIRE_MAX_ATTEMPTS; attempt++) {
@@ -528,7 +545,7 @@ class Dispatcher {
528
545
  var _a, _b, _c;
529
546
  if (!execution.dispatched) {
530
547
  await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED });
531
- logger.info(`execution (${execution.id}) from pending list updated to started`);
548
+ logger.info(`execution (${execution.id}) from workflow queue updated to started`);
532
549
  }
533
550
  this.plugin.timeoutManager.scheduleExecutionTimeout(execution);
534
551
  const processor = this.plugin.createProcessor(execution, processorOptions);
@@ -11,16 +11,39 @@ import { Transactionable } from 'sequelize';
11
11
  import { Plugin } from '@nocobase/server';
12
12
  import { Registry } from '@nocobase/utils';
13
13
  import { Logger } from '@nocobase/logger';
14
- import Dispatcher, { EventOptions } from './Dispatcher';
15
- import Processor from './Processor';
14
+ import { EventOptions } from './Dispatcher';
15
+ import Processor, { ProcessorRerunOptions } from './Processor';
16
16
  import ExecutionTimeoutManager from './ExecutionTimeoutManager';
17
17
  import RunningExecutionRegistry from './RunningExecutionRegistry';
18
18
  import { CustomFunction } from './functions';
19
19
  import Trigger from './triggers';
20
20
  import { InstructionInterface } from './instructions';
21
- import type { ExecutionModel, WorkflowModel } from './types';
21
+ import type { ExecutionModel, JobModel, WorkflowModel } from './types';
22
22
  import type { Transaction } from '@nocobase/database';
23
23
  type ID = number | string;
24
+ type TaskStats = {
25
+ pending: number;
26
+ all: number;
27
+ };
28
+ export type TaskStatsRow = {
29
+ userId: number;
30
+ workflowKey: string;
31
+ type: string;
32
+ pending: number;
33
+ all: number;
34
+ };
35
+ export type RepairTaskStatsOptions = {
36
+ userIds?: number[];
37
+ workflowKeys?: string[];
38
+ types?: string[];
39
+ transaction?: Transaction;
40
+ silent?: boolean;
41
+ };
42
+ export type TaskStatsProvider = {
43
+ collectTaskStats: (options: RepairTaskStatsOptions, context: {
44
+ plugin: PluginWorkflowServer;
45
+ }) => Promise<TaskStatsRow[]>;
46
+ };
24
47
  export declare const WORKER_JOB_WORKFLOW_PROCESS = "workflow:process";
25
48
  export default class PluginWorkflowServer extends Plugin {
26
49
  instructions: Registry<InstructionInterface>;
@@ -35,6 +58,7 @@ export default class PluginWorkflowServer extends Plugin {
35
58
  private loggerCache;
36
59
  private meter;
37
60
  private checker;
61
+ private taskStatsProviders;
38
62
  private onBeforeSave;
39
63
  private onAfterCreate;
40
64
  private onAfterUpdate;
@@ -75,15 +99,24 @@ export default class PluginWorkflowServer extends Plugin {
75
99
  load(): Promise<void>;
76
100
  private toggle;
77
101
  trigger(workflow: WorkflowModel, context: object, options?: EventOptions): void | Promise<Processor | null | void>;
78
- run(pending: Parameters<Dispatcher['run']>[0]): Promise<void>;
79
- dispatch(): void;
80
- resume(job: any): Promise<void>;
102
+ rerun(execution: ExecutionModel | ID, rerun?: ProcessorRerunOptions): Promise<void>;
103
+ recover(): Promise<void>;
104
+ /**
105
+ * Persist the current job state and publish a poke that evaluates that state.
106
+ *
107
+ * PENDING resume calls are valid for instructions such as delay and sequential approval.
108
+ * Callers that aggregate multiple user actions must only call this method when their
109
+ * atomic update changes the job from PENDING to a terminal status.
110
+ * Persistence and queue publication failures are logged and rethrown to awaiting callers.
111
+ */
112
+ resume(job: JobModel): Promise<void>;
81
113
  /**
82
114
  * Start a deferred execution
83
115
  * @experimental
84
116
  */
85
- start(execution: ExecutionModel): Promise<void>;
117
+ start(execution: ExecutionModel | ID): Promise<void>;
86
118
  createProcessor(execution: ExecutionModel, options?: {}): Processor;
119
+ private getModelId;
87
120
  execute(workflow: WorkflowModel, values: any, options?: EventOptions): Promise<void | Processor>;
88
121
  /**
89
122
  * @experimental
@@ -94,12 +127,25 @@ export default class PluginWorkflowServer extends Plugin {
94
127
  */
95
128
  useDataSourceTransaction(dataSourceName?: string, transaction?: Transaction | null, create?: false): Transaction | undefined;
96
129
  useDataSourceTransaction(dataSourceName: string | undefined, transaction: Transaction | null | undefined, create: true): Transaction | Promise<Transaction> | undefined;
130
+ registerTaskStatsProvider(type: string, provider: TaskStatsProvider): void;
131
+ getWorkflowIdsByKey(workflowKey: string, transaction?: Transaction): Promise<any>;
132
+ private buildTaskStatsFilter;
133
+ private normalizeTaskStats;
134
+ private upsertTaskStatsRow;
135
+ refreshUserWorkflowTaskTypeStats(userId: number, type: string, { transaction }?: Transactionable): Promise<any>;
136
+ refreshUserWorkflowTaskWorkflowStats(userId: number, workflowKey: string, { transaction }?: Transactionable): Promise<any>;
137
+ private sendTaskWorkflowStatsUpdated;
138
+ updateTaskStatsByWorkflow(input: {
139
+ userId: number;
140
+ workflowKey: string;
141
+ type: string;
142
+ stats: TaskStats;
143
+ }, { transaction }?: Transactionable): Promise<void>;
144
+ repairTaskStats(options?: RepairTaskStatsOptions): Promise<void>;
97
145
  /**
98
146
  * @experimental
147
+ * @deprecated Use updateTaskStatsByWorkflow() when workflowKey is available.
99
148
  */
100
- updateTasksStats(userId: number, type: string, stats: {
101
- pending: number;
102
- all: number;
103
- }, { transaction }: Transactionable): Promise<void>;
149
+ updateTasksStats(userId: number, type: string, stats: TaskStats, { transaction }: Transactionable): Promise<void>;
104
150
  }
105
151
  export {};