@nocobase/plugin-workflow 2.1.0-beta.43 → 2.1.0-beta.45

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.
@@ -11,8 +11,8 @@ module.exports = {
11
11
  "react": "18.2.0",
12
12
  "@formily/core": "2.3.7",
13
13
  "@formily/react": "2.3.7",
14
- "@nocobase/client": "2.1.0-beta.43",
15
- "@nocobase/utils": "2.1.0-beta.43",
14
+ "@nocobase/client": "2.1.0-beta.45",
15
+ "@nocobase/utils": "2.1.0-beta.45",
16
16
  "antd": "5.24.2",
17
17
  "@ant-design/icons": "5.6.1",
18
18
  "react-router-dom": "6.30.1",
@@ -20,20 +20,20 @@ module.exports = {
20
20
  "lodash": "4.18.1",
21
21
  "@dnd-kit/core": "6.1.0",
22
22
  "@formily/shared": "2.3.7",
23
- "@nocobase/flow-engine": "2.1.0-beta.43",
24
- "@nocobase/plugin-mobile": "2.1.0-beta.43",
23
+ "@nocobase/flow-engine": "2.1.0-beta.45",
24
+ "@nocobase/plugin-mobile": "2.1.0-beta.45",
25
25
  "sequelize": "6.35.2",
26
- "@nocobase/server": "2.1.0-beta.43",
27
- "@nocobase/database": "2.1.0-beta.43",
28
- "@nocobase/data-source-manager": "2.1.0-beta.43",
29
- "@nocobase/logger": "2.1.0-beta.43",
30
- "@nocobase/evaluators": "2.1.0-beta.43",
26
+ "@nocobase/server": "2.1.0-beta.45",
27
+ "@nocobase/database": "2.1.0-beta.45",
28
+ "@nocobase/data-source-manager": "2.1.0-beta.45",
29
+ "@nocobase/logger": "2.1.0-beta.45",
30
+ "@nocobase/evaluators": "2.1.0-beta.45",
31
31
  "@formily/antd-v5": "1.2.3",
32
32
  "@formily/reactive": "2.3.7",
33
33
  "@emotion/css": "11.13.0",
34
34
  "@formily/json-schema": "2.3.7",
35
- "@nocobase/actions": "2.1.0-beta.43",
35
+ "@nocobase/actions": "2.1.0-beta.45",
36
36
  "dayjs": "1.11.13",
37
- "@nocobase/plugin-workflow-test": "2.1.0-beta.43",
38
- "@nocobase/test": "2.1.0-beta.43"
37
+ "@nocobase/plugin-workflow-test": "2.1.0-beta.45",
38
+ "@nocobase/test": "2.1.0-beta.45"
39
39
  };
@@ -1 +1 @@
1
- {"name":"cron-parser","version":"4.4.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^1.28.0"},"devDependencies":{"eslint":"^8.2.0","sinon":"^10.0.0","tap":"^16.0.1","tsd":"^0.19.0"},"engines":{"node":">=0.8"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"_lastModified":"2026-06-02T14:17:13.022Z"}
1
+ {"name":"cron-parser","version":"4.4.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^1.28.0"},"devDependencies":{"eslint":"^8.2.0","sinon":"^10.0.0","tap":"^16.0.1","tsd":"^0.19.0"},"engines":{"node":">=0.8"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"_lastModified":"2026-06-08T04:33:04.401Z"}
@@ -1 +1 @@
1
- {"name":"joi","description":"Object schema validation","version":"17.13.3","repository":"git://github.com/hapijs/joi","main":"lib/index.js","types":"lib/index.d.ts","browser":"dist/joi-browser.min.js","files":["lib/**/*","dist/*"],"keywords":["schema","validation"],"dependencies":{"@hapi/hoek":"^9.3.0","@hapi/topo":"^5.1.0","@sideway/address":"^4.1.5","@sideway/formula":"^3.0.1","@sideway/pinpoint":"^2.0.0"},"devDependencies":{"@hapi/bourne":"2.x.x","@hapi/code":"8.x.x","@hapi/joi-legacy-test":"npm:@hapi/joi@15.x.x","@hapi/lab":"^25.1.3","@types/node":"^14.18.63","typescript":"4.3.x"},"scripts":{"prepublishOnly":"cd browser && npm install && npm run build","test":"lab -t 100 -a @hapi/code -L -Y","test-cov-html":"lab -r html -o coverage.html -a @hapi/code"},"license":"BSD-3-Clause","_lastModified":"2026-06-02T14:17:12.766Z"}
1
+ {"name":"joi","description":"Object schema validation","version":"17.13.3","repository":"git://github.com/hapijs/joi","main":"lib/index.js","types":"lib/index.d.ts","browser":"dist/joi-browser.min.js","files":["lib/**/*","dist/*"],"keywords":["schema","validation"],"dependencies":{"@hapi/hoek":"^9.3.0","@hapi/topo":"^5.1.0","@sideway/address":"^4.1.5","@sideway/formula":"^3.0.1","@sideway/pinpoint":"^2.0.0"},"devDependencies":{"@hapi/bourne":"2.x.x","@hapi/code":"8.x.x","@hapi/joi-legacy-test":"npm:@hapi/joi@15.x.x","@hapi/lab":"^25.1.3","@types/node":"^14.18.63","typescript":"4.3.x"},"scripts":{"prepublishOnly":"cd browser && npm install && npm run build","test":"lab -t 100 -a @hapi/code -L -Y","test-cov-html":"lab -r html -o coverage.html -a @hapi/code"},"license":"BSD-3-Clause","_lastModified":"2026-06-08T04:33:04.152Z"}
@@ -1 +1 @@
1
- {"name":"lru-cache","description":"A cache object that deletes the least-recently-used items.","version":"11.3.5","author":"Isaac Z. Schlueter <i@izs.me>","keywords":["mru","lru","cache"],"sideEffects":false,"scripts":{"build":"npm run prepare","prepare":"tshy && bash scripts/build.sh","pretest":"npm run prepare","presnap":"npm run prepare","test":"tap","snap":"tap","preversion":"npm test","postversion":"npm publish","prepublishOnly":"git push origin --follow-tags","format":"prettier --write .","typedoc":"typedoc --tsconfig ./.tshy/esm.json ./src/*.ts","benchmark-results-typedoc":"bash scripts/benchmark-results-typedoc.sh","prebenchmark":"npm run prepare","benchmark":"make -C benchmark","preprofile":"npm run prepare","profile":"make -C benchmark profile","lint":"oxlint --fix src test","postsnap":"npm run lint","postlint":"npm run format"},"main":"./dist/commonjs/index.min.js","types":"./dist/commonjs/index.d.ts","tshy":{"esmDialects":["node","browser"],"exports":{"./raw":"./src/index.ts",".":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.min.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.min.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.min.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.min.js"}}},"selfLink":false},"repository":{"type":"git","url":"git+ssh://git@github.com/isaacs/node-lru-cache.git"},"devDependencies":{"benchmark":"^2.1.4","esbuild":"^0.25.9","marked":"^4.2.12","mkdirp":"^3.0.1","oxlint":"^1.58.0","oxlint-tsgolint":"^0.19.0","prettier":"^3.8.1","tap":"^21.6.3","tshy":"^4.1.1","typedoc":"^0.28.18"},"license":"BlueOak-1.0.0","files":["dist"],"engines":{"node":"20 || >=22"},"exports":{"./raw":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.js"}},".":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.min.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.min.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.min.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.min.js"}}},"type":"module","module":"./dist/esm/index.min.js","_lastModified":"2026-06-02T14:17:11.970Z"}
1
+ {"name":"lru-cache","description":"A cache object that deletes the least-recently-used items.","version":"11.3.5","author":"Isaac Z. Schlueter <i@izs.me>","keywords":["mru","lru","cache"],"sideEffects":false,"scripts":{"build":"npm run prepare","prepare":"tshy && bash scripts/build.sh","pretest":"npm run prepare","presnap":"npm run prepare","test":"tap","snap":"tap","preversion":"npm test","postversion":"npm publish","prepublishOnly":"git push origin --follow-tags","format":"prettier --write .","typedoc":"typedoc --tsconfig ./.tshy/esm.json ./src/*.ts","benchmark-results-typedoc":"bash scripts/benchmark-results-typedoc.sh","prebenchmark":"npm run prepare","benchmark":"make -C benchmark","preprofile":"npm run prepare","profile":"make -C benchmark profile","lint":"oxlint --fix src test","postsnap":"npm run lint","postlint":"npm run format"},"main":"./dist/commonjs/index.min.js","types":"./dist/commonjs/index.d.ts","tshy":{"esmDialects":["node","browser"],"exports":{"./raw":"./src/index.ts",".":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.min.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.min.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.min.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.min.js"}}},"selfLink":false},"repository":{"type":"git","url":"git+ssh://git@github.com/isaacs/node-lru-cache.git"},"devDependencies":{"benchmark":"^2.1.4","esbuild":"^0.25.9","marked":"^4.2.12","mkdirp":"^3.0.1","oxlint":"^1.58.0","oxlint-tsgolint":"^0.19.0","prettier":"^3.8.1","tap":"^21.6.3","tshy":"^4.1.1","typedoc":"^0.28.18"},"license":"BlueOak-1.0.0","files":["dist"],"engines":{"node":"20 || >=22"},"exports":{"./raw":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.js"}},".":{"import":{"node":{"types":"./dist/esm/node/index.d.ts","default":"./dist/esm/node/index.min.js"},"browser":{"types":"./dist/esm/browser/index.d.ts","default":"./dist/esm/browser/index.min.js"},"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.min.js"},"require":{"types":"./dist/commonjs/index.d.ts","default":"./dist/commonjs/index.min.js"}}},"type":"module","module":"./dist/esm/index.min.js","_lastModified":"2026-06-08T04:33:03.437Z"}
@@ -1 +1 @@
1
- {"name":"nodejs-snowflake","collaborators":["Utkarsh Srivastava <utkarsh@sagacious.dev>"],"description":"Generate time sortable 64 bits unique ids for distributed systems (inspired from twitter snowflake)","version":"2.0.1","license":"Apache 2.0","repository":{"type":"git","url":"https://github.com/utkarsh-pro/nodejs-snowflake.git"},"files":["nodejs_snowflake_bg.wasm","nodejs_snowflake.js","nodejs_snowflake.d.ts"],"main":"nodejs_snowflake.js","types":"nodejs_snowflake.d.ts","_lastModified":"2026-06-02T14:17:11.788Z"}
1
+ {"name":"nodejs-snowflake","collaborators":["Utkarsh Srivastava <utkarsh@sagacious.dev>"],"description":"Generate time sortable 64 bits unique ids for distributed systems (inspired from twitter snowflake)","version":"2.0.1","license":"Apache 2.0","repository":{"type":"git","url":"https://github.com/utkarsh-pro/nodejs-snowflake.git"},"files":["nodejs_snowflake_bg.wasm","nodejs_snowflake.js","nodejs_snowflake.d.ts"],"main":"nodejs_snowflake.js","types":"nodejs_snowflake.d.ts","_lastModified":"2026-06-08T04:33:03.280Z"}
@@ -52,7 +52,10 @@ export default class Dispatcher {
52
52
  private validateEvent;
53
53
  private createExecution;
54
54
  private prepare;
55
+ private acquireExecution;
56
+ private acquireWithRetry;
55
57
  private enter;
58
+ private isConcurrentAcquireError;
56
59
  private process;
57
60
  }
58
61
  export {};
@@ -34,6 +34,7 @@ var import_sequelize = require("sequelize");
34
34
  var import_constants = require("./constants");
35
35
  var import_Plugin = require("./Plugin");
36
36
  var import_utils = require("./utils");
37
+ const EXECUTION_ACQUIRE_MAX_ATTEMPTS = 5;
37
38
  class Dispatcher {
38
39
  constructor(plugin) {
39
40
  this.plugin = plugin;
@@ -248,9 +249,7 @@ class Dispatcher {
248
249
  return null;
249
250
  }
250
251
  try {
251
- const entered = await this.prepare(execution, {
252
- transaction: this.plugin.useDataSourceTransaction("main", options.transaction)
253
- });
252
+ const entered = await this.prepare(execution);
254
253
  if (!entered) {
255
254
  return null;
256
255
  }
@@ -274,8 +273,7 @@ class Dispatcher {
274
273
  const existed = await workflow.countExecutions({
275
274
  where: {
276
275
  id: stack
277
- },
278
- transaction: options.transaction
276
+ }
279
277
  });
280
278
  const limitCount = workflow.options.stackLimit || 1;
281
279
  if (existed >= limitCount) {
@@ -290,115 +288,146 @@ class Dispatcher {
290
288
  async createExecution(workflow, context, options) {
291
289
  var _a;
292
290
  const { deferred } = options;
293
- const transaction = await this.plugin.useDataSourceTransaction("main", options.transaction, true);
294
- const sameTransaction = options.transaction === transaction;
295
291
  let stack = options.stack;
296
292
  if (options.parentExecutionId && !stack) {
297
293
  const parentExecution = await this.plugin.db.getRepository("executions").findOne({
298
- filterByTk: options.parentExecutionId,
299
- transaction
294
+ filterByTk: options.parentExecutionId
300
295
  });
301
296
  stack = parentExecution ? [...parentExecution.stack ?? [], parentExecution.id] : [];
302
297
  }
303
- const valid = await this.validateEvent(workflow, context, { ...options, stack, transaction });
298
+ const valid = await this.validateEvent(workflow, context, { ...options, stack });
304
299
  if (!valid) {
305
- if (!sameTransaction) {
306
- await transaction.commit();
307
- }
308
300
  (_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
309
301
  throw new Error("event is not valid");
310
302
  }
311
- let execution;
312
- try {
313
- execution = await workflow.createExecution(
314
- {
315
- context,
316
- key: workflow.key,
317
- eventKey: options.eventKey ?? (0, import_node_crypto.randomUUID)(),
318
- stack,
319
- parentExecutionId: options.parentExecutionId ?? null,
320
- dispatched: deferred ?? false,
321
- status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
322
- manually: options.manually
323
- },
324
- { transaction }
325
- );
326
- } catch (err) {
327
- if (!sameTransaction) {
328
- await transaction.rollback();
329
- }
330
- throw err;
331
- }
303
+ const execution = await workflow.createExecution({
304
+ context,
305
+ key: workflow.key,
306
+ eventKey: options.eventKey ?? (0, import_node_crypto.randomUUID)(),
307
+ stack,
308
+ parentExecutionId: options.parentExecutionId ?? null,
309
+ dispatched: deferred ?? false,
310
+ status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING,
311
+ manually: options.manually
312
+ });
332
313
  this.plugin.getLogger(workflow.id).info(`execution of workflow ${workflow.id} created as ${execution.id}`);
333
314
  if (!workflow.stats) {
334
- workflow.stats = await workflow.getStats({ transaction });
315
+ workflow.stats = await workflow.getStats();
335
316
  }
336
- await workflow.stats.increment("executed", { transaction });
317
+ await workflow.stats.increment("executed");
337
318
  if (this.plugin.db.options.dialect !== "postgres") {
338
- await workflow.stats.reload({ transaction });
319
+ await workflow.stats.reload();
339
320
  }
340
321
  if (!workflow.versionStats) {
341
- workflow.versionStats = await workflow.getVersionStats({ transaction });
322
+ workflow.versionStats = await workflow.getVersionStats();
342
323
  }
343
- await workflow.versionStats.increment("executed", { transaction });
324
+ await workflow.versionStats.increment("executed");
344
325
  if (this.plugin.db.options.dialect !== "postgres") {
345
- await workflow.versionStats.reload({ transaction });
346
- }
347
- if (!sameTransaction) {
348
- await transaction.commit();
326
+ await workflow.versionStats.reload();
349
327
  }
350
328
  execution.workflow = workflow;
351
329
  return execution;
352
330
  }
353
331
  async prepare(input, options = {}) {
354
- const transaction = options.transaction;
355
- const ownTransaction = !transaction;
356
- const tx = transaction || await this.plugin.db.sequelize.transaction({
357
- isolationLevel: this.plugin.db.options.dialect === "sqlite" ? void 0 : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ
358
- });
359
332
  const logger = input ? this.plugin.getLogger(input.workflowId) : this.plugin.getLogger("dispatcher");
333
+ if (options.transaction) {
334
+ try {
335
+ const { execution } = await this.acquireExecution(input, options, options.transaction);
336
+ return execution;
337
+ } catch (error) {
338
+ if (error instanceof Error) {
339
+ logger.error(`entering execution failed: ${error.message}`, { error });
340
+ }
341
+ return null;
342
+ }
343
+ }
344
+ let result = null;
360
345
  try {
361
- let execution = input;
362
- if (execution) {
363
- if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
364
- await execution.reload({ transaction: tx });
346
+ await this.acquireWithRetry(
347
+ async () => {
348
+ const tx = await this.plugin.db.sequelize.transaction({
349
+ isolationLevel: this.plugin.db.options.dialect === "sqlite" ? void 0 : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ
350
+ });
351
+ try {
352
+ const { execution, shouldRetry } = await this.acquireExecution(input, options, tx);
353
+ await tx.commit();
354
+ result = execution;
355
+ return shouldRetry;
356
+ } catch (error) {
357
+ await tx.rollback();
358
+ if (this.isConcurrentAcquireError(error)) {
359
+ throw error;
360
+ }
361
+ if (error instanceof Error) {
362
+ logger.error(`entering execution failed: ${error.message}`, { error });
363
+ }
364
+ result = null;
365
+ return false;
366
+ }
367
+ },
368
+ {
369
+ logger,
370
+ conflictMessage: input ? `acquiring pending execution (${input.id}) conflicted with another worker, retrying` : `acquiring execution conflicted with another worker, retrying`,
371
+ maxAttemptsMessage: input ? `acquiring pending execution (${input.id}) reached max retry attempts` : `acquiring execution reached max retry attempts, will retry on next dispatch`
365
372
  }
373
+ );
374
+ } catch (error) {
375
+ if (error instanceof Error) {
376
+ logger.error(`acquiring execution failed: ${error.message}`, { error });
377
+ }
378
+ return null;
379
+ }
380
+ return result;
381
+ }
382
+ async acquireExecution(input, options, transaction) {
383
+ let execution = input;
384
+ if (execution) {
385
+ if (!options.immediate || execution.status !== import_constants.EXECUTION_STATUS.QUEUEING) {
386
+ await execution.reload({ transaction });
387
+ }
388
+ } else {
389
+ execution = await this.plugin.db.getRepository("executions").findOne({
390
+ filter: {
391
+ dispatched: false,
392
+ "workflow.enabled": true
393
+ },
394
+ sort: "id",
395
+ transaction,
396
+ lock: transaction.LOCK.UPDATE,
397
+ skipLocked: true
398
+ });
399
+ if (execution) {
400
+ this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
366
401
  } else {
367
- execution = await this.plugin.db.getRepository("executions").findOne({
368
- filter: {
369
- dispatched: false,
370
- "workflow.enabled": true
371
- },
372
- sort: "id",
373
- transaction: tx,
374
- lock: tx.LOCK.UPDATE,
375
- skipLocked: true
376
- });
377
- if (execution) {
378
- this.plugin.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
379
- } else {
380
- this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
381
- }
402
+ this.plugin.getLogger("dispatcher").debug(`no execution in db queued to process`);
382
403
  }
383
- if (!execution) {
384
- if (ownTransaction) {
385
- await tx.commit();
404
+ }
405
+ if (!execution) {
406
+ return { execution: null, shouldRetry: false };
407
+ }
408
+ const entered = await this.enter(execution, transaction);
409
+ const shouldRetry = !input && !entered;
410
+ return { execution: entered, shouldRetry };
411
+ }
412
+ async acquireWithRetry(acquire, options) {
413
+ for (let attempt = 1; attempt <= EXECUTION_ACQUIRE_MAX_ATTEMPTS; attempt++) {
414
+ let shouldRetry = false;
415
+ try {
416
+ shouldRetry = await acquire();
417
+ } catch (error) {
418
+ if (!this.isConcurrentAcquireError(error)) {
419
+ throw error;
386
420
  }
387
- return null;
421
+ shouldRetry = true;
422
+ options.logger.warn(options.conflictMessage, { error });
388
423
  }
389
- const entered = await this.enter(execution, tx);
390
- if (ownTransaction) {
391
- await tx.commit();
424
+ if (!shouldRetry) {
425
+ break;
392
426
  }
393
- return entered;
394
- } catch (error) {
395
- if (ownTransaction) {
396
- await tx.rollback();
397
- }
398
- if (error instanceof Error) {
399
- logger.error(`entering execution failed: ${error.message}`, { error });
427
+ if (attempt >= EXECUTION_ACQUIRE_MAX_ATTEMPTS) {
428
+ options.logger.warn(options.maxAttemptsMessage);
429
+ break;
400
430
  }
401
- return null;
402
431
  }
403
432
  }
404
433
  async enter(execution, transaction) {
@@ -446,14 +475,23 @@ class Dispatcher {
446
475
  execution.workflow = workflow;
447
476
  return execution;
448
477
  }
478
+ isConcurrentAcquireError(error) {
479
+ var _a, _b, _c, _d;
480
+ if (!(error instanceof Error)) {
481
+ return false;
482
+ }
483
+ const databaseError = error;
484
+ const code = ((_a = databaseError.parent) == null ? void 0 : _a.code) || ((_b = databaseError.original) == null ? void 0 : _b.code);
485
+ const errno = ((_c = databaseError.parent) == null ? void 0 : _c.errno) || ((_d = databaseError.original) == null ? void 0 : _d.errno);
486
+ return code === "40001" || code === "40P01" || code === "ER_LOCK_DEADLOCK" || errno === 1205 || errno === 1213;
487
+ }
449
488
  async process(execution, job = null, options = {}) {
450
489
  const { rerun, ...processorOptions } = options;
451
490
  const logger = this.plugin.getLogger(execution.workflowId);
452
491
  const run = async () => {
453
492
  var _a, _b, _c;
454
493
  if (!execution.dispatched) {
455
- const transaction = await this.plugin.useDataSourceTransaction("main", processorOptions.transaction);
456
- await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED }, { transaction });
494
+ await execution.update({ dispatched: true, status: import_constants.EXECUTION_STATUS.STARTED });
457
495
  logger.info(`execution (${execution.id}) from pending list updated to started`);
458
496
  }
459
497
  this.plugin.timeoutManager.scheduleExecutionTimeout(execution);
@@ -464,7 +502,7 @@ class Dispatcher {
464
502
  logger.info(`execution (${execution.id}) finished with status: ${execution.status}`);
465
503
  logger.debug(`execution (${execution.id}) details:`, { execution });
466
504
  if (execution.status && ((_c = (_b = (_a = execution.workflow) == null ? void 0 : _a.options) == null ? void 0 : _b.deleteExecutionOnStatus) == null ? void 0 : _c.includes(execution.status))) {
467
- await execution.destroy({ transaction: processor.mainTransaction });
505
+ await execution.destroy();
468
506
  }
469
507
  } catch (err) {
470
508
  if (err instanceof Error) {
@@ -26,6 +26,7 @@ export default class ExecutionTimeoutManager {
26
26
  getRemainingMs(execution: ExecutionModel, now?: Date): number;
27
27
  abort(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
28
28
  abortExecutionIfExpired(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
29
+ private abortExecutionIfExpiredWithLock;
29
30
  clear(executionId: number | string): void;
30
31
  shouldContinue(execution: ExecutionModel, options?: Transactionable): Promise<boolean>;
31
32
  /**
@@ -36,6 +36,7 @@ const LOAD_BATCH_SIZE = 1e3;
36
36
  const DEFAULT_SCAN_INTERVAL = 3e4;
37
37
  const SCAN_JITTER = 5e3;
38
38
  const MAX_TIMER_DELAY = 2147483647;
39
+ const EXECUTION_LOCK_TIMEOUT = 6e4;
39
40
  class ExecutionTimeoutManager {
40
41
  constructor(plugin) {
41
42
  this.plugin = plugin;
@@ -105,6 +106,19 @@ class ExecutionTimeoutManager {
105
106
  }
106
107
  return this.abort(execution, options);
107
108
  }
109
+ async abortExecutionIfExpiredWithLock(executionOrId) {
110
+ const executionId = typeof executionOrId === "object" ? executionOrId.id : executionOrId;
111
+ const lock = await this.plugin.app.lockManager.tryAcquire((0, import_utils.getExecutionLockKey)(executionId), EXECUTION_LOCK_TIMEOUT);
112
+ return lock.runExclusive(async () => {
113
+ const execution = await this.plugin.db.getRepository("executions").findOne({
114
+ filterByTk: executionId
115
+ });
116
+ if (!execution) {
117
+ return false;
118
+ }
119
+ return this.abortExecutionIfExpired(execution);
120
+ }, EXECUTION_LOCK_TIMEOUT);
121
+ }
108
122
  clear(executionId) {
109
123
  this.clearExecutionTimeout(executionId);
110
124
  }
@@ -195,7 +209,7 @@ class ExecutionTimeoutManager {
195
209
  limit: LOAD_BATCH_SIZE
196
210
  });
197
211
  for (const execution of executions) {
198
- await this.abortExecutionIfExpired(execution);
212
+ await this.abortExecutionIfExpiredWithLock(execution);
199
213
  }
200
214
  })();
201
215
  try {
@@ -301,12 +315,6 @@ class ExecutionTimeoutManager {
301
315
  if (this.plugin.abortRunningExecution(executionId, import_constants.EXECUTION_REASON.TIMEOUT)) {
302
316
  return;
303
317
  }
304
- const execution = await this.plugin.db.getRepository("executions").findOne({
305
- filterByTk: executionId
306
- });
307
- if (!execution) {
308
- return;
309
- }
310
- await this.abortExecutionIfExpired(execution);
318
+ await this.abortExecutionIfExpiredWithLock(executionId);
311
319
  }
312
320
  }
@@ -89,10 +89,11 @@ export default class PluginWorkflowServer extends Plugin {
89
89
  * @experimental
90
90
  * @param {string} dataSourceName
91
91
  * @param {Transaction} transaction
92
- * @param {boolean} create
93
- * @returns {Trasaction}
92
+ * @param {boolean} create Create a new transaction when the input transaction does not belong to this data source.
93
+ * @returns {Transaction}
94
94
  */
95
- useDataSourceTransaction(dataSourceName: string, transaction: any, create?: boolean): any;
95
+ useDataSourceTransaction(dataSourceName?: string, transaction?: Transaction | null, create?: false): Transaction | undefined;
96
+ useDataSourceTransaction(dataSourceName: string | undefined, transaction: Transaction | null | undefined, create: true): Transaction | Promise<Transaction> | undefined;
96
97
  /**
97
98
  * @experimental
98
99
  */
@@ -477,15 +477,12 @@ class PluginWorkflowServer extends import_server.Plugin {
477
477
  }
478
478
  return trigger.execute(workflow, values, options);
479
479
  }
480
- /**
481
- * @experimental
482
- * @param {string} dataSourceName
483
- * @param {Transaction} transaction
484
- * @param {boolean} create
485
- * @returns {Trasaction}
486
- */
487
480
  useDataSourceTransaction(dataSourceName = "main", transaction, create = false) {
488
- const { db } = this.app.dataSourceManager.dataSources.get(dataSourceName).collectionManager;
481
+ const dataSource = this.app.dataSourceManager.dataSources.get(dataSourceName);
482
+ if (!dataSource) {
483
+ throw new Error(`data source ${dataSourceName} is not found`);
484
+ }
485
+ const { db } = dataSource.collectionManager;
489
486
  if (!db) {
490
487
  return;
491
488
  }
@@ -47,10 +47,6 @@ export default class Processor {
47
47
  * @experimental
48
48
  */
49
49
  transaction?: Transaction | null;
50
- /**
51
- * @experimental
52
- */
53
- mainTransaction?: Transaction | null;
54
50
  /**
55
51
  * @experimental
56
52
  */
@@ -68,10 +68,6 @@ class Processor {
68
68
  * @experimental
69
69
  */
70
70
  transaction = null;
71
- /**
72
- * @experimental
73
- */
74
- mainTransaction = null;
75
71
  /**
76
72
  * @experimental
77
73
  */
@@ -210,15 +206,13 @@ class Processor {
210
206
  execution,
211
207
  options: { plugin }
212
208
  } = this;
213
- this.mainTransaction = plugin.useDataSourceTransaction("main", this.transaction);
214
- const transaction = this.mainTransaction;
215
209
  if (!execution.workflow) {
216
- execution.workflow = plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow({ transaction });
210
+ execution.workflow = plugin.enabledCache.get(execution.workflowId) || await execution.getWorkflow();
217
211
  }
218
212
  if (!execution.workflow) {
219
213
  throw new Error(`workflow (#${execution.workflowId}) not found for execution (#${execution.id})`);
220
214
  }
221
- const nodes = execution.workflow.nodes || await execution.workflow.getNodes({ transaction });
215
+ const nodes = execution.workflow.nodes || await execution.workflow.getNodes();
222
216
  execution.workflow.nodes = nodes;
223
217
  this.makeNodes(nodes);
224
218
  const JobDBModel = plugin.db.getModel("jobs");
@@ -228,15 +222,13 @@ class Processor {
228
222
  where: {
229
223
  executionId: execution.id
230
224
  },
231
- raw: true,
232
- transaction
225
+ raw: true
233
226
  });
234
227
  const jobs = await execution.getJobs({
235
228
  where: {
236
229
  id: jobIds.map((item) => item.id)
237
230
  },
238
- order: [["id", "ASC"]],
239
- transaction
231
+ order: [["id", "ASC"]]
240
232
  });
241
233
  execution.jobs = jobs;
242
234
  this.makeJobs(jobs);
@@ -478,7 +470,7 @@ class Processor {
478
470
  if (changes.length) {
479
471
  await this.options.plugin.db.sequelize.query(
480
472
  `UPDATE ${JobCollection.quotedTableName()} SET ${changes.map(([key]) => `${key} = ?`)} WHERE id='${job.id}'`,
481
- { replacements: changes.map(([, value]) => value), transaction: this.mainTransaction }
473
+ { replacements: changes.map(([, value]) => value) }
482
474
  );
483
475
  }
484
476
  }
@@ -488,7 +480,6 @@ class Processor {
488
480
  await JobsModel.bulkCreate(
489
481
  newJobs.map((job) => job.toJSON()),
490
482
  {
491
- transaction: this.mainTransaction,
492
483
  returning: false
493
484
  }
494
485
  );
@@ -510,18 +501,14 @@ class Processor {
510
501
  id: this.execution.id,
511
502
  status: import_constants.EXECUTION_STATUS.STARTED
512
503
  },
513
- individualHooks: true,
514
- transaction: this.mainTransaction
504
+ individualHooks: true
515
505
  });
516
506
  if (affected) {
517
507
  this.execution.set(values);
518
508
  } else {
519
- await this.execution.reload({ transaction: this.mainTransaction });
509
+ await this.execution.reload();
520
510
  }
521
511
  }
522
- if (this.mainTransaction && this.mainTransaction !== this.transaction) {
523
- await this.mainTransaction.commit();
524
- }
525
512
  if (this.execution.status === import_constants.EXECUTION_STATUS.STARTED) {
526
513
  this.options.plugin.timeoutManager.scheduleExecutionTimeout(this.execution);
527
514
  } else {
@@ -598,8 +585,7 @@ class Processor {
598
585
  this.setTimeoutGuard(remaining);
599
586
  }
600
587
  async shouldContinueExecution() {
601
- const transaction = this.mainTransaction ?? this.transaction;
602
- return this.options.plugin.timeoutManager.shouldContinue(this.execution, { transaction });
588
+ return this.options.plugin.timeoutManager.shouldContinue(this.execution);
603
589
  }
604
590
  leaveRunningState() {
605
591
  if (this.timeoutGuard) {
@@ -50,7 +50,7 @@ class ExecutionResultInstruction extends import__.Instruction {
50
50
  const { value } = node.config;
51
51
  const output = processor.getParsedValue(value, node.id);
52
52
  try {
53
- await processor.execution.update({ output }, { hooks: false, transaction: processor.mainTransaction });
53
+ await processor.execution.update({ output }, { hooks: false });
54
54
  } catch (e) {
55
55
  return {
56
56
  result: e.message,
@@ -124,6 +124,12 @@ class CollectionTrigger extends import__.default {
124
124
  return;
125
125
  }
126
126
  if (workflow.sync) {
127
+ if (transaction && this.workflow.db.options.dialect === "sqlite") {
128
+ transaction.afterCommit(async () => {
129
+ await this.workflow.trigger(workflow, ctx, { stack });
130
+ });
131
+ return;
132
+ }
127
133
  await this.workflow.trigger(workflow, ctx, {
128
134
  transaction,
129
135
  stack