@acorex-platform/framework-server 0.0.37 → 0.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/src/index.d.ts +1 -0
- package/core/src/index.d.ts.map +1 -1
- package/core/src/index.js +1 -0
- package/core/src/index.js.map +1 -1
- package/core/src/lib/filters/http-exception.filter.d.ts +1 -4
- package/core/src/lib/filters/http-exception.filter.d.ts.map +1 -1
- package/core/src/lib/filters/http-exception.filter.js +6 -11
- package/core/src/lib/filters/http-exception.filter.js.map +1 -1
- package/core/src/lib/hook/axp-hook-registry.service.d.ts +10 -0
- package/core/src/lib/hook/axp-hook-registry.service.d.ts.map +1 -0
- package/core/src/lib/hook/axp-hook-registry.service.js +35 -0
- package/core/src/lib/hook/axp-hook-registry.service.js.map +1 -0
- package/core/src/lib/hook/axp-hook.module.d.ts +3 -0
- package/core/src/lib/hook/axp-hook.module.d.ts.map +1 -0
- package/core/src/lib/hook/axp-hook.module.js +21 -0
- package/core/src/lib/hook/axp-hook.module.js.map +1 -0
- package/core/src/lib/hook/index.d.ts +3 -0
- package/core/src/lib/hook/index.d.ts.map +1 -0
- package/core/src/lib/hook/index.js +6 -0
- package/core/src/lib/hook/index.js.map +1 -0
- package/core/src/lib/runtime/entity-runtime.handlers.d.ts +32 -0
- package/core/src/lib/runtime/entity-runtime.handlers.d.ts.map +1 -0
- package/core/src/lib/runtime/entity-runtime.handlers.js +124 -0
- package/core/src/lib/runtime/entity-runtime.handlers.js.map +1 -0
- package/core/src/lib/runtime/index.d.ts +1 -0
- package/core/src/lib/runtime/index.d.ts.map +1 -1
- package/core/src/lib/runtime/index.js +1 -0
- package/core/src/lib/runtime/index.js.map +1 -1
- package/core/src/lib/runtime/runtime-dispatcher.service.d.ts +4 -1
- package/core/src/lib/runtime/runtime-dispatcher.service.d.ts.map +1 -1
- package/core/src/lib/runtime/runtime-dispatcher.service.js +60 -50
- package/core/src/lib/runtime/runtime-dispatcher.service.js.map +1 -1
- package/core/src/lib/runtime/runtime.module.d.ts.map +1 -1
- package/core/src/lib/runtime/runtime.module.js +3 -0
- package/core/src/lib/runtime/runtime.module.js.map +1 -1
- package/domain/abstract/src/index.d.ts +1 -0
- package/domain/abstract/src/index.d.ts.map +1 -1
- package/domain/abstract/src/index.js +1 -0
- package/domain/abstract/src/index.js.map +1 -1
- package/domain/abstract/src/lib/definitions/entity-definition.types.d.ts +2 -0
- package/domain/abstract/src/lib/definitions/entity-definition.types.d.ts.map +1 -1
- package/domain/abstract/src/lib/queries/server-query-fallback.token.d.ts +15 -0
- package/domain/abstract/src/lib/queries/server-query-fallback.token.d.ts.map +1 -0
- package/domain/abstract/src/lib/queries/server-query-fallback.token.js +12 -0
- package/domain/abstract/src/lib/queries/server-query-fallback.token.js.map +1 -0
- package/domain/abstract/src/lib/queries/server-query-handler.decorator.d.ts +1 -0
- package/domain/abstract/src/lib/queries/server-query-handler.decorator.d.ts.map +1 -1
- package/domain/abstract/src/lib/queries/server-query-handler.decorator.js +2 -1
- package/domain/abstract/src/lib/queries/server-query-handler.decorator.js.map +1 -1
- package/domain/providers/src/lib/middlewares/child-count.middleware.d.ts.map +1 -1
- package/domain/providers/src/lib/middlewares/child-count.middleware.js +13 -2
- package/domain/providers/src/lib/middlewares/child-count.middleware.js.map +1 -1
- package/domain/providers/src/lib/middlewares/merge-detail-relation.middleware.d.ts.map +1 -1
- package/domain/providers/src/lib/middlewares/merge-detail-relation.middleware.js +4 -2
- package/domain/providers/src/lib/middlewares/merge-detail-relation.middleware.js.map +1 -1
- package/domain/providers/src/lib/server-command.registry.d.ts +3 -1
- package/domain/providers/src/lib/server-command.registry.d.ts.map +1 -1
- package/domain/providers/src/lib/server-command.registry.js +17 -7
- package/domain/providers/src/lib/server-command.registry.js.map +1 -1
- package/domain/providers/src/lib/server-query.registry.d.ts +8 -3
- package/domain/providers/src/lib/server-query.registry.d.ts.map +1 -1
- package/domain/providers/src/lib/server-query.registry.js +33 -6
- package/domain/providers/src/lib/server-query.registry.js.map +1 -1
- package/domain/providers/src/lib/utils/entity-relation-join.planner.d.ts +8 -1
- package/domain/providers/src/lib/utils/entity-relation-join.planner.d.ts.map +1 -1
- package/domain/providers/src/lib/utils/entity-relation-join.planner.js +46 -6
- package/domain/providers/src/lib/utils/entity-relation-join.planner.js.map +1 -1
- package/domain/providers/src/lib/utils/in-memory-query.util.d.ts +2 -2
- package/domain/providers/src/lib/utils/in-memory-query.util.d.ts.map +1 -1
- package/domain/providers/src/lib/utils/in-memory-query.util.js +16 -10
- package/domain/providers/src/lib/utils/in-memory-query.util.js.map +1 -1
- package/domain/providers/src/lib/utils/instrument-runtime-middleware.util.d.ts +5 -0
- package/domain/providers/src/lib/utils/instrument-runtime-middleware.util.d.ts.map +1 -0
- package/domain/providers/src/lib/utils/instrument-runtime-middleware.util.js +56 -0
- package/domain/providers/src/lib/utils/instrument-runtime-middleware.util.js.map +1 -0
- package/domain/providers/src/lib/utils/merge-detail-query.util.d.ts +1 -0
- package/domain/providers/src/lib/utils/merge-detail-query.util.d.ts.map +1 -1
- package/domain/providers/src/lib/utils/merge-detail-query.util.js +1 -1
- package/domain/providers/src/lib/utils/merge-detail-query.util.js.map +1 -1
- package/domain/seed/src/lib/axp-entity-to-definition.converter.d.ts.map +1 -1
- package/domain/seed/src/lib/axp-entity-to-definition.converter.js +20 -1
- package/domain/seed/src/lib/axp-entity-to-definition.converter.js.map +1 -1
- package/jql/abstract/src/index.d.ts +3 -0
- package/jql/abstract/src/index.d.ts.map +1 -1
- package/jql/abstract/src/index.js +3 -0
- package/jql/abstract/src/index.js.map +1 -1
- package/jql/abstract/src/lib/cli-seed-process.d.ts +3 -0
- package/jql/abstract/src/lib/cli-seed-process.d.ts.map +1 -0
- package/jql/abstract/src/lib/cli-seed-process.js +8 -0
- package/jql/abstract/src/lib/cli-seed-process.js.map +1 -0
- package/jql/abstract/src/lib/datasource/storage-binding-definition.cache.d.ts.map +1 -1
- package/jql/abstract/src/lib/datasource/storage-binding-definition.cache.js +3 -2
- package/jql/abstract/src/lib/datasource/storage-binding-definition.cache.js.map +1 -1
- package/jql/abstract/src/lib/entity-store/entity-seed-storage.adapter.d.ts +7 -0
- package/jql/abstract/src/lib/entity-store/entity-seed-storage.adapter.d.ts.map +1 -1
- package/jql/abstract/src/lib/entity-store/entity-seed-storage.adapter.js +4 -3
- package/jql/abstract/src/lib/entity-store/entity-seed-storage.adapter.js.map +1 -1
- package/jql/abstract/src/lib/entity-store/entity-store-request.util.d.ts.map +1 -1
- package/jql/abstract/src/lib/entity-store/entity-store-request.util.js +2 -0
- package/jql/abstract/src/lib/entity-store/entity-store-request.util.js.map +1 -1
- package/jql/abstract/src/lib/entity-store/identity-membership.util.d.ts +11 -0
- package/jql/abstract/src/lib/entity-store/identity-membership.util.d.ts.map +1 -0
- package/jql/abstract/src/lib/entity-store/identity-membership.util.js +138 -0
- package/jql/abstract/src/lib/entity-store/identity-membership.util.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/query-sort-locale.util.d.ts +6 -0
- package/jql/abstract/src/lib/entity-store/query-sort-locale.util.d.ts.map +1 -0
- package/jql/abstract/src/lib/entity-store/query-sort-locale.util.js +23 -0
- package/jql/abstract/src/lib/entity-store/query-sort-locale.util.js.map +1 -0
- package/jql/abstract/src/lib/jql.types.d.ts +5 -0
- package/jql/abstract/src/lib/jql.types.d.ts.map +1 -1
- package/jql/abstract/src/lib/jql.types.js.map +1 -1
- package/jql/mongodb/src/lib/compile/aggregate-group-keys.util.d.ts +8 -0
- package/jql/mongodb/src/lib/compile/aggregate-group-keys.util.d.ts.map +1 -0
- package/jql/mongodb/src/lib/compile/aggregate-group-keys.util.js +57 -0
- package/jql/mongodb/src/lib/compile/aggregate-group-keys.util.js.map +1 -0
- package/jql/mongodb/src/lib/compile/multilanguage-sort.util.d.ts +14 -0
- package/jql/mongodb/src/lib/compile/multilanguage-sort.util.d.ts.map +1 -0
- package/jql/mongodb/src/lib/compile/multilanguage-sort.util.js +135 -0
- package/jql/mongodb/src/lib/compile/multilanguage-sort.util.js.map +1 -0
- package/jql/mongodb/src/lib/jql-mongodb.engine.d.ts +1 -0
- package/jql/mongodb/src/lib/jql-mongodb.engine.d.ts.map +1 -1
- package/jql/mongodb/src/lib/jql-mongodb.engine.js +32 -21
- package/jql/mongodb/src/lib/jql-mongodb.engine.js.map +1 -1
- package/jql/schema/src/lib/generated/plugin-contributions.bootstrap.d.ts.map +1 -1
- package/jql/schema/src/lib/generated/plugin-contributions.bootstrap.js +29 -0
- package/jql/schema/src/lib/generated/plugin-contributions.bootstrap.js.map +1 -1
- package/jql/schema/src/lib/resolve-storage-data-type.util.d.ts +5 -0
- package/jql/schema/src/lib/resolve-storage-data-type.util.d.ts.map +1 -1
- package/jql/schema/src/lib/resolve-storage-data-type.util.js +3 -2
- package/jql/schema/src/lib/resolve-storage-data-type.util.js.map +1 -1
- package/jql/seed/src/index.d.ts +8 -5
- package/jql/seed/src/index.d.ts.map +1 -1
- package/jql/seed/src/index.js +8 -5
- package/jql/seed/src/index.js.map +1 -1
- package/jql/seed/src/lib/jql-multi-seed-runner.service.d.ts +1 -1
- package/jql/seed/src/lib/jql-multi-seed-runner.service.d.ts.map +1 -1
- package/jql/seed/src/lib/jql-multi-seed-runner.service.js +26 -16
- package/jql/seed/src/lib/jql-multi-seed-runner.service.js.map +1 -1
- package/jql/seed/src/lib/jql-release-ledger.service.d.ts +24 -0
- package/jql/seed/src/lib/jql-release-ledger.service.d.ts.map +1 -0
- package/jql/seed/src/lib/jql-release-ledger.service.js +197 -0
- package/jql/seed/src/lib/jql-release-ledger.service.js.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.options.d.ts +4 -0
- package/jql/seed/src/lib/jql-release-migrate.options.d.ts.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.options.js +78 -0
- package/jql/seed/src/lib/jql-release-migrate.options.js.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.service.d.ts +15 -0
- package/jql/seed/src/lib/jql-release-migrate.service.d.ts.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.service.js +103 -0
- package/jql/seed/src/lib/jql-release-migrate.service.js.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.tokens.d.ts +5 -0
- package/jql/seed/src/lib/jql-release-migrate.tokens.d.ts.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.tokens.js +8 -0
- package/jql/seed/src/lib/jql-release-migrate.tokens.js.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.types.d.ts +63 -0
- package/jql/seed/src/lib/jql-release-migrate.types.d.ts.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.types.js +15 -0
- package/jql/seed/src/lib/jql-release-migrate.types.js.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.util.d.ts +35 -0
- package/jql/seed/src/lib/jql-release-migrate.util.d.ts.map +1 -0
- package/jql/seed/src/lib/jql-release-migrate.util.js +172 -0
- package/jql/seed/src/lib/jql-release-migrate.util.js.map +1 -0
- package/jql/seed/src/lib/jql-release-migration.runner.d.ts +17 -0
- package/jql/seed/src/lib/jql-release-migration.runner.d.ts.map +1 -0
- package/jql/seed/src/lib/jql-release-migration.runner.js +69 -0
- package/jql/seed/src/lib/jql-release-migration.runner.js.map +1 -0
- package/jql/seed/src/lib/jql-release-seed-reconcile.service.d.ts +19 -0
- package/jql/seed/src/lib/jql-release-seed-reconcile.service.d.ts.map +1 -0
- package/jql/seed/src/lib/jql-release-seed-reconcile.service.js +175 -0
- package/jql/seed/src/lib/jql-release-seed-reconcile.service.js.map +1 -0
- package/jql/seed/src/lib/jql-seed-api.types.d.ts +0 -6
- package/jql/seed/src/lib/jql-seed-api.types.d.ts.map +1 -1
- package/jql/seed/src/lib/jql-seed-api.types.js +0 -8
- package/jql/seed/src/lib/jql-seed-api.types.js.map +1 -1
- package/jql/seed/src/lib/jql-seed-dependency.util.js +1 -1
- package/jql/seed/src/lib/jql-seed-dependency.util.js.map +1 -1
- package/jql/seed/src/lib/jql-seed-diff.service.d.ts +9 -3
- package/jql/seed/src/lib/jql-seed-diff.service.d.ts.map +1 -1
- package/jql/seed/src/lib/jql-seed-diff.service.js +38 -18
- package/jql/seed/src/lib/jql-seed-diff.service.js.map +1 -1
- package/jql/seed/src/lib/jql-seed-options.resolve.js +2 -2
- package/jql/seed/src/lib/jql-seed-options.resolve.js.map +1 -1
- package/jql/seed/src/lib/jql-seed-plan.util.d.ts.map +1 -1
- package/jql/seed/src/lib/jql-seed-plan.util.js +5 -0
- package/jql/seed/src/lib/jql-seed-plan.util.js.map +1 -1
- package/jql/seed/src/lib/jql-seed-progress.util.d.ts +27 -0
- package/jql/seed/src/lib/jql-seed-progress.util.d.ts.map +1 -0
- package/jql/seed/src/lib/jql-seed-progress.util.js +146 -0
- package/jql/seed/src/lib/jql-seed-progress.util.js.map +1 -0
- package/jql/seed/src/lib/jql-seed-runner.service.d.ts +2 -2
- package/jql/seed/src/lib/jql-seed-runner.service.d.ts.map +1 -1
- package/jql/seed/src/lib/jql-seed-runner.service.js +2 -2
- package/jql/seed/src/lib/jql-seed-runner.service.js.map +1 -1
- package/jql/seed/src/lib/jql-seed-selection.util.js +8 -8
- package/jql/seed/src/lib/jql-seed-selection.util.js.map +1 -1
- package/jql/seed/src/lib/jql-seed.module.d.ts +3 -0
- package/jql/seed/src/lib/jql-seed.module.d.ts.map +1 -1
- package/jql/seed/src/lib/jql-seed.module.js +18 -3
- package/jql/seed/src/lib/jql-seed.module.js.map +1 -1
- package/jql/seed/src/lib/jql-seed.tokens.d.ts +0 -2
- package/jql/seed/src/lib/jql-seed.tokens.d.ts.map +1 -1
- package/jql/seed/src/lib/jql-seed.tokens.js +1 -3
- package/jql/seed/src/lib/jql-seed.tokens.js.map +1 -1
- package/observability/abstract/src/index.d.ts +1 -6
- package/observability/abstract/src/index.d.ts.map +1 -1
- package/observability/abstract/src/index.js +1 -6
- package/observability/abstract/src/index.js.map +1 -1
- package/observability/abstract/src/lib/entity-observability-skip.util.d.ts +4 -1
- package/observability/abstract/src/lib/entity-observability-skip.util.d.ts.map +1 -1
- package/observability/abstract/src/lib/entity-observability-skip.util.js +4 -1
- package/observability/abstract/src/lib/entity-observability-skip.util.js.map +1 -1
- package/observability/abstract/src/lib/execution-trace/runtime-trace-buffer.d.ts.map +1 -1
- package/observability/abstract/src/lib/execution-trace/runtime-trace-buffer.js +15 -3
- package/observability/abstract/src/lib/execution-trace/runtime-trace-buffer.js.map +1 -1
- package/observability/abstract/src/lib/observability.config.d.ts +3 -0
- package/observability/abstract/src/lib/observability.config.d.ts.map +1 -1
- package/observability/abstract/src/lib/observability.config.js.map +1 -1
- package/observability/abstract/src/lib/observability.tokens.d.ts +0 -3
- package/observability/abstract/src/lib/observability.tokens.d.ts.map +1 -1
- package/observability/abstract/src/lib/observability.tokens.js +1 -4
- package/observability/abstract/src/lib/observability.tokens.js.map +1 -1
- package/observability/abstract/src/lib/structured-log.writer.d.ts +12 -0
- package/observability/abstract/src/lib/structured-log.writer.d.ts.map +1 -0
- package/observability/abstract/src/lib/structured-log.writer.js +19 -0
- package/observability/abstract/src/lib/structured-log.writer.js.map +1 -0
- package/observability/providers/src/index.d.ts +0 -1
- package/observability/providers/src/index.d.ts.map +1 -1
- package/observability/providers/src/index.js +0 -1
- package/observability/providers/src/index.js.map +1 -1
- package/observability/providers/src/lib/config/observability-config.resolve.d.ts.map +1 -1
- package/observability/providers/src/lib/config/observability-config.resolve.js +2 -0
- package/observability/providers/src/lib/config/observability-config.resolve.js.map +1 -1
- package/observability/providers/src/lib/context/resolve-request-observability-entity.util.d.ts.map +1 -1
- package/observability/providers/src/lib/context/resolve-request-observability-entity.util.js +13 -0
- package/observability/providers/src/lib/context/resolve-request-observability-entity.util.js.map +1 -1
- package/observability/providers/src/lib/execution-trace/execution-trace-recorder.service.d.ts +4 -4
- package/observability/providers/src/lib/execution-trace/execution-trace-recorder.service.d.ts.map +1 -1
- package/observability/providers/src/lib/execution-trace/execution-trace-recorder.service.js +111 -26
- package/observability/providers/src/lib/execution-trace/execution-trace-recorder.service.js.map +1 -1
- package/observability/providers/src/lib/http/http-exception-log.util.d.ts +2 -3
- package/observability/providers/src/lib/http/http-exception-log.util.d.ts.map +1 -1
- package/observability/providers/src/lib/http/http-exception-log.util.js +6 -21
- package/observability/providers/src/lib/http/http-exception-log.util.js.map +1 -1
- package/observability/providers/src/lib/http/http-metrics.interceptor.d.ts +1 -2
- package/observability/providers/src/lib/http/http-metrics.interceptor.d.ts.map +1 -1
- package/observability/providers/src/lib/http/http-metrics.interceptor.js +10 -9
- package/observability/providers/src/lib/http/http-metrics.interceptor.js.map +1 -1
- package/observability/providers/src/lib/http/observability-context.interceptor.d.ts +4 -2
- package/observability/providers/src/lib/http/observability-context.interceptor.d.ts.map +1 -1
- package/observability/providers/src/lib/http/observability-context.interceptor.js +44 -8
- package/observability/providers/src/lib/http/observability-context.interceptor.js.map +1 -1
- package/observability/providers/src/lib/http/register-express-observability.middleware.d.ts.map +1 -1
- package/observability/providers/src/lib/http/register-express-observability.middleware.js +2 -0
- package/observability/providers/src/lib/http/register-express-observability.middleware.js.map +1 -1
- package/observability/providers/src/lib/observability.bootstrap.d.ts.map +1 -1
- package/observability/providers/src/lib/observability.bootstrap.js +12 -6
- package/observability/providers/src/lib/observability.bootstrap.js.map +1 -1
- package/observability/providers/src/lib/observability.module.d.ts.map +1 -1
- package/observability/providers/src/lib/observability.module.js +0 -23
- package/observability/providers/src/lib/observability.module.js.map +1 -1
- package/observability/providers/src/lib/pino/pino-structured-log.writer.d.ts +5 -0
- package/observability/providers/src/lib/pino/pino-structured-log.writer.d.ts.map +1 -0
- package/observability/providers/src/lib/pino/pino-structured-log.writer.js +16 -0
- package/observability/providers/src/lib/pino/pino-structured-log.writer.js.map +1 -0
- package/observability/providers/src/lib/pino/pino.factory.d.ts.map +1 -1
- package/observability/providers/src/lib/pino/pino.factory.js +69 -25
- package/observability/providers/src/lib/pino/pino.factory.js.map +1 -1
- package/package.json +2 -2
- package/realtime/src/lib/auth/auth-security-alert-realtime.listener.d.ts.map +1 -1
- package/realtime/src/lib/auth/auth-security-alert-realtime.listener.js +19 -0
- package/realtime/src/lib/auth/auth-security-alert-realtime.listener.js.map +1 -1
- package/realtime/src/lib/topics/realtime-topics.d.ts +1 -0
- package/realtime/src/lib/topics/realtime-topics.d.ts.map +1 -1
- package/realtime/src/lib/topics/realtime-topics.js +2 -1
- package/realtime/src/lib/topics/realtime-topics.js.map +1 -1
- package/realtime/src/lib/types/realtime.types.d.ts +13 -1
- package/realtime/src/lib/types/realtime.types.d.ts.map +1 -1
- package/realtime/src/lib/types/realtime.types.js.map +1 -1
- package/report/src/lib/report-definition.registry.d.ts +11 -15
- package/report/src/lib/report-definition.registry.d.ts.map +1 -1
- package/report/src/lib/report-definition.registry.js +25 -101
- package/report/src/lib/report-definition.registry.js.map +1 -1
- package/search/elasticsearch/src/lib/elasticsearch-quick-search.backend.d.ts.map +1 -1
- package/search/elasticsearch/src/lib/elasticsearch-quick-search.backend.js +56 -18
- package/search/elasticsearch/src/lib/elasticsearch-quick-search.backend.js.map +1 -1
- package/search/providers/src/lib/quick-search-index.service.d.ts.map +1 -1
- package/search/providers/src/lib/quick-search-index.service.js +29 -14
- package/search/providers/src/lib/quick-search-index.service.js.map +1 -1
- package/search/providers/src/lib/quick-search-provider.module.d.ts.map +1 -1
- package/search/providers/src/lib/quick-search-provider.module.js +19 -12
- package/search/providers/src/lib/quick-search-provider.module.js.map +1 -1
- package/search/providers/src/lib/quick-search-reindex.service.d.ts +5 -1
- package/search/providers/src/lib/quick-search-reindex.service.d.ts.map +1 -1
- package/search/providers/src/lib/quick-search-reindex.service.js +23 -9
- package/search/providers/src/lib/quick-search-reindex.service.js.map +1 -1
- package/security/src/index.d.ts +5 -0
- package/security/src/index.d.ts.map +1 -1
- package/security/src/index.js +5 -0
- package/security/src/index.js.map +1 -1
- package/security/src/lib/auth/auth-runtime.handlers.d.ts +18 -4
- package/security/src/lib/auth/auth-runtime.handlers.d.ts.map +1 -1
- package/security/src/lib/auth/auth-runtime.handlers.js +60 -1
- package/security/src/lib/auth/auth-runtime.handlers.js.map +1 -1
- package/security/src/lib/auth/auth-two-factor-gate.d.ts +38 -0
- package/security/src/lib/auth/auth-two-factor-gate.d.ts.map +1 -0
- package/security/src/lib/auth/auth-two-factor-gate.js +33 -0
- package/security/src/lib/auth/auth-two-factor-gate.js.map +1 -0
- package/security/src/lib/auth/auth.events.d.ts +1 -1
- package/security/src/lib/auth/auth.events.d.ts.map +1 -1
- package/security/src/lib/auth/auth.events.js.map +1 -1
- package/security/src/lib/auth/auth.service.d.ts.map +1 -1
- package/security/src/lib/auth/auth.service.js +2 -0
- package/security/src/lib/auth/auth.service.js.map +1 -1
- package/security/src/lib/auth/auth.types.d.ts +6 -0
- package/security/src/lib/auth/auth.types.d.ts.map +1 -1
- package/security/src/lib/auth/auth.types.js.map +1 -1
- package/security/src/lib/auth/device-session.service.d.ts +14 -3
- package/security/src/lib/auth/device-session.service.d.ts.map +1 -1
- package/security/src/lib/auth/device-session.service.js +129 -20
- package/security/src/lib/auth/device-session.service.js.map +1 -1
- package/security/src/lib/auth/token.service.d.ts.map +1 -1
- package/security/src/lib/auth/token.service.js +6 -2
- package/security/src/lib/auth/token.service.js.map +1 -1
- package/security/src/lib/entity-storage/entity-access-policy.loader.d.ts.map +1 -1
- package/security/src/lib/entity-storage/entity-access-policy.loader.js +3 -0
- package/security/src/lib/entity-storage/entity-access-policy.loader.js.map +1 -1
- package/security/src/lib/entity-storage/visibility-filter.middleware.d.ts +1 -2
- package/security/src/lib/entity-storage/visibility-filter.middleware.d.ts.map +1 -1
- package/security/src/lib/entity-storage/visibility-filter.middleware.js +2 -75
- package/security/src/lib/entity-storage/visibility-filter.middleware.js.map +1 -1
- package/security/src/lib/oauth/oidc-provider-engine.service.js +3 -3
- package/security/src/lib/oauth/oidc-provider-engine.service.js.map +1 -1
- package/security/src/lib/security.module.d.ts.map +1 -1
- package/security/src/lib/security.module.js +1 -0
- package/security/src/lib/security.module.js.map +1 -1
- package/workflow/src/index.d.ts +2 -0
- package/workflow/src/index.d.ts.map +1 -1
- package/workflow/src/index.js +8 -0
- package/workflow/src/index.js.map +1 -1
- package/workflow/src/lib/activities/workflow-request-session.adapter.d.ts +10 -4
- package/workflow/src/lib/activities/workflow-request-session.adapter.d.ts.map +1 -1
- package/workflow/src/lib/activities/workflow-request-session.adapter.js +11 -4
- package/workflow/src/lib/activities/workflow-request-session.adapter.js.map +1 -1
- package/workflow/src/lib/jobs/workflow-job-dispatcher.service.d.ts +10 -1
- package/workflow/src/lib/jobs/workflow-job-dispatcher.service.d.ts.map +1 -1
- package/workflow/src/lib/jobs/workflow-job-dispatcher.service.js +74 -9
- package/workflow/src/lib/jobs/workflow-job-dispatcher.service.js.map +1 -1
- package/workflow/src/lib/jobs/workflow-maintenance.scheduler.d.ts +3 -3
- package/workflow/src/lib/jobs/workflow-maintenance.scheduler.d.ts.map +1 -1
- package/workflow/src/lib/jobs/workflow-maintenance.scheduler.js +6 -4
- package/workflow/src/lib/jobs/workflow-maintenance.scheduler.js.map +1 -1
- package/workflow/src/lib/jobs/workflow-start-idempotency.d.ts +14 -0
- package/workflow/src/lib/jobs/workflow-start-idempotency.d.ts.map +1 -0
- package/workflow/src/lib/jobs/workflow-start-idempotency.js +33 -0
- package/workflow/src/lib/jobs/workflow-start-idempotency.js.map +1 -0
- package/workflow/src/lib/middlewares/workflow-trigger-guard.d.ts +6 -0
- package/workflow/src/lib/middlewares/workflow-trigger-guard.d.ts.map +1 -1
- package/workflow/src/lib/middlewares/workflow-trigger-guard.js +14 -0
- package/workflow/src/lib/middlewares/workflow-trigger-guard.js.map +1 -1
- package/workflow/src/lib/middlewares/workflow-trigger.middleware.d.ts.map +1 -1
- package/workflow/src/lib/middlewares/workflow-trigger.middleware.js +2 -1
- package/workflow/src/lib/middlewares/workflow-trigger.middleware.js.map +1 -1
- package/workflow/src/lib/runtime/jql-workflow-execution-store.service.d.ts +6 -1
- package/workflow/src/lib/runtime/jql-workflow-execution-store.service.d.ts.map +1 -1
- package/workflow/src/lib/runtime/jql-workflow-execution-store.service.js +15 -1
- package/workflow/src/lib/runtime/jql-workflow-execution-store.service.js.map +1 -1
- package/workflow/src/lib/runtime/sync-task-current-assignee.util.d.ts +4 -2
- package/workflow/src/lib/runtime/sync-task-current-assignee.util.d.ts.map +1 -1
- package/workflow/src/lib/runtime/sync-task-current-assignee.util.js +47 -4
- package/workflow/src/lib/runtime/sync-task-current-assignee.util.js.map +1 -1
- package/workflow/src/lib/runtime/workflow-engine.service.d.ts +19 -0
- package/workflow/src/lib/runtime/workflow-engine.service.d.ts.map +1 -1
- package/workflow/src/lib/runtime/workflow-engine.service.js +1070 -795
- package/workflow/src/lib/runtime/workflow-engine.service.js.map +1 -1
- package/workflow/src/lib/runtime/workflow-execution-store.service.d.ts +12 -2
- package/workflow/src/lib/runtime/workflow-execution-store.service.d.ts.map +1 -1
- package/workflow/src/lib/runtime/workflow-execution-store.service.js +11 -1
- package/workflow/src/lib/runtime/workflow-execution-store.service.js.map +1 -1
- package/jql/seed/src/lib/jql-seed-analyze.command.d.ts +0 -9
- package/jql/seed/src/lib/jql-seed-analyze.command.d.ts.map +0 -1
- package/jql/seed/src/lib/jql-seed-analyze.command.js +0 -40
- package/jql/seed/src/lib/jql-seed-analyze.command.js.map +0 -1
- package/jql/seed/src/lib/jql-seed-apply.command.d.ts +0 -9
- package/jql/seed/src/lib/jql-seed-apply.command.d.ts.map +0 -1
- package/jql/seed/src/lib/jql-seed-apply.command.js +0 -53
- package/jql/seed/src/lib/jql-seed-apply.command.js.map +0 -1
- package/jql/seed/src/lib/jql-seed-command.bootstrap.d.ts +0 -9
- package/jql/seed/src/lib/jql-seed-command.bootstrap.d.ts.map +0 -1
- package/jql/seed/src/lib/jql-seed-command.bootstrap.js +0 -29
- package/jql/seed/src/lib/jql-seed-command.bootstrap.js.map +0 -1
- package/jql/seed/src/lib/jql-seed-command.registrar.d.ts +0 -12
- package/jql/seed/src/lib/jql-seed-command.registrar.d.ts.map +0 -1
- package/jql/seed/src/lib/jql-seed-command.registrar.js +0 -29
- package/jql/seed/src/lib/jql-seed-command.registrar.js.map +0 -1
- package/jql/seed/src/lib/jql-seed-list-catalog.command.d.ts +0 -9
- package/jql/seed/src/lib/jql-seed-list-catalog.command.d.ts.map +0 -1
- package/jql/seed/src/lib/jql-seed-list-catalog.command.js +0 -30
- package/jql/seed/src/lib/jql-seed-list-catalog.command.js.map +0 -1
- package/jql/seed/src/lib/jql-seed-run.command.d.ts +0 -9
- package/jql/seed/src/lib/jql-seed-run.command.d.ts.map +0 -1
- package/jql/seed/src/lib/jql-seed-run.command.js +0 -43
- package/jql/seed/src/lib/jql-seed-run.command.js.map +0 -1
- package/observability/abstract/src/lib/logger/axp-logger.interface.d.ts +0 -11
- package/observability/abstract/src/lib/logger/axp-logger.interface.d.ts.map +0 -1
- package/observability/abstract/src/lib/logger/axp-logger.interface.js +0 -5
- package/observability/abstract/src/lib/logger/axp-logger.interface.js.map +0 -1
- package/observability/abstract/src/lib/metrics/axp-metrics.interface.d.ts +0 -19
- package/observability/abstract/src/lib/metrics/axp-metrics.interface.d.ts.map +0 -1
- package/observability/abstract/src/lib/metrics/axp-metrics.interface.js +0 -5
- package/observability/abstract/src/lib/metrics/axp-metrics.interface.js.map +0 -1
- package/observability/abstract/src/lib/noop/noop-logger.adapter.d.ts +0 -11
- package/observability/abstract/src/lib/noop/noop-logger.adapter.d.ts.map +0 -1
- package/observability/abstract/src/lib/noop/noop-logger.adapter.js +0 -30
- package/observability/abstract/src/lib/noop/noop-logger.adapter.js.map +0 -1
- package/observability/abstract/src/lib/noop/noop-metrics.adapter.d.ts +0 -16
- package/observability/abstract/src/lib/noop/noop-metrics.adapter.d.ts.map +0 -1
- package/observability/abstract/src/lib/noop/noop-metrics.adapter.js +0 -33
- package/observability/abstract/src/lib/noop/noop-metrics.adapter.js.map +0 -1
- package/observability/abstract/src/lib/noop/noop-tracer.adapter.d.ts +0 -7
- package/observability/abstract/src/lib/noop/noop-tracer.adapter.d.ts.map +0 -1
- package/observability/abstract/src/lib/noop/noop-tracer.adapter.js +0 -32
- package/observability/abstract/src/lib/noop/noop-tracer.adapter.js.map +0 -1
- package/observability/abstract/src/lib/tracer/axp-tracer.interface.d.ts +0 -17
- package/observability/abstract/src/lib/tracer/axp-tracer.interface.d.ts.map +0 -1
- package/observability/abstract/src/lib/tracer/axp-tracer.interface.js +0 -5
- package/observability/abstract/src/lib/tracer/axp-tracer.interface.js.map +0 -1
- package/observability/providers/src/lib/observability.service.d.ts +0 -9
- package/observability/providers/src/lib/observability.service.d.ts.map +0 -1
- package/observability/providers/src/lib/observability.service.js +0 -26
- package/observability/providers/src/lib/observability.service.js.map +0 -1
- package/observability/providers/src/lib/otel/otel-metrics.adapter.d.ts +0 -11
- package/observability/providers/src/lib/otel/otel-metrics.adapter.d.ts.map +0 -1
- package/observability/providers/src/lib/otel/otel-metrics.adapter.js +0 -83
- package/observability/providers/src/lib/otel/otel-metrics.adapter.js.map +0 -1
- package/observability/providers/src/lib/otel/otel-tracer.adapter.d.ts +0 -8
- package/observability/providers/src/lib/otel/otel-tracer.adapter.d.ts.map +0 -1
- package/observability/providers/src/lib/otel/otel-tracer.adapter.js +0 -80
- package/observability/providers/src/lib/otel/otel-tracer.adapter.js.map +0 -1
- package/observability/providers/src/lib/pino/pino-logger.adapter.d.ts +0 -19
- package/observability/providers/src/lib/pino/pino-logger.adapter.d.ts.map +0 -1
- package/observability/providers/src/lib/pino/pino-logger.adapter.js +0 -45
- package/observability/providers/src/lib/pino/pino-logger.adapter.js.map +0 -1
|
@@ -16,6 +16,7 @@ const workflow_expression_scope_service_1 = require("./workflow-expression-scope
|
|
|
16
16
|
const workflow_execution_store_service_1 = require("./workflow-execution-store.service");
|
|
17
17
|
const entity_workflow_info_sync_1 = require("./entity-workflow-info.sync");
|
|
18
18
|
const sync_task_current_assignee_util_1 = require("./sync-task-current-assignee.util");
|
|
19
|
+
const workflow_trigger_guard_1 = require("../middlewares/workflow-trigger-guard");
|
|
19
20
|
const core_1 = require("@acorex-platform/framework-server/core");
|
|
20
21
|
/** Task context key for composed human-task titles (workflow-management). */
|
|
21
22
|
const AXP_WORKFLOW_TASK_CONTEXT_TITLE_PARTS_KEY = '_taskTitleParts';
|
|
@@ -64,6 +65,21 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
64
65
|
}
|
|
65
66
|
return { id: userId, title: userId, name: userId };
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Tenant/app stamp for engine upserts. `insertOneAsEngine` skips visibility
|
|
70
|
+
* create-stamp, so instances would otherwise be invisible to tenant queries.
|
|
71
|
+
*/
|
|
72
|
+
getSessionScopeInfo() {
|
|
73
|
+
const tenantId = this.sessionService?.tenantId?.trim();
|
|
74
|
+
const applicationId = this.sessionService?.applicationId?.trim();
|
|
75
|
+
if (!tenantId && !applicationId) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...(tenantId ? { tenantId } : {}),
|
|
80
|
+
...(applicationId ? { applicationId } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
67
83
|
//#endregion
|
|
68
84
|
//#region ---- Definition Loading ----
|
|
69
85
|
/**
|
|
@@ -316,176 +332,244 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
316
332
|
entityRefId: entityRefId || null,
|
|
317
333
|
entityRefType: entityRefType || null,
|
|
318
334
|
};
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
taskType === 'human-task')
|
|
371
|
-
? executionMode === 'backend'
|
|
335
|
+
let stored = false;
|
|
336
|
+
try {
|
|
337
|
+
return await (0, workflow_trigger_guard_1.runInsideWorkflowEngine)(() => this.store.runWithoutPersist(async () => {
|
|
338
|
+
await this.store.addInstance(instance);
|
|
339
|
+
stored = true;
|
|
340
|
+
console.log(`[BackendWorkflowEngineService] 💾 Stored workflow instance:`, {
|
|
341
|
+
id: instance.id,
|
|
342
|
+
definitionId: instance.definitionId,
|
|
343
|
+
name: instance.name,
|
|
344
|
+
correlationId: instance.correlationId,
|
|
345
|
+
entityRefId: instance.entityRefId,
|
|
346
|
+
entityRefType: instance.entityRefType,
|
|
347
|
+
status: instance.status,
|
|
348
|
+
subStatus: instance.subStatus,
|
|
349
|
+
});
|
|
350
|
+
// Instance row is inserted once after the step loop, not on every activity.
|
|
351
|
+
// Convert to instance state for response (async - needs bookmark lookup)
|
|
352
|
+
let state = await this.instanceToInstanceState(instance);
|
|
353
|
+
// 🎯 KEY LOGIC: Execute all backend activities until we reach a frontend activity
|
|
354
|
+
let currentActivity = firstActivity;
|
|
355
|
+
let executionCount = 0;
|
|
356
|
+
const maxExecutions = 500; // Prevent infinite loops
|
|
357
|
+
// Only last activity output is kept; each step overwrites it. Expression scope uses outputs = lastActivityOutput.
|
|
358
|
+
let lastActivityOutput = state.lastActivityOutput;
|
|
359
|
+
while (currentActivity && executionCount < maxExecutions) {
|
|
360
|
+
executionCount++;
|
|
361
|
+
const activityType = currentActivity.name;
|
|
362
|
+
if (request.workflowId === 'documentReview') {
|
|
363
|
+
console.log('[WFT-ENG] documentReview step', executionCount, 'activityId=', currentActivity.id, 'type=', activityType);
|
|
364
|
+
}
|
|
365
|
+
// Determine execution mode (async)
|
|
366
|
+
const executionMode = await this.determineExecutionMode(activityType, currentActivity);
|
|
367
|
+
// Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
|
|
368
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
369
|
+
let taskType = currentActivity.metadata?.['taskType'] ??
|
|
370
|
+
activityDef?.taskType ??
|
|
371
|
+
'ui-activity';
|
|
372
|
+
// Defensive: human-task and cartable must suspend for task board
|
|
373
|
+
if (this.isTaskBoardSuspendGraphActivity(activityType)) {
|
|
374
|
+
if (activityDef?.taskType === 'human-task') {
|
|
375
|
+
taskType = 'human-task';
|
|
376
|
+
}
|
|
377
|
+
if (executionMode === 'backend') {
|
|
378
|
+
console.warn(`[BackendWorkflowEngineService] ⚠️ ${activityType} had executionMode=backend (definition or cache). Forcing frontend so workflow suspends at this step.`, {
|
|
379
|
+
activityId: currentActivity.id,
|
|
380
|
+
definitionExecutionMode: activityDef?.executionMode,
|
|
381
|
+
definitionTaskType: activityDef?.taskType,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const effectiveExecutionMode = taskType === 'human-task' && executionMode === 'backend'
|
|
372
386
|
? 'frontend'
|
|
373
|
-
:
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
387
|
+
: this.isTaskBoardSuspendGraphActivity(activityType) &&
|
|
388
|
+
(activityDef?.taskType === 'human-task' ||
|
|
389
|
+
taskType === 'human-task')
|
|
390
|
+
? executionMode === 'backend'
|
|
391
|
+
? 'frontend'
|
|
392
|
+
: executionMode
|
|
393
|
+
: executionMode;
|
|
394
|
+
// If frontend activity → stop and return as pendingTask
|
|
395
|
+
if (effectiveExecutionMode === 'frontend' ||
|
|
396
|
+
effectiveExecutionMode === 'both') {
|
|
397
|
+
const pendingTask = await this.createTaskFromActivity(currentActivity, state);
|
|
398
|
+
// Use local taskType (may be forced to human-task for workflow-activity:human-task) so bookmark and UI see correct type
|
|
399
|
+
const resolvedTaskTypeForBookmark = taskType === 'human-task' || pendingTask.taskType === 'human-task'
|
|
400
|
+
? 'human-task'
|
|
401
|
+
: pendingTask.taskType;
|
|
402
|
+
// Extract assignment and task metadata from activity inputs (for human-task activities)
|
|
403
|
+
let bookmarkPayload = {
|
|
404
|
+
activityType,
|
|
405
|
+
activityName: currentActivity.name,
|
|
406
|
+
taskToken: pendingTask.taskToken, // Store taskToken for resume
|
|
407
|
+
taskType: resolvedTaskTypeForBookmark,
|
|
408
|
+
};
|
|
409
|
+
// For human-task activities, extract assignment and task metadata from inputs
|
|
410
|
+
if (resolvedTaskTypeForBookmark === 'human-task' && pendingTask.input) {
|
|
411
|
+
const inputs = pendingTask.input;
|
|
412
|
+
// Assignment metadata (normalize to array so task board filter works; expression may return single id)
|
|
413
|
+
bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
|
|
414
|
+
bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
|
|
415
|
+
bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
|
|
416
|
+
// Task metadata
|
|
417
|
+
bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
|
|
418
|
+
bookmarkPayload['description'] = inputs['description'] || null;
|
|
419
|
+
bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
|
|
420
|
+
bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
|
|
421
|
+
// UI configuration
|
|
422
|
+
bookmarkPayload['layout'] = inputs['layout'] || null;
|
|
423
|
+
bookmarkPayload['actions'] = inputs['actions'] || null;
|
|
424
|
+
bookmarkPayload['context'] = inputs['context'] || {};
|
|
425
|
+
}
|
|
426
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
|
|
427
|
+
bookmarkPayload['assignedUserIds'] = [];
|
|
428
|
+
}
|
|
429
|
+
if (resolvedTaskTypeForBookmark === 'human-task') {
|
|
430
|
+
const htInputs = pendingTask.input;
|
|
431
|
+
this.logHumanTaskBookmarkAssignmentTrace({
|
|
432
|
+
phase: 'engine-start',
|
|
433
|
+
instanceId,
|
|
434
|
+
workflowDefinitionName: workflow.name,
|
|
435
|
+
activityId: currentActivity.id,
|
|
436
|
+
activityType,
|
|
437
|
+
rawAssignedUserIds: htInputs?.['assignedUserIds'],
|
|
438
|
+
normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
|
|
439
|
+
variables: state.variables,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
const bookmarkId = await this.createBookmark(instanceId, currentActivity.id, 'user-task', bookmarkPayload);
|
|
443
|
+
const isTaskBoardSuspend = this.isTaskBoardSuspendGraphActivity(activityType);
|
|
444
|
+
if (isTaskBoardSuspend) {
|
|
445
|
+
console.log('[WFT-ENG] task-board bookmark created instanceId=', instanceId, 'activityId=', currentActivity.id, 'bookmarkId=', bookmarkId, 'type=', activityType);
|
|
446
|
+
}
|
|
447
|
+
const newStatus = isTaskBoardSuspend ? 'suspended' : 'running';
|
|
448
|
+
state = {
|
|
449
|
+
...state,
|
|
450
|
+
lastActivityOutput,
|
|
451
|
+
status: newStatus,
|
|
452
|
+
currentStepId: currentActivity.id,
|
|
453
|
+
};
|
|
454
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
455
|
+
return {
|
|
456
|
+
instanceId: instanceId,
|
|
457
|
+
state,
|
|
458
|
+
pendingTask,
|
|
459
|
+
lastActivityOutput,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
const parallelWrites = await this.runIndependentEntityWriteBatch({
|
|
463
|
+
workflow,
|
|
464
|
+
connections,
|
|
465
|
+
graph,
|
|
414
466
|
instanceId,
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
rawAssignedUserIds: htInputs?.['assignedUserIds'],
|
|
419
|
-
normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
|
|
420
|
-
variables: state.variables,
|
|
467
|
+
activity: currentActivity,
|
|
468
|
+
state,
|
|
469
|
+
lastActivityOutput,
|
|
421
470
|
});
|
|
471
|
+
if (parallelWrites) {
|
|
472
|
+
state = parallelWrites.state;
|
|
473
|
+
lastActivityOutput = parallelWrites.lastActivityOutput;
|
|
474
|
+
executionCount += parallelWrites.executedCount - 1;
|
|
475
|
+
if (!parallelWrites.nextActivity) {
|
|
476
|
+
return {
|
|
477
|
+
instanceId,
|
|
478
|
+
state,
|
|
479
|
+
pendingTask: null,
|
|
480
|
+
lastActivityOutput,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
currentActivity = parallelWrites.nextActivity;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
// Backend activity → execute it
|
|
487
|
+
const lastOutputBeforeBackend = lastActivityOutput;
|
|
488
|
+
const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutput);
|
|
489
|
+
lastActivityOutput = executionResult.output;
|
|
490
|
+
await this.maybeLinkCreatedEntityRecord(instanceId, currentActivity, state, lastOutputBeforeBackend, executionResult.output);
|
|
491
|
+
// set-variable writes workflow variables directly into output map (no outputToVariables).
|
|
492
|
+
const isSetVariableRun = currentActivity.name === 'workflow-activity:set-variable';
|
|
493
|
+
const variableUpdates = isSetVariableRun
|
|
494
|
+
? executionResult.output
|
|
495
|
+
: this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
|
|
496
|
+
const outcome = Object.keys(executionResult.outcomes)[0] || 'Done';
|
|
497
|
+
const sessionUser = this.getSessionUser();
|
|
498
|
+
const backendStepAudit = {
|
|
499
|
+
[`${currentActivity.id}_outcome`]: outcome,
|
|
500
|
+
[`${currentActivity.id}_activityOutput`]: executionResult.output,
|
|
501
|
+
};
|
|
502
|
+
if (sessionUser?.id) {
|
|
503
|
+
backendStepAudit[`${currentActivity.id}_completedByUserId`] =
|
|
504
|
+
sessionUser.id;
|
|
505
|
+
backendStepAudit[`${currentActivity.id}_completedByLabel`] =
|
|
506
|
+
sessionUser.title || sessionUser.name || sessionUser.id;
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
backendStepAudit[`${currentActivity.id}_completedByLabel`] = 'System';
|
|
510
|
+
}
|
|
511
|
+
state = {
|
|
512
|
+
...state,
|
|
513
|
+
variables: {
|
|
514
|
+
...state.variables,
|
|
515
|
+
...variableUpdates,
|
|
516
|
+
...backendStepAudit,
|
|
517
|
+
},
|
|
518
|
+
lastActivityOutput,
|
|
519
|
+
currentStepId: currentActivity.id,
|
|
520
|
+
lastUpdated: new Date(),
|
|
521
|
+
};
|
|
522
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
523
|
+
// Find next activity based on outcome
|
|
524
|
+
const nextConnection = this.findWorkflowConnection(connections, currentActivity.id, outcome);
|
|
525
|
+
if (!nextConnection) {
|
|
526
|
+
// No next activity → workflow completed
|
|
527
|
+
state = {
|
|
528
|
+
...state,
|
|
529
|
+
status: 'completed',
|
|
530
|
+
currentStepId: undefined,
|
|
531
|
+
output: state.variables,
|
|
532
|
+
};
|
|
533
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
534
|
+
return {
|
|
535
|
+
instanceId: instanceId,
|
|
536
|
+
state,
|
|
537
|
+
pendingTask: null,
|
|
538
|
+
lastActivityOutput,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
// Find next activity
|
|
542
|
+
const nextActivity = this.findActivityInGraph(graph, nextConnection.target.activtyName);
|
|
543
|
+
if (!nextActivity) {
|
|
544
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
|
|
545
|
+
state = {
|
|
546
|
+
...state,
|
|
547
|
+
status: 'error',
|
|
548
|
+
currentStepId: undefined,
|
|
549
|
+
};
|
|
550
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
551
|
+
throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
|
|
552
|
+
}
|
|
553
|
+
currentActivity = nextActivity;
|
|
422
554
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
555
|
+
// If we exit loop due to max executions, mark as error
|
|
556
|
+
if (executionCount >= maxExecutions) {
|
|
557
|
+
console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
|
|
558
|
+
state = {
|
|
559
|
+
...state,
|
|
560
|
+
status: 'error',
|
|
561
|
+
currentStepId: undefined,
|
|
562
|
+
};
|
|
563
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
564
|
+
throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
|
|
427
565
|
}
|
|
428
|
-
|
|
429
|
-
state = {
|
|
430
|
-
...state,
|
|
431
|
-
lastActivityOutput,
|
|
432
|
-
status: newStatus,
|
|
433
|
-
currentStepId: currentActivity.id,
|
|
434
|
-
};
|
|
435
|
-
await this.updateInstanceFromState(instanceId, state);
|
|
436
|
-
return {
|
|
437
|
-
instanceId: instanceId,
|
|
438
|
-
state,
|
|
439
|
-
pendingTask,
|
|
440
|
-
lastActivityOutput,
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
// Backend activity → execute it
|
|
444
|
-
const lastOutputBeforeBackend = lastActivityOutput;
|
|
445
|
-
const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutput);
|
|
446
|
-
lastActivityOutput = executionResult.output;
|
|
447
|
-
await this.maybeLinkCreatedEntityRecord(instanceId, currentActivity, state, lastOutputBeforeBackend, executionResult.output);
|
|
448
|
-
// set-variable writes workflow variables directly into output map (no outputToVariables).
|
|
449
|
-
const isSetVariableRun = currentActivity.name === 'workflow-activity:set-variable';
|
|
450
|
-
const variableUpdates = isSetVariableRun
|
|
451
|
-
? executionResult.output
|
|
452
|
-
: this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
|
|
453
|
-
const outcome = Object.keys(executionResult.outcomes)[0] || 'Done';
|
|
454
|
-
const sessionUser = this.getSessionUser();
|
|
455
|
-
const backendStepAudit = {
|
|
456
|
-
[`${currentActivity.id}_outcome`]: outcome,
|
|
457
|
-
[`${currentActivity.id}_activityOutput`]: executionResult.output,
|
|
458
|
-
};
|
|
459
|
-
if (sessionUser?.id) {
|
|
460
|
-
backendStepAudit[`${currentActivity.id}_completedByUserId`] =
|
|
461
|
-
sessionUser.id;
|
|
462
|
-
backendStepAudit[`${currentActivity.id}_completedByLabel`] =
|
|
463
|
-
sessionUser.title || sessionUser.name || sessionUser.id;
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
466
|
-
backendStepAudit[`${currentActivity.id}_completedByLabel`] = 'System';
|
|
467
|
-
}
|
|
468
|
-
state = {
|
|
469
|
-
...state,
|
|
470
|
-
variables: {
|
|
471
|
-
...state.variables,
|
|
472
|
-
...variableUpdates,
|
|
473
|
-
...backendStepAudit,
|
|
474
|
-
},
|
|
475
|
-
lastActivityOutput,
|
|
476
|
-
currentStepId: currentActivity.id,
|
|
477
|
-
lastUpdated: new Date(),
|
|
478
|
-
};
|
|
479
|
-
await this.updateInstanceFromState(instanceId, state);
|
|
480
|
-
// Find next activity based on outcome
|
|
481
|
-
const nextConnection = this.findWorkflowConnection(connections, currentActivity.id, outcome);
|
|
482
|
-
if (!nextConnection) {
|
|
483
|
-
// No next activity → workflow completed
|
|
566
|
+
// If we exit loop without frontend activity, workflow is complete
|
|
484
567
|
state = {
|
|
485
568
|
...state,
|
|
486
569
|
status: 'completed',
|
|
487
570
|
currentStepId: undefined,
|
|
488
571
|
output: state.variables,
|
|
572
|
+
lastActivityOutput,
|
|
489
573
|
};
|
|
490
574
|
await this.updateInstanceFromState(instanceId, state);
|
|
491
575
|
return {
|
|
@@ -494,47 +578,16 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
494
578
|
pendingTask: null,
|
|
495
579
|
lastActivityOutput,
|
|
496
580
|
};
|
|
497
|
-
}
|
|
498
|
-
// Find next activity
|
|
499
|
-
const nextActivity = this.findActivityInGraph(graph, nextConnection.target.activtyName);
|
|
500
|
-
if (!nextActivity) {
|
|
501
|
-
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
|
|
502
|
-
state = {
|
|
503
|
-
...state,
|
|
504
|
-
status: 'error',
|
|
505
|
-
currentStepId: undefined,
|
|
506
|
-
};
|
|
507
|
-
await this.updateInstanceFromState(instanceId, state);
|
|
508
|
-
throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
|
|
509
|
-
}
|
|
510
|
-
currentActivity = nextActivity;
|
|
581
|
+
}));
|
|
511
582
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
};
|
|
520
|
-
await this.updateInstanceFromState(instanceId, state);
|
|
521
|
-
throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
|
|
583
|
+
finally {
|
|
584
|
+
if (stored) {
|
|
585
|
+
const current = await this.store.getInstance(instanceId);
|
|
586
|
+
if (current) {
|
|
587
|
+
await this.writeInstanceProjection(current, workflow, true);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
522
590
|
}
|
|
523
|
-
// If we exit loop without frontend activity, workflow is complete
|
|
524
|
-
state = {
|
|
525
|
-
...state,
|
|
526
|
-
status: 'completed',
|
|
527
|
-
currentStepId: undefined,
|
|
528
|
-
output: state.variables,
|
|
529
|
-
lastActivityOutput,
|
|
530
|
-
};
|
|
531
|
-
await this.updateInstanceFromState(instanceId, state);
|
|
532
|
-
return {
|
|
533
|
-
instanceId: instanceId,
|
|
534
|
-
state,
|
|
535
|
-
pendingTask: null,
|
|
536
|
-
lastActivityOutput,
|
|
537
|
-
};
|
|
538
591
|
}
|
|
539
592
|
/**
|
|
540
593
|
* Terminates a non-finished workflow instance (e.g. superseded lifecycle run).
|
|
@@ -798,286 +851,357 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
798
851
|
* 7. Returns either pendingTask (with new secure token) or completed status
|
|
799
852
|
*/
|
|
800
853
|
async resume(request) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
// Validate task token (secure validation). If token missing/expired, allow resume when instance is suspended and bookmark for this step exists (task board tasks may wait days).
|
|
816
|
-
const tokenValid = request.taskToken &&
|
|
817
|
-
this.validateTaskToken(request.taskToken, request.instanceId, request.stepId);
|
|
818
|
-
if (!tokenValid) {
|
|
819
|
-
const allowedByBookmark = await this.canResumeByBookmark(request.instanceId, request.stepId);
|
|
820
|
-
if (!allowedByBookmark) {
|
|
821
|
-
throw new Error(request.taskToken
|
|
822
|
-
? 'Invalid or expired task token'
|
|
823
|
-
: 'Missing taskToken for resume operation');
|
|
824
|
-
}
|
|
825
|
-
console.log(`[BackendWorkflowEngineService] ✅ Resume allowed by bookmark (token expired/missing): ${request.instanceId}:${request.stepId}`);
|
|
826
|
-
}
|
|
827
|
-
// Verify stepId matches current step
|
|
828
|
-
if (state.currentStepId !== request.stepId) {
|
|
829
|
-
throw new Error(`Step mismatch: expected '${state.currentStepId}', got '${request.stepId}'`);
|
|
830
|
-
}
|
|
831
|
-
// Enforce claim / assignee for task-board bookmarks
|
|
832
|
-
const resumeBookmarks = await this.store.getBookmarksByInstanceAndActivity(request.instanceId, request.stepId, true);
|
|
833
|
-
const resumePayloadSource = resumeBookmarks[0];
|
|
834
|
-
const resumePayload = resumePayloadSource
|
|
835
|
-
? this.parseBookmarkPayloadJson(resumePayloadSource.payloadJson)
|
|
836
|
-
: null;
|
|
837
|
-
if (resumePayload) {
|
|
838
|
-
const payloadActivityType = String(resumePayload['activityType'] ?? '');
|
|
839
|
-
const assigned = this.normalizeUserIds(resumePayload['assignedUserIds']) ?? [];
|
|
840
|
-
const candidates = this.normalizeUserIds(resumePayload['candidateUserIds']) ?? [];
|
|
841
|
-
const isCartablePayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable;
|
|
842
|
-
const isPooledHumanPayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask &&
|
|
843
|
-
assigned.length === 0 &&
|
|
844
|
-
candidates.length > 0;
|
|
845
|
-
if (isCartablePayload || isPooledHumanPayload) {
|
|
846
|
-
if (assigned.length === 0) {
|
|
847
|
-
throw new workflow_engine_errors_1.AXPWorkflowEngineError('Claim the task before completing it', workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.CLAIM_REQUIRED);
|
|
854
|
+
let dirty = false;
|
|
855
|
+
let workflowForFlush = null;
|
|
856
|
+
try {
|
|
857
|
+
return await (0, workflow_trigger_guard_1.runInsideWorkflowEngine)(() => this.store.runWithoutPersist(async () => {
|
|
858
|
+
console.log(`[BackendWorkflowEngineService] 🔄 Resuming workflow: ${request.instanceId}`, {
|
|
859
|
+
stepId: request.stepId,
|
|
860
|
+
outcome: request.outcome,
|
|
861
|
+
});
|
|
862
|
+
// Get current state
|
|
863
|
+
let state = await this.getState({
|
|
864
|
+
instanceId: request.instanceId,
|
|
865
|
+
});
|
|
866
|
+
if (!state) {
|
|
867
|
+
throw new Error(`Workflow instance not found: ${request.instanceId}`);
|
|
848
868
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const uid = this.getSessionUser()?.id;
|
|
852
|
-
if (uid && !assigned.includes(String(uid))) {
|
|
853
|
-
throw new workflow_engine_errors_1.AXPWorkflowEngineError('Only the assigned user can complete this task', workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.TASK_NOT_ASSIGNEE, { assignedUserIds: assigned });
|
|
869
|
+
if (state.status !== 'suspended') {
|
|
870
|
+
throw new Error(`Cannot resume workflow with status: ${state.status}. Expected: suspended`);
|
|
854
871
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
output:
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
}
|
|
927
|
-
// 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
|
|
928
|
-
let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
|
|
929
|
-
let lastActivityOutputResume = lastActivityOutput;
|
|
930
|
-
let executionCount = 0;
|
|
931
|
-
const maxExecutions = 500;
|
|
932
|
-
if (!currentActivity) {
|
|
933
|
-
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
|
|
934
|
-
state = {
|
|
935
|
-
...state,
|
|
936
|
-
status: 'error',
|
|
937
|
-
currentStepId: undefined,
|
|
938
|
-
};
|
|
939
|
-
await this.updateInstanceFromState(request.instanceId, state);
|
|
940
|
-
throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
|
|
941
|
-
}
|
|
942
|
-
while (currentActivity && executionCount < maxExecutions) {
|
|
943
|
-
executionCount++;
|
|
944
|
-
// Map activity name (registry key) to activity type
|
|
945
|
-
const activityType = currentActivity.name;
|
|
946
|
-
// Determine execution mode (async)
|
|
947
|
-
const executionMode = await this.determineExecutionMode(activityType, currentActivity);
|
|
948
|
-
// Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
|
|
949
|
-
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
950
|
-
let taskType = currentActivity.metadata?.['taskType'] ??
|
|
951
|
-
activityDef?.taskType ??
|
|
952
|
-
'ui-activity';
|
|
953
|
-
// Defensive: human-task and cartable must suspend (same as run())
|
|
954
|
-
if (this.isTaskBoardSuspendGraphActivity(activityType)) {
|
|
955
|
-
if (activityDef?.taskType === 'human-task')
|
|
956
|
-
taskType = 'human-task';
|
|
957
|
-
}
|
|
958
|
-
const effectiveExecutionModeResume = taskType === 'human-task' && executionMode === 'backend'
|
|
959
|
-
? 'frontend'
|
|
960
|
-
: this.isTaskBoardSuspendGraphActivity(activityType) &&
|
|
961
|
-
(activityDef?.taskType === 'human-task' ||
|
|
962
|
-
taskType === 'human-task')
|
|
963
|
-
? executionMode === 'backend'
|
|
964
|
-
? 'frontend'
|
|
965
|
-
: executionMode
|
|
966
|
-
: executionMode;
|
|
967
|
-
// If frontend activity → stop and return as pendingTask
|
|
968
|
-
if (effectiveExecutionModeResume === 'frontend' ||
|
|
969
|
-
effectiveExecutionModeResume === 'both') {
|
|
970
|
-
const pendingTask = await this.createTaskFromActivity(currentActivity, state);
|
|
971
|
-
// Extract assignment and task metadata from activity inputs (for human-task activities)
|
|
972
|
-
const resolvedTaskType = taskType === 'human-task' || pendingTask.taskType === 'human-task'
|
|
973
|
-
? 'human-task'
|
|
974
|
-
: pendingTask.taskType;
|
|
975
|
-
let bookmarkPayload = {
|
|
976
|
-
activityType,
|
|
977
|
-
activityName: currentActivity.name,
|
|
978
|
-
taskToken: pendingTask.taskToken, // Store taskToken for resume
|
|
979
|
-
taskType: resolvedTaskType, // Add taskType to payload
|
|
872
|
+
// Validate task token (secure validation). If token missing/expired, allow resume when instance is suspended and bookmark for this step exists (task board tasks may wait days).
|
|
873
|
+
const tokenValid = request.taskToken &&
|
|
874
|
+
this.validateTaskToken(request.taskToken, request.instanceId, request.stepId);
|
|
875
|
+
if (!tokenValid) {
|
|
876
|
+
const allowedByBookmark = await this.canResumeByBookmark(request.instanceId, request.stepId);
|
|
877
|
+
if (!allowedByBookmark) {
|
|
878
|
+
throw new Error(request.taskToken
|
|
879
|
+
? 'Invalid or expired task token'
|
|
880
|
+
: 'Missing taskToken for resume operation');
|
|
881
|
+
}
|
|
882
|
+
console.log(`[BackendWorkflowEngineService] ✅ Resume allowed by bookmark (token expired/missing): ${request.instanceId}:${request.stepId}`);
|
|
883
|
+
}
|
|
884
|
+
// Verify stepId matches current step
|
|
885
|
+
if (state.currentStepId !== request.stepId) {
|
|
886
|
+
throw new Error(`Step mismatch: expected '${state.currentStepId}', got '${request.stepId}'`);
|
|
887
|
+
}
|
|
888
|
+
// Enforce claim / assignee for task-board bookmarks
|
|
889
|
+
const resumeBookmarks = await this.store.getBookmarksByInstanceAndActivity(request.instanceId, request.stepId, true);
|
|
890
|
+
const resumePayloadSource = resumeBookmarks[0];
|
|
891
|
+
const resumePayload = resumePayloadSource
|
|
892
|
+
? this.parseBookmarkPayloadJson(resumePayloadSource.payloadJson)
|
|
893
|
+
: null;
|
|
894
|
+
if (resumePayload) {
|
|
895
|
+
const payloadActivityType = String(resumePayload['activityType'] ?? '');
|
|
896
|
+
const assigned = this.normalizeUserIds(resumePayload['assignedUserIds']) ?? [];
|
|
897
|
+
const candidates = this.normalizeUserIds(resumePayload['candidateUserIds']) ?? [];
|
|
898
|
+
const isCartablePayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable;
|
|
899
|
+
const isPooledHumanPayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask &&
|
|
900
|
+
assigned.length === 0 &&
|
|
901
|
+
candidates.length > 0;
|
|
902
|
+
if (isCartablePayload || isPooledHumanPayload) {
|
|
903
|
+
if (assigned.length === 0) {
|
|
904
|
+
throw new workflow_engine_errors_1.AXPWorkflowEngineError('Claim the task before completing it', workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.CLAIM_REQUIRED);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (assigned.length > 0) {
|
|
908
|
+
const uid = this.getSessionUser()?.id;
|
|
909
|
+
if (uid && !assigned.includes(String(uid))) {
|
|
910
|
+
throw new workflow_engine_errors_1.AXPWorkflowEngineError('Only the assigned user can complete this task', workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.TASK_NOT_ASSIGNEE, { assignedUserIds: assigned });
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
// Mark bookmark as consumed
|
|
915
|
+
dirty = true;
|
|
916
|
+
await this.consumeBookmark(request.instanceId, request.stepId);
|
|
917
|
+
// Remove task token after validation (prevent replay); no-op if token was already expired/missing
|
|
918
|
+
if (request.taskToken) {
|
|
919
|
+
this.removeTaskToken(request.taskToken);
|
|
920
|
+
}
|
|
921
|
+
// Load workflow definition using definition loader (queries by name field)
|
|
922
|
+
const workflow = await this.getDefinition(state.workflowId);
|
|
923
|
+
workflowForFlush = workflow;
|
|
924
|
+
if (!workflow) {
|
|
925
|
+
console.error(`[BackendWorkflowRuntime] ❌ Workflow definition not found: ${state.workflowId}`);
|
|
926
|
+
throw new Error(`Workflow definition not found: ${state.workflowId}`);
|
|
927
|
+
}
|
|
928
|
+
const connections = workflow.graph.connections || [];
|
|
929
|
+
// Resolve allowed outcomes from workflow definition (e.g. human-task from instance inputs.actions)
|
|
930
|
+
const allowedOutcomes = await this.getActivityAllowedOutcomes(workflow, request.stepId);
|
|
931
|
+
const outcome = this.normalizeOutcome(request.outcome ?? 'Done', allowedOutcomes, request.stepId);
|
|
932
|
+
// Apply frontend output: last activity output for expressions (outputs.*); outcome/output/completer go into variables for audit UI
|
|
933
|
+
const userInput = request.userInput || {};
|
|
934
|
+
const lastActivityOutput = userInput;
|
|
935
|
+
const variableUpdatesResume = this.getVariableUpdatesFromOutput(workflow, request.stepId, userInput);
|
|
936
|
+
const sessionUser = this.getSessionUser();
|
|
937
|
+
const completerId = sessionUser?.id;
|
|
938
|
+
const completerLabel = sessionUser?.title ?? sessionUser?.name ?? completerId;
|
|
939
|
+
const resumeVariablePatch = {
|
|
940
|
+
...variableUpdatesResume,
|
|
941
|
+
[`${request.stepId}_outcome`]: outcome,
|
|
942
|
+
[`${request.stepId}_activityOutput`]: userInput,
|
|
980
943
|
};
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const inputs = pendingTask.input;
|
|
984
|
-
// Assignment metadata (normalize to array; expression may return single id e.g. {{ variables.managerId }})
|
|
985
|
-
bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
|
|
986
|
-
bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
|
|
987
|
-
bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
|
|
988
|
-
// Task metadata
|
|
989
|
-
bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
|
|
990
|
-
bookmarkPayload['description'] = inputs['description'] || null;
|
|
991
|
-
bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
|
|
992
|
-
bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
|
|
993
|
-
// UI configuration
|
|
994
|
-
bookmarkPayload['layout'] = inputs['layout'] || null;
|
|
995
|
-
bookmarkPayload['actions'] = inputs['actions'] || null;
|
|
996
|
-
bookmarkPayload['context'] = inputs['context'] || {};
|
|
944
|
+
if (completerId) {
|
|
945
|
+
resumeVariablePatch[`${request.stepId}_completedByUserId`] = completerId;
|
|
997
946
|
}
|
|
998
|
-
if (
|
|
999
|
-
|
|
947
|
+
if (completerLabel) {
|
|
948
|
+
resumeVariablePatch[`${request.stepId}_completedByLabel`] =
|
|
949
|
+
completerLabel;
|
|
1000
950
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
instanceId: request.instanceId,
|
|
1006
|
-
workflowDefinitionName: workflow.name,
|
|
1007
|
-
activityId: currentActivity.id,
|
|
1008
|
-
activityType,
|
|
1009
|
-
rawAssignedUserIds: htInputs?.['assignedUserIds'],
|
|
1010
|
-
normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
|
|
1011
|
-
variables: state.variables,
|
|
1012
|
-
});
|
|
951
|
+
else {
|
|
952
|
+
resumeVariablePatch[`${request.stepId}_completedByLabel`] = completerId
|
|
953
|
+
? String(completerId)
|
|
954
|
+
: 'System';
|
|
1013
955
|
}
|
|
1014
|
-
// Create bookmark for frontend activity
|
|
1015
|
-
// Store all metadata in bookmark payload (replaces Work Item)
|
|
1016
|
-
await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
|
|
1017
|
-
// Note: Work Item creation removed - all metadata stored in bookmark payload
|
|
1018
|
-
// Set status: task-board activities → suspended, else running (form/popup)
|
|
1019
|
-
const isTaskBoardSuspendResume = this.isTaskBoardSuspendGraphActivity(activityType);
|
|
1020
956
|
state = {
|
|
1021
957
|
...state,
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
return {
|
|
1029
|
-
output: { outcome, userInput: request.userInput },
|
|
1030
|
-
outcomes: { [outcome]: true },
|
|
1031
|
-
nextTask: pendingTask,
|
|
1032
|
-
state,
|
|
958
|
+
variables: {
|
|
959
|
+
...state.variables,
|
|
960
|
+
...resumeVariablePatch,
|
|
961
|
+
},
|
|
962
|
+
lastActivityOutput,
|
|
963
|
+
lastUpdated: new Date(),
|
|
1033
964
|
};
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
965
|
+
// Find next activity based on outcome from frontend (use normalized outcome)
|
|
966
|
+
const nextConnection = this.findWorkflowConnection(connections, request.stepId, outcome);
|
|
967
|
+
if (!nextConnection) {
|
|
968
|
+
// No next activity → workflow completed
|
|
969
|
+
console.log('[AXCWorkflowExecutionService] ✅ No next activity found - workflow completed');
|
|
970
|
+
state = {
|
|
971
|
+
...state,
|
|
972
|
+
status: 'completed',
|
|
973
|
+
currentStepId: undefined,
|
|
974
|
+
output: state.variables,
|
|
975
|
+
lastActivityOutput,
|
|
976
|
+
};
|
|
977
|
+
console.log('[AXCWorkflowExecutionService] 📝 Updating instance to completed state');
|
|
978
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
979
|
+
return {
|
|
980
|
+
output: { outcome, userInput: request.userInput },
|
|
981
|
+
outcomes: { [outcome]: true },
|
|
982
|
+
nextTask: null,
|
|
983
|
+
state,
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
// 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
|
|
987
|
+
let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
|
|
988
|
+
let lastActivityOutputResume = lastActivityOutput;
|
|
989
|
+
let executionCount = 0;
|
|
990
|
+
const maxExecutions = 500;
|
|
991
|
+
if (!currentActivity) {
|
|
992
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
|
|
993
|
+
state = {
|
|
994
|
+
...state,
|
|
995
|
+
status: 'error',
|
|
996
|
+
currentStepId: undefined,
|
|
997
|
+
};
|
|
998
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
999
|
+
throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
|
|
1000
|
+
}
|
|
1001
|
+
while (currentActivity && executionCount < maxExecutions) {
|
|
1002
|
+
executionCount++;
|
|
1003
|
+
// Map activity name (registry key) to activity type
|
|
1004
|
+
const activityType = currentActivity.name;
|
|
1005
|
+
// Determine execution mode (async)
|
|
1006
|
+
const executionMode = await this.determineExecutionMode(activityType, currentActivity);
|
|
1007
|
+
// Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
|
|
1008
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
1009
|
+
let taskType = currentActivity.metadata?.['taskType'] ??
|
|
1010
|
+
activityDef?.taskType ??
|
|
1011
|
+
'ui-activity';
|
|
1012
|
+
// Defensive: human-task and cartable must suspend (same as run())
|
|
1013
|
+
if (this.isTaskBoardSuspendGraphActivity(activityType)) {
|
|
1014
|
+
if (activityDef?.taskType === 'human-task')
|
|
1015
|
+
taskType = 'human-task';
|
|
1016
|
+
}
|
|
1017
|
+
const effectiveExecutionModeResume = taskType === 'human-task' && executionMode === 'backend'
|
|
1018
|
+
? 'frontend'
|
|
1019
|
+
: this.isTaskBoardSuspendGraphActivity(activityType) &&
|
|
1020
|
+
(activityDef?.taskType === 'human-task' ||
|
|
1021
|
+
taskType === 'human-task')
|
|
1022
|
+
? executionMode === 'backend'
|
|
1023
|
+
? 'frontend'
|
|
1024
|
+
: executionMode
|
|
1025
|
+
: executionMode;
|
|
1026
|
+
// If frontend activity → stop and return as pendingTask
|
|
1027
|
+
if (effectiveExecutionModeResume === 'frontend' ||
|
|
1028
|
+
effectiveExecutionModeResume === 'both') {
|
|
1029
|
+
const pendingTask = await this.createTaskFromActivity(currentActivity, state);
|
|
1030
|
+
// Extract assignment and task metadata from activity inputs (for human-task activities)
|
|
1031
|
+
const resolvedTaskType = taskType === 'human-task' || pendingTask.taskType === 'human-task'
|
|
1032
|
+
? 'human-task'
|
|
1033
|
+
: pendingTask.taskType;
|
|
1034
|
+
let bookmarkPayload = {
|
|
1035
|
+
activityType,
|
|
1036
|
+
activityName: currentActivity.name,
|
|
1037
|
+
taskToken: pendingTask.taskToken, // Store taskToken for resume
|
|
1038
|
+
taskType: resolvedTaskType, // Add taskType to payload
|
|
1039
|
+
};
|
|
1040
|
+
// For human-task activities, extract assignment and task metadata from inputs
|
|
1041
|
+
if (resolvedTaskType === 'human-task' && pendingTask.input) {
|
|
1042
|
+
const inputs = pendingTask.input;
|
|
1043
|
+
// Assignment metadata (normalize to array; expression may return single id e.g. {{ variables.managerId }})
|
|
1044
|
+
bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
|
|
1045
|
+
bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
|
|
1046
|
+
bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
|
|
1047
|
+
// Task metadata
|
|
1048
|
+
bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
|
|
1049
|
+
bookmarkPayload['description'] = inputs['description'] || null;
|
|
1050
|
+
bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
|
|
1051
|
+
bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
|
|
1052
|
+
// UI configuration
|
|
1053
|
+
bookmarkPayload['layout'] = inputs['layout'] || null;
|
|
1054
|
+
bookmarkPayload['actions'] = inputs['actions'] || null;
|
|
1055
|
+
bookmarkPayload['context'] = inputs['context'] || {};
|
|
1056
|
+
}
|
|
1057
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
|
|
1058
|
+
bookmarkPayload['assignedUserIds'] = [];
|
|
1059
|
+
}
|
|
1060
|
+
if (resolvedTaskType === 'human-task') {
|
|
1061
|
+
const htInputs = pendingTask.input;
|
|
1062
|
+
this.logHumanTaskBookmarkAssignmentTrace({
|
|
1063
|
+
phase: 'engine-resume',
|
|
1064
|
+
instanceId: request.instanceId,
|
|
1065
|
+
workflowDefinitionName: workflow.name,
|
|
1066
|
+
activityId: currentActivity.id,
|
|
1067
|
+
activityType,
|
|
1068
|
+
rawAssignedUserIds: htInputs?.['assignedUserIds'],
|
|
1069
|
+
normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
|
|
1070
|
+
variables: state.variables,
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
// Create bookmark for frontend activity
|
|
1074
|
+
// Store all metadata in bookmark payload (replaces Work Item)
|
|
1075
|
+
await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
|
|
1076
|
+
// Note: Work Item creation removed - all metadata stored in bookmark payload
|
|
1077
|
+
// Set status: task-board activities → suspended, else running (form/popup)
|
|
1078
|
+
const isTaskBoardSuspendResume = this.isTaskBoardSuspendGraphActivity(activityType);
|
|
1079
|
+
state = {
|
|
1080
|
+
...state,
|
|
1081
|
+
lastActivityOutput: lastActivityOutputResume,
|
|
1082
|
+
status: isTaskBoardSuspendResume ? 'suspended' : 'running',
|
|
1083
|
+
currentStepId: currentActivity.id,
|
|
1084
|
+
};
|
|
1085
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1086
|
+
console.log(`[BackendWorkflowEngineService] ✅ Workflow suspended at frontend activity: ${activityType} (taskType: ${taskType}, status: ${state.status})`);
|
|
1087
|
+
return {
|
|
1088
|
+
output: { outcome, userInput: request.userInput },
|
|
1089
|
+
outcomes: { [outcome]: true },
|
|
1090
|
+
nextTask: pendingTask,
|
|
1091
|
+
state,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
const parallelWrites = await this.runIndependentEntityWriteBatch({
|
|
1095
|
+
workflow,
|
|
1096
|
+
connections,
|
|
1097
|
+
graph: workflow.graph,
|
|
1098
|
+
instanceId: request.instanceId,
|
|
1099
|
+
activity: currentActivity,
|
|
1100
|
+
state,
|
|
1101
|
+
lastActivityOutput: lastActivityOutputResume,
|
|
1102
|
+
});
|
|
1103
|
+
if (parallelWrites) {
|
|
1104
|
+
state = parallelWrites.state;
|
|
1105
|
+
lastActivityOutputResume = parallelWrites.lastActivityOutput;
|
|
1106
|
+
executionCount += parallelWrites.executedCount - 1;
|
|
1107
|
+
if (!parallelWrites.nextActivity) {
|
|
1108
|
+
return {
|
|
1109
|
+
output: state.variables,
|
|
1110
|
+
outcomes: { Done: true },
|
|
1111
|
+
nextTask: null,
|
|
1112
|
+
state,
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
currentActivity = parallelWrites.nextActivity;
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
// Backend activity → execute it
|
|
1119
|
+
console.log(`[BackendWorkflowEngineService] ⚙️ Executing backend activity '${currentActivity.name || currentActivity.id}' (${activityType})`);
|
|
1120
|
+
const lastOutputBeforeBackendResume = lastActivityOutputResume;
|
|
1121
|
+
const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutputResume);
|
|
1122
|
+
lastActivityOutputResume = executionResult.output;
|
|
1123
|
+
await this.maybeLinkCreatedEntityRecord(request.instanceId, currentActivity, state, lastOutputBeforeBackendResume, executionResult.output);
|
|
1124
|
+
const isSetVariable = currentActivity.name === 'workflow-activity:set-variable';
|
|
1125
|
+
const variableUpdatesResumeLoop = isSetVariable
|
|
1126
|
+
? executionResult.output
|
|
1127
|
+
: this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
|
|
1128
|
+
const backendOutcome = Object.keys(executionResult.outcomes)[0] || 'Done';
|
|
1129
|
+
const sessionUserResume = this.getSessionUser();
|
|
1130
|
+
const backendAuditResume = {
|
|
1131
|
+
[`${currentActivity.id}_outcome`]: backendOutcome,
|
|
1132
|
+
[`${currentActivity.id}_activityOutput`]: executionResult.output,
|
|
1133
|
+
};
|
|
1134
|
+
if (sessionUserResume?.id) {
|
|
1135
|
+
backendAuditResume[`${currentActivity.id}_completedByUserId`] =
|
|
1136
|
+
sessionUserResume.id;
|
|
1137
|
+
backendAuditResume[`${currentActivity.id}_completedByLabel`] =
|
|
1138
|
+
sessionUserResume.title ||
|
|
1139
|
+
sessionUserResume.name ||
|
|
1140
|
+
sessionUserResume.id;
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
backendAuditResume[`${currentActivity.id}_completedByLabel`] = 'System';
|
|
1144
|
+
}
|
|
1145
|
+
state = {
|
|
1146
|
+
...state,
|
|
1147
|
+
variables: {
|
|
1148
|
+
...state.variables,
|
|
1149
|
+
...(typeof variableUpdatesResumeLoop === 'object' &&
|
|
1150
|
+
variableUpdatesResumeLoop !== null
|
|
1151
|
+
? variableUpdatesResumeLoop
|
|
1152
|
+
: {}),
|
|
1153
|
+
...backendAuditResume,
|
|
1154
|
+
},
|
|
1155
|
+
lastActivityOutput: lastActivityOutputResume,
|
|
1156
|
+
currentStepId: currentActivity.id,
|
|
1157
|
+
lastUpdated: new Date(),
|
|
1158
|
+
};
|
|
1159
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1160
|
+
// Find next activity based on outcome (backend execution result)
|
|
1161
|
+
const nextConn = this.findWorkflowConnection(connections, currentActivity.id, backendOutcome);
|
|
1162
|
+
if (!nextConn) {
|
|
1163
|
+
// No next activity → workflow completed
|
|
1164
|
+
state = {
|
|
1165
|
+
...state,
|
|
1166
|
+
status: 'completed',
|
|
1167
|
+
currentStepId: undefined,
|
|
1168
|
+
output: state.variables,
|
|
1169
|
+
lastActivityOutput: lastActivityOutputResume,
|
|
1170
|
+
};
|
|
1171
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1172
|
+
return {
|
|
1173
|
+
output: state.variables,
|
|
1174
|
+
outcomes: { Done: true },
|
|
1175
|
+
nextTask: null,
|
|
1176
|
+
state,
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
// Find next activity
|
|
1180
|
+
const nextActivity = this.findActivityInGraph(workflow.graph, nextConn.target.activtyName);
|
|
1181
|
+
if (!nextActivity) {
|
|
1182
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConn.target.activtyName}' not found in graph`);
|
|
1183
|
+
state = {
|
|
1184
|
+
...state,
|
|
1185
|
+
status: 'error',
|
|
1186
|
+
currentStepId: undefined,
|
|
1187
|
+
};
|
|
1188
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1189
|
+
throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
|
|
1190
|
+
}
|
|
1191
|
+
currentActivity = nextActivity;
|
|
1192
|
+
}
|
|
1193
|
+
// If we exit loop due to max executions, mark as error
|
|
1194
|
+
if (executionCount >= maxExecutions) {
|
|
1195
|
+
console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
|
|
1196
|
+
state = {
|
|
1197
|
+
...state,
|
|
1198
|
+
status: 'error',
|
|
1199
|
+
currentStepId: undefined,
|
|
1200
|
+
};
|
|
1201
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1202
|
+
throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
|
|
1203
|
+
}
|
|
1204
|
+
// If we exit loop, workflow is complete
|
|
1081
1205
|
state = {
|
|
1082
1206
|
...state,
|
|
1083
1207
|
status: 'completed',
|
|
@@ -1092,47 +1216,16 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
1092
1216
|
nextTask: null,
|
|
1093
1217
|
state,
|
|
1094
1218
|
};
|
|
1095
|
-
}
|
|
1096
|
-
// Find next activity
|
|
1097
|
-
const nextActivity = this.findActivityInGraph(workflow.graph, nextConn.target.activtyName);
|
|
1098
|
-
if (!nextActivity) {
|
|
1099
|
-
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConn.target.activtyName}' not found in graph`);
|
|
1100
|
-
state = {
|
|
1101
|
-
...state,
|
|
1102
|
-
status: 'error',
|
|
1103
|
-
currentStepId: undefined,
|
|
1104
|
-
};
|
|
1105
|
-
await this.updateInstanceFromState(request.instanceId, state);
|
|
1106
|
-
throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
|
|
1107
|
-
}
|
|
1108
|
-
currentActivity = nextActivity;
|
|
1219
|
+
}));
|
|
1109
1220
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
};
|
|
1118
|
-
await this.updateInstanceFromState(request.instanceId, state);
|
|
1119
|
-
throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
|
|
1221
|
+
finally {
|
|
1222
|
+
if (dirty) {
|
|
1223
|
+
const current = await this.store.getInstance(request.instanceId);
|
|
1224
|
+
if (current) {
|
|
1225
|
+
await this.writeInstanceProjection(current, workflowForFlush, false);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1120
1228
|
}
|
|
1121
|
-
// If we exit loop, workflow is complete
|
|
1122
|
-
state = {
|
|
1123
|
-
...state,
|
|
1124
|
-
status: 'completed',
|
|
1125
|
-
currentStepId: undefined,
|
|
1126
|
-
output: state.variables,
|
|
1127
|
-
lastActivityOutput: lastActivityOutputResume,
|
|
1128
|
-
};
|
|
1129
|
-
await this.updateInstanceFromState(request.instanceId, state);
|
|
1130
|
-
return {
|
|
1131
|
-
output: state.variables,
|
|
1132
|
-
outcomes: { Done: true },
|
|
1133
|
-
nextTask: null,
|
|
1134
|
-
state,
|
|
1135
|
-
};
|
|
1136
1229
|
}
|
|
1137
1230
|
/**
|
|
1138
1231
|
* Get current workflow execution state.
|
|
@@ -2110,10 +2203,21 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2110
2203
|
.map((value) => value.replace(/([a-z])([A-Z])/g, '$1 $2').trim());
|
|
2111
2204
|
return fallbackTokens.length > 0 ? fallbackTokens.join(' - ') : 'Unknown';
|
|
2112
2205
|
}
|
|
2206
|
+
/**
|
|
2207
|
+
* One entity write for a finished start/resume/front-complete run.
|
|
2208
|
+
* New runs insert; later steps update. Skips a second store persist.
|
|
2209
|
+
*/
|
|
2210
|
+
async writeInstanceProjection(instance, definition, insert) {
|
|
2211
|
+
if (insert) {
|
|
2212
|
+
await this.saveInstanceToEntityService(instance, definition);
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
await this.updateInstanceInEntityService(instance, definition);
|
|
2216
|
+
}
|
|
2113
2217
|
/**
|
|
2114
2218
|
* Save workflow instance to entity service for entity-based access.
|
|
2115
2219
|
*/
|
|
2116
|
-
async saveInstanceToEntityService(instance) {
|
|
2220
|
+
async saveInstanceToEntityService(instance, predefined) {
|
|
2117
2221
|
try {
|
|
2118
2222
|
// Get active bookmarks for this instance to include in entity model
|
|
2119
2223
|
const activeBookmarks = await this.getActiveBookmarks(instance.id);
|
|
@@ -2132,12 +2236,13 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2132
2236
|
createdAt: b.createdAt,
|
|
2133
2237
|
})),
|
|
2134
2238
|
};
|
|
2135
|
-
const definition = await this.getDefinition(instance.definitionId);
|
|
2239
|
+
const definition = predefined ?? (await this.getDefinition(instance.definitionId));
|
|
2136
2240
|
const overview = this.computeOverviewDisplayFields(instance, definition);
|
|
2137
2241
|
const runtimeDetails = this.computeRuntimeDetails(instance, definition, activeBookmarks);
|
|
2138
2242
|
const workflowVariablesTable = this.computeWorkflowVariablesTable(instance, definition);
|
|
2139
2243
|
const assignUserFields = await this.resolveAssignUserLookupFields(this.resolvePrimaryAssignUserId(instance, activeBookmarks));
|
|
2140
2244
|
await this.syncLinkedTaskAssignee(instance, assignUserFields);
|
|
2245
|
+
const scopeInfo = this.getSessionScopeInfo();
|
|
2141
2246
|
const entityModel = {
|
|
2142
2247
|
id: instance.id,
|
|
2143
2248
|
definitionId: instance.definitionId,
|
|
@@ -2163,11 +2268,12 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2163
2268
|
totalSteps: overview.totalSteps,
|
|
2164
2269
|
progressPercentage: overview.progressPercentage,
|
|
2165
2270
|
workflowVariablesTable,
|
|
2271
|
+
...(scopeInfo ? { scopeInfo } : {}),
|
|
2166
2272
|
};
|
|
2167
2273
|
const entityName = 'WorkflowManagement.WorkflowInstance';
|
|
2168
2274
|
// Check if instance already exists
|
|
2169
2275
|
try {
|
|
2170
|
-
const existing = await this.entityStorageService.
|
|
2276
|
+
const existing = await this.entityStorageService.getOneAsEngine(entityName, entityModel.id);
|
|
2171
2277
|
if (existing) {
|
|
2172
2278
|
// Already exists - don't insert again, will be updated later
|
|
2173
2279
|
console.log('[AXCWorkflowExecutionService] ℹ️ Instance already exists in entity service, will be updated later', {
|
|
@@ -2180,7 +2286,7 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2180
2286
|
// Instance doesn't exist - proceed with insert
|
|
2181
2287
|
}
|
|
2182
2288
|
// Insert new instance
|
|
2183
|
-
await this.entityStorageService.
|
|
2289
|
+
await this.entityStorageService.insertOneAsEngine(entityName, entityModel);
|
|
2184
2290
|
console.log('[AXCWorkflowExecutionService] ✅ Inserted new instance in entity service', {
|
|
2185
2291
|
instanceId: instance.id,
|
|
2186
2292
|
status: instance.status,
|
|
@@ -2196,7 +2302,7 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2196
2302
|
* Update workflow instance in entity service.
|
|
2197
2303
|
* Uses upsert pattern: try update first, if fails then insert.
|
|
2198
2304
|
*/
|
|
2199
|
-
async updateInstanceInEntityService(instance) {
|
|
2305
|
+
async updateInstanceInEntityService(instance, predefined) {
|
|
2200
2306
|
try {
|
|
2201
2307
|
// Get active bookmarks for this instance to include in entity model
|
|
2202
2308
|
const activeBookmarks = await this.getActiveBookmarks(instance.id);
|
|
@@ -2215,12 +2321,13 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2215
2321
|
createdAt: b.createdAt,
|
|
2216
2322
|
})),
|
|
2217
2323
|
};
|
|
2218
|
-
const definition = await this.getDefinition(instance.definitionId);
|
|
2324
|
+
const definition = predefined ?? (await this.getDefinition(instance.definitionId));
|
|
2219
2325
|
const overview = this.computeOverviewDisplayFields(instance, definition);
|
|
2220
2326
|
const runtimeDetails = this.computeRuntimeDetails(instance, definition, activeBookmarks);
|
|
2221
2327
|
const workflowVariablesTable = this.computeWorkflowVariablesTable(instance, definition);
|
|
2222
2328
|
const assignUserFields = await this.resolveAssignUserLookupFields(this.resolvePrimaryAssignUserId(instance, activeBookmarks));
|
|
2223
2329
|
await this.syncLinkedTaskAssignee(instance, assignUserFields);
|
|
2330
|
+
const scopeInfo = this.getSessionScopeInfo();
|
|
2224
2331
|
const entityModel = {
|
|
2225
2332
|
id: instance.id,
|
|
2226
2333
|
definitionId: instance.definitionId,
|
|
@@ -2246,11 +2353,12 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2246
2353
|
totalSteps: overview.totalSteps,
|
|
2247
2354
|
progressPercentage: overview.progressPercentage,
|
|
2248
2355
|
workflowVariablesTable,
|
|
2356
|
+
...(scopeInfo ? { scopeInfo } : {}),
|
|
2249
2357
|
};
|
|
2250
2358
|
const entityName = 'WorkflowManagement.WorkflowInstance';
|
|
2251
2359
|
// Try update first (upsert pattern)
|
|
2252
2360
|
try {
|
|
2253
|
-
await this.entityStorageService.
|
|
2361
|
+
await this.entityStorageService.updateOneAsEngine(entityName, entityModel.id, entityModel);
|
|
2254
2362
|
console.log('[AXCWorkflowExecutionService] ✅ Updated instance in entity service', {
|
|
2255
2363
|
instanceId: instance.id,
|
|
2256
2364
|
status: instance.status,
|
|
@@ -2267,7 +2375,7 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2267
2375
|
}
|
|
2268
2376
|
// Try insert
|
|
2269
2377
|
try {
|
|
2270
|
-
await this.entityStorageService.
|
|
2378
|
+
await this.entityStorageService.insertOneAsEngine(entityName, entityModel);
|
|
2271
2379
|
console.log('[AXCWorkflowExecutionService] ✅ Inserted instance in entity service', {
|
|
2272
2380
|
instanceId: instance.id,
|
|
2273
2381
|
status: instance.status,
|
|
@@ -2278,7 +2386,7 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2278
2386
|
// Insert might fail if instance was created between update and insert
|
|
2279
2387
|
// Try update again as fallback
|
|
2280
2388
|
try {
|
|
2281
|
-
await this.entityStorageService.
|
|
2389
|
+
await this.entityStorageService.updateOneAsEngine(entityName, entityModel.id, entityModel);
|
|
2282
2390
|
console.log('[AXCWorkflowExecutionService] ✅ Updated instance in entity service (after insert failed)', {
|
|
2283
2391
|
instanceId: instance.id,
|
|
2284
2392
|
status: instance.status,
|
|
@@ -2357,6 +2465,128 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2357
2465
|
correlationId: correlationId != null ? String(correlationId) : null,
|
|
2358
2466
|
});
|
|
2359
2467
|
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Runs a Done-chain of entity creates and updates that do not read each other's new ids.
|
|
2470
|
+
* Returns null when this step must stay on the one-activity path.
|
|
2471
|
+
*/
|
|
2472
|
+
async runIndependentEntityWriteBatch(args) {
|
|
2473
|
+
const batch = this.collectIndependentEntityWriteBatch(args.connections, args.graph, args.activity);
|
|
2474
|
+
if (batch.length < 2) {
|
|
2475
|
+
return null;
|
|
2476
|
+
}
|
|
2477
|
+
const results = await Promise.all(batch.map((activity) => this.executeBackendActivityLocally(activity, args.state, args.lastActivityOutput)));
|
|
2478
|
+
let state = args.state;
|
|
2479
|
+
let lastActivityOutput = args.lastActivityOutput;
|
|
2480
|
+
let routeActivity = batch[batch.length - 1];
|
|
2481
|
+
let routeOutcome = 'Done';
|
|
2482
|
+
let executedCount = 0;
|
|
2483
|
+
for (let index = 0; index < batch.length; index++) {
|
|
2484
|
+
const activity = batch[index];
|
|
2485
|
+
const executionResult = results[index];
|
|
2486
|
+
executedCount++;
|
|
2487
|
+
lastActivityOutput = executionResult.output;
|
|
2488
|
+
await this.maybeLinkCreatedEntityRecord(args.instanceId, activity, state, args.lastActivityOutput, executionResult.output);
|
|
2489
|
+
const variableUpdates = activity.name === 'workflow-activity:set-variable'
|
|
2490
|
+
? executionResult.output
|
|
2491
|
+
: this.getVariableUpdatesFromOutput(args.workflow, activity.id, executionResult.output);
|
|
2492
|
+
const outcome = Object.keys(executionResult.outcomes ?? {})[0] || 'Done';
|
|
2493
|
+
const sessionUser = this.getSessionUser();
|
|
2494
|
+
const audit = {
|
|
2495
|
+
[`${activity.id}_outcome`]: outcome,
|
|
2496
|
+
[`${activity.id}_activityOutput`]: executionResult.output,
|
|
2497
|
+
};
|
|
2498
|
+
if (sessionUser?.id) {
|
|
2499
|
+
audit[`${activity.id}_completedByUserId`] = sessionUser.id;
|
|
2500
|
+
audit[`${activity.id}_completedByLabel`] =
|
|
2501
|
+
sessionUser.title || sessionUser.name || sessionUser.id;
|
|
2502
|
+
}
|
|
2503
|
+
else {
|
|
2504
|
+
audit[`${activity.id}_completedByLabel`] = 'System';
|
|
2505
|
+
}
|
|
2506
|
+
state = {
|
|
2507
|
+
...state,
|
|
2508
|
+
variables: {
|
|
2509
|
+
...state.variables,
|
|
2510
|
+
...(variableUpdates && typeof variableUpdates === 'object' ? variableUpdates : {}),
|
|
2511
|
+
...audit,
|
|
2512
|
+
},
|
|
2513
|
+
lastActivityOutput,
|
|
2514
|
+
currentStepId: activity.id,
|
|
2515
|
+
lastUpdated: new Date(),
|
|
2516
|
+
};
|
|
2517
|
+
routeActivity = activity;
|
|
2518
|
+
routeOutcome = outcome;
|
|
2519
|
+
if (outcome.toLowerCase() !== 'done') {
|
|
2520
|
+
break;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
await this.updateInstanceFromState(args.instanceId, state);
|
|
2524
|
+
const nextConn = this.findWorkflowConnection(args.connections, routeActivity.id, routeOutcome);
|
|
2525
|
+
if (!nextConn) {
|
|
2526
|
+
state = {
|
|
2527
|
+
...state,
|
|
2528
|
+
status: 'completed',
|
|
2529
|
+
currentStepId: undefined,
|
|
2530
|
+
output: state.variables,
|
|
2531
|
+
lastActivityOutput,
|
|
2532
|
+
};
|
|
2533
|
+
await this.updateInstanceFromState(args.instanceId, state);
|
|
2534
|
+
return { state, lastActivityOutput, nextActivity: null, executedCount };
|
|
2535
|
+
}
|
|
2536
|
+
const nextActivity = this.findActivityInGraph(args.graph, nextConn.target.activtyName);
|
|
2537
|
+
if (!nextActivity) {
|
|
2538
|
+
state = { ...state, status: 'error', currentStepId: undefined };
|
|
2539
|
+
await this.updateInstanceFromState(args.instanceId, state);
|
|
2540
|
+
throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
|
|
2541
|
+
}
|
|
2542
|
+
return { state, lastActivityOutput, nextActivity, executedCount };
|
|
2543
|
+
}
|
|
2544
|
+
collectIndependentEntityWriteBatch(connections, graph, start) {
|
|
2545
|
+
if (!BackendWorkflowEngineService_1.ENTITY_WRITE_ACTIVITY_NAMES.has(start.name)) {
|
|
2546
|
+
return [start];
|
|
2547
|
+
}
|
|
2548
|
+
const batch = [start];
|
|
2549
|
+
const produced = new Set(this.outputVariableNames(start));
|
|
2550
|
+
let cursor = start;
|
|
2551
|
+
while (batch.length < 8) {
|
|
2552
|
+
const conn = this.findWorkflowConnection(connections, cursor.id, 'Done');
|
|
2553
|
+
if (!conn) {
|
|
2554
|
+
break;
|
|
2555
|
+
}
|
|
2556
|
+
const next = this.findActivityInGraph(graph, conn.target.activtyName);
|
|
2557
|
+
if (!next || !BackendWorkflowEngineService_1.ENTITY_WRITE_ACTIVITY_NAMES.has(next.name)) {
|
|
2558
|
+
break;
|
|
2559
|
+
}
|
|
2560
|
+
if (this.activityReadsVariables(next, produced)) {
|
|
2561
|
+
break;
|
|
2562
|
+
}
|
|
2563
|
+
batch.push(next);
|
|
2564
|
+
for (const name of this.outputVariableNames(next)) {
|
|
2565
|
+
produced.add(name);
|
|
2566
|
+
}
|
|
2567
|
+
cursor = next;
|
|
2568
|
+
}
|
|
2569
|
+
return batch;
|
|
2570
|
+
}
|
|
2571
|
+
outputVariableNames(activity) {
|
|
2572
|
+
const mapping = activity.outputToVariables;
|
|
2573
|
+
if (!mapping || typeof mapping !== 'object') {
|
|
2574
|
+
return [];
|
|
2575
|
+
}
|
|
2576
|
+
return Object.keys(mapping);
|
|
2577
|
+
}
|
|
2578
|
+
activityReadsVariables(activity, names) {
|
|
2579
|
+
if (names.size === 0) {
|
|
2580
|
+
return false;
|
|
2581
|
+
}
|
|
2582
|
+
const blob = JSON.stringify(activity.inputs ?? {});
|
|
2583
|
+
for (const name of names) {
|
|
2584
|
+
if (new RegExp(`variables\\.${name}(?![A-Za-z0-9_])`).test(blob)) {
|
|
2585
|
+
return true;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
return false;
|
|
2589
|
+
}
|
|
2360
2590
|
/**
|
|
2361
2591
|
* Execute backend activities locally via {@link WorkflowActivityRegistryService}.
|
|
2362
2592
|
*
|
|
@@ -2696,9 +2926,12 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2696
2926
|
}
|
|
2697
2927
|
});
|
|
2698
2928
|
}
|
|
2699
|
-
// Save
|
|
2700
|
-
|
|
2701
|
-
|
|
2929
|
+
// Save runtime state in memory. Entity row is written once per start/resume
|
|
2930
|
+
// unless persistence is not deferred (cancel and other direct updates).
|
|
2931
|
+
await this.store.updateInstance(instance, { persist: false });
|
|
2932
|
+
if (this.store.isPersistenceDeferred()) {
|
|
2933
|
+
return instance;
|
|
2934
|
+
}
|
|
2702
2935
|
await this.updateInstanceInEntityService(instance);
|
|
2703
2936
|
return instance;
|
|
2704
2937
|
}
|
|
@@ -2732,332 +2965,370 @@ let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class Backen
|
|
|
2732
2965
|
* @returns Response with nextTask (if frontend activity) or completion status
|
|
2733
2966
|
*/
|
|
2734
2967
|
async frontActivtyComplete(request) {
|
|
2968
|
+
let dirty = false;
|
|
2969
|
+
let workflowForFlush = null;
|
|
2735
2970
|
try {
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
// Check if workflow is suspended
|
|
2743
|
-
// If suspended, check if the activity being completed is a ui-activity (not human-task)
|
|
2744
|
-
// If it's a ui-activity, we can continue execution (workflow manager is handling it)
|
|
2745
|
-
// If it's a human-task, use resume() instead
|
|
2746
|
-
if (state.status === 'suspended') {
|
|
2747
|
-
// Find the activity being completed to check its taskType
|
|
2748
|
-
const workflow = await this.getDefinition(state.workflowId);
|
|
2749
|
-
if (!workflow) {
|
|
2750
|
-
console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
|
|
2751
|
-
throw new Error(`Workflow definition not found: ${state.workflowId}`);
|
|
2971
|
+
return await (0, workflow_trigger_guard_1.runInsideWorkflowEngine)(() => this.store.runWithoutPersist(async () => {
|
|
2972
|
+
// Get current workflow instance state
|
|
2973
|
+
let state = await this.getState({ instanceId: request.instanceId });
|
|
2974
|
+
if (!state) {
|
|
2975
|
+
console.error('[BackendWorkflowEngineService] ❌ Workflow instance not found:', request.instanceId);
|
|
2976
|
+
throw new Error(`Workflow instance not found: ${request.instanceId}`);
|
|
2752
2977
|
}
|
|
2753
|
-
|
|
2754
|
-
if (
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
//
|
|
2759
|
-
|
|
2760
|
-
|
|
2978
|
+
// Check if workflow is suspended
|
|
2979
|
+
// If suspended, check if the activity being completed is a ui-activity (not human-task)
|
|
2980
|
+
// If it's a ui-activity, we can continue execution (workflow manager is handling it)
|
|
2981
|
+
// If it's a human-task, use resume() instead
|
|
2982
|
+
if (state.status === 'suspended') {
|
|
2983
|
+
// Find the activity being completed to check its taskType
|
|
2984
|
+
const workflow = await this.getDefinition(state.workflowId);
|
|
2985
|
+
if (!workflow) {
|
|
2986
|
+
console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
|
|
2987
|
+
throw new Error(`Workflow definition not found: ${state.workflowId}`);
|
|
2988
|
+
}
|
|
2989
|
+
const activity = this.findActivityInGraph(workflow.graph, request.activityNode);
|
|
2990
|
+
if (activity) {
|
|
2991
|
+
const activityType = activity.name;
|
|
2992
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
2993
|
+
const taskType = activityDef?.taskType || 'ui-activity';
|
|
2994
|
+
// If it's a ui-activity, allow continuation (workflow manager is handling it)
|
|
2995
|
+
if (taskType === 'ui-activity') {
|
|
2996
|
+
// Change status to running to allow continuation
|
|
2997
|
+
state = {
|
|
2998
|
+
...state,
|
|
2999
|
+
status: 'running',
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
else {
|
|
3003
|
+
// It's a human-task, must use resume()
|
|
3004
|
+
console.warn('[BackendWorkflowEngineService] ⚠️ Workflow is suspended. Use resume() method instead.');
|
|
3005
|
+
throw new Error('Workflow is suspended. Use resume() method for suspended workflows.');
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
else {
|
|
3009
|
+
// Activity not found, but allow continuation (might be edge case)
|
|
2761
3010
|
state = {
|
|
2762
3011
|
...state,
|
|
2763
3012
|
status: 'running',
|
|
2764
3013
|
};
|
|
2765
3014
|
}
|
|
2766
|
-
else {
|
|
2767
|
-
// It's a human-task, must use resume()
|
|
2768
|
-
console.warn('[BackendWorkflowEngineService] ⚠️ Workflow is suspended. Use resume() method instead.');
|
|
2769
|
-
throw new Error('Workflow is suspended. Use resume() method for suspended workflows.');
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
else {
|
|
2773
|
-
// Activity not found, but allow continuation (might be edge case)
|
|
2774
|
-
state = {
|
|
2775
|
-
...state,
|
|
2776
|
-
status: 'running',
|
|
2777
|
-
};
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
|
-
// Load workflow definition
|
|
2781
|
-
const workflow = await this.getDefinition(state.workflowId);
|
|
2782
|
-
if (!workflow) {
|
|
2783
|
-
console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
|
|
2784
|
-
throw new Error(`Workflow definition not found: ${state.workflowId}`);
|
|
2785
|
-
}
|
|
2786
|
-
const connections = workflow.graph.connections || [];
|
|
2787
|
-
// Consume bookmark for the activity being completed so only the next step has an active bookmark
|
|
2788
|
-
await this.consumeBookmark(request.instanceId, request.activityNode);
|
|
2789
|
-
const output = (request.output || {});
|
|
2790
|
-
const completedActivity = this.findActivityInGraph(workflow.graph, request.activityNode);
|
|
2791
|
-
// Resolve allowed outcomes from workflow definition and normalize outcome
|
|
2792
|
-
const allowedOutcomes = await this.getActivityAllowedOutcomes(workflow, request.activityNode);
|
|
2793
|
-
const coercedOutcome = this.coerceCreateEntityFormOutcome(completedActivity, request.outcome ?? 'Done', output);
|
|
2794
|
-
const outcome = this.normalizeOutcome(coercedOutcome, allowedOutcomes, request.activityNode);
|
|
2795
|
-
// Apply frontend output: only last activity output is kept (overwritten each step)
|
|
2796
|
-
let lastActivityOutput = output;
|
|
2797
|
-
// Apply outputToVariables for the completed frontend activity (e.g. leaveRequestForm → leaveRequestId: 'id')
|
|
2798
|
-
const variableUpdatesFromOutput = this.getVariableUpdatesFromOutput(workflow, request.activityNode, output);
|
|
2799
|
-
const formResultPromotions = this.buildPersistedEntityFormVariablePromotions(output, completedActivity?.name, outcome);
|
|
2800
|
-
const sessionUserFrontComplete = this.getSessionUser();
|
|
2801
|
-
const frontCompleteAudit = {
|
|
2802
|
-
[`${request.activityNode}_outcome`]: outcome,
|
|
2803
|
-
[`${request.activityNode}_activityOutput`]: output,
|
|
2804
|
-
};
|
|
2805
|
-
if (sessionUserFrontComplete?.id) {
|
|
2806
|
-
frontCompleteAudit[`${request.activityNode}_completedByUserId`] =
|
|
2807
|
-
sessionUserFrontComplete.id;
|
|
2808
|
-
frontCompleteAudit[`${request.activityNode}_completedByLabel`] =
|
|
2809
|
-
sessionUserFrontComplete.title ||
|
|
2810
|
-
sessionUserFrontComplete.name ||
|
|
2811
|
-
sessionUserFrontComplete.id;
|
|
2812
|
-
}
|
|
2813
|
-
else {
|
|
2814
|
-
frontCompleteAudit[`${request.activityNode}_completedByLabel`] =
|
|
2815
|
-
'System';
|
|
2816
|
-
}
|
|
2817
|
-
state = {
|
|
2818
|
-
...state,
|
|
2819
|
-
variables: {
|
|
2820
|
-
...state.variables,
|
|
2821
|
-
...output, // Merge frontend output into variables
|
|
2822
|
-
...formResultPromotions, // Promote capture-form fields (e.g. effectiveDate) onto variables
|
|
2823
|
-
...variableUpdatesFromOutput, // Map output paths to variable names (e.g. id → leaveRequestId)
|
|
2824
|
-
...frontCompleteAudit,
|
|
2825
|
-
},
|
|
2826
|
-
lastActivityOutput,
|
|
2827
|
-
lastUpdated: new Date(),
|
|
2828
|
-
};
|
|
2829
|
-
// Find next activity based on outcome from frontend (use normalized outcome)
|
|
2830
|
-
const nextConnection = this.findWorkflowConnection(connections, request.activityNode, outcome);
|
|
2831
|
-
if (!nextConnection) {
|
|
2832
|
-
// No next activity → workflow completed
|
|
2833
|
-
state = {
|
|
2834
|
-
...state,
|
|
2835
|
-
status: 'completed',
|
|
2836
|
-
currentStepId: undefined,
|
|
2837
|
-
output: state.variables,
|
|
2838
|
-
};
|
|
2839
|
-
await this.updateInstanceFromState(request.instanceId, state);
|
|
2840
|
-
return {
|
|
2841
|
-
output: { outcome, userInput: request.output },
|
|
2842
|
-
outcomes: { [outcome]: true },
|
|
2843
|
-
nextTask: null,
|
|
2844
|
-
state,
|
|
2845
|
-
};
|
|
2846
|
-
}
|
|
2847
|
-
// 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
|
|
2848
|
-
let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
|
|
2849
|
-
let executionCount = 0;
|
|
2850
|
-
const maxExecutions = 500;
|
|
2851
|
-
if (!currentActivity) {
|
|
2852
|
-
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
|
|
2853
|
-
state = {
|
|
2854
|
-
...state,
|
|
2855
|
-
status: 'error',
|
|
2856
|
-
currentStepId: undefined,
|
|
2857
|
-
};
|
|
2858
|
-
await this.updateInstanceFromState(request.instanceId, state);
|
|
2859
|
-
throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
|
|
2860
|
-
}
|
|
2861
|
-
// Only lastActivityOutput is persisted; used as scope.outputs for expressions.
|
|
2862
|
-
while (currentActivity && executionCount < maxExecutions) {
|
|
2863
|
-
executionCount++;
|
|
2864
|
-
// Map activity name (registry key) to activity type
|
|
2865
|
-
const activityType = currentActivity.name;
|
|
2866
|
-
// Determine execution mode (async)
|
|
2867
|
-
const executionMode = await this.determineExecutionMode(activityType, currentActivity);
|
|
2868
|
-
// Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
|
|
2869
|
-
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
2870
|
-
let taskTypeComplete = currentActivity.metadata?.['taskType'] ??
|
|
2871
|
-
activityDef?.taskType ??
|
|
2872
|
-
'ui-activity';
|
|
2873
|
-
// Defensive: human-task and cartable must suspend (same as run())
|
|
2874
|
-
if (this.isTaskBoardSuspendGraphActivity(activityType)) {
|
|
2875
|
-
if (activityDef?.taskType === 'human-task')
|
|
2876
|
-
taskTypeComplete = 'human-task';
|
|
2877
3015
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
? 'frontend'
|
|
2885
|
-
: executionMode
|
|
2886
|
-
: executionMode;
|
|
2887
|
-
// If frontend activity → stop and return as nextTask
|
|
2888
|
-
if (effectiveExecutionModeComplete === 'frontend' ||
|
|
2889
|
-
effectiveExecutionModeComplete === 'both') {
|
|
2890
|
-
const pendingTask = await this.createTaskFromActivity(currentActivity, state);
|
|
2891
|
-
const resolvedTaskTypeComplete = taskTypeComplete === 'human-task' ||
|
|
2892
|
-
pendingTask.taskType === 'human-task'
|
|
2893
|
-
? 'human-task'
|
|
2894
|
-
: pendingTask.taskType;
|
|
2895
|
-
// Extract assignment and task metadata from activity inputs (for human-task activities)
|
|
2896
|
-
let bookmarkPayload = {
|
|
2897
|
-
activityType,
|
|
2898
|
-
activityName: currentActivity.name,
|
|
2899
|
-
taskToken: pendingTask.taskToken, // Store taskToken for resume
|
|
2900
|
-
taskType: resolvedTaskTypeComplete,
|
|
2901
|
-
};
|
|
2902
|
-
// For human-task activities, extract assignment and task metadata from inputs
|
|
2903
|
-
if (resolvedTaskTypeComplete === 'human-task' && pendingTask.input) {
|
|
2904
|
-
const inputs = pendingTask.input;
|
|
2905
|
-
// Assignment metadata (normalize to array; expression may return single id)
|
|
2906
|
-
bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
|
|
2907
|
-
bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
|
|
2908
|
-
bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
|
|
2909
|
-
// Task metadata
|
|
2910
|
-
bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
|
|
2911
|
-
bookmarkPayload['description'] = inputs['description'] || null;
|
|
2912
|
-
bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
|
|
2913
|
-
bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
|
|
2914
|
-
// UI configuration
|
|
2915
|
-
bookmarkPayload['layout'] = inputs['layout'] || null;
|
|
2916
|
-
bookmarkPayload['actions'] = inputs['actions'] || null;
|
|
2917
|
-
bookmarkPayload['context'] = inputs['context'] || {};
|
|
2918
|
-
}
|
|
2919
|
-
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
|
|
2920
|
-
bookmarkPayload['assignedUserIds'] = [];
|
|
2921
|
-
}
|
|
2922
|
-
if (resolvedTaskTypeComplete === 'human-task') {
|
|
2923
|
-
const htInputs = pendingTask.input;
|
|
2924
|
-
this.logHumanTaskBookmarkAssignmentTrace({
|
|
2925
|
-
phase: 'engine-frontActivityComplete',
|
|
2926
|
-
instanceId: request.instanceId,
|
|
2927
|
-
workflowDefinitionName: workflow.name,
|
|
2928
|
-
activityId: currentActivity.id,
|
|
2929
|
-
activityType,
|
|
2930
|
-
rawAssignedUserIds: htInputs?.['assignedUserIds'],
|
|
2931
|
-
normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
|
|
2932
|
-
variables: state.variables,
|
|
2933
|
-
});
|
|
2934
|
-
}
|
|
2935
|
-
// Create bookmark for frontend activity
|
|
2936
|
-
// Store all metadata in bookmark payload (replaces Work Item)
|
|
2937
|
-
const bookmarkId = await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
|
|
2938
|
-
// Update state: task-board activities → suspended, else running (form/popup)
|
|
2939
|
-
const isHumanTaskComplete = this.isTaskBoardSuspendGraphActivity(activityType);
|
|
2940
|
-
state = {
|
|
2941
|
-
...state,
|
|
2942
|
-
lastActivityOutput,
|
|
2943
|
-
status: isHumanTaskComplete ? 'suspended' : 'running',
|
|
2944
|
-
currentStepId: currentActivity.id,
|
|
2945
|
-
};
|
|
2946
|
-
await this.updateInstanceFromState(request.instanceId, state);
|
|
2947
|
-
return {
|
|
2948
|
-
output: { outcome, userInput: request.output },
|
|
2949
|
-
outcomes: { [outcome]: true },
|
|
2950
|
-
nextTask: pendingTask,
|
|
2951
|
-
state,
|
|
2952
|
-
};
|
|
3016
|
+
// Load workflow definition
|
|
3017
|
+
const workflow = await this.getDefinition(state.workflowId);
|
|
3018
|
+
workflowForFlush = workflow;
|
|
3019
|
+
if (!workflow) {
|
|
3020
|
+
console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
|
|
3021
|
+
throw new Error(`Workflow definition not found: ${state.workflowId}`);
|
|
2953
3022
|
}
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
const
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
3023
|
+
const connections = workflow.graph.connections || [];
|
|
3024
|
+
// Consume bookmark for the activity being completed so only the next step has an active bookmark
|
|
3025
|
+
dirty = true;
|
|
3026
|
+
await this.consumeBookmark(request.instanceId, request.activityNode);
|
|
3027
|
+
const output = (request.output || {});
|
|
3028
|
+
const completedActivity = this.findActivityInGraph(workflow.graph, request.activityNode);
|
|
3029
|
+
// Resolve allowed outcomes from workflow definition and normalize outcome
|
|
3030
|
+
const allowedOutcomes = await this.getActivityAllowedOutcomes(workflow, request.activityNode);
|
|
3031
|
+
const coercedOutcome = this.coerceCreateEntityFormOutcome(completedActivity, request.outcome ?? 'Done', output);
|
|
3032
|
+
const outcome = this.normalizeOutcome(coercedOutcome, allowedOutcomes, request.activityNode);
|
|
3033
|
+
// Apply frontend output: only last activity output is kept (overwritten each step)
|
|
3034
|
+
let lastActivityOutput = output;
|
|
3035
|
+
// Apply outputToVariables for the completed frontend activity (e.g. leaveRequestForm → leaveRequestId: 'id')
|
|
3036
|
+
const variableUpdatesFromOutput = this.getVariableUpdatesFromOutput(workflow, request.activityNode, output);
|
|
3037
|
+
const formResultPromotions = this.buildPersistedEntityFormVariablePromotions(output, completedActivity?.name, outcome);
|
|
3038
|
+
const sessionUserFrontComplete = this.getSessionUser();
|
|
3039
|
+
const frontCompleteAudit = {
|
|
3040
|
+
[`${request.activityNode}_outcome`]: outcome,
|
|
3041
|
+
[`${request.activityNode}_activityOutput`]: output,
|
|
2969
3042
|
};
|
|
2970
|
-
if (
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3043
|
+
if (sessionUserFrontComplete?.id) {
|
|
3044
|
+
frontCompleteAudit[`${request.activityNode}_completedByUserId`] =
|
|
3045
|
+
sessionUserFrontComplete.id;
|
|
3046
|
+
frontCompleteAudit[`${request.activityNode}_completedByLabel`] =
|
|
3047
|
+
sessionUserFrontComplete.title ||
|
|
3048
|
+
sessionUserFrontComplete.name ||
|
|
3049
|
+
sessionUserFrontComplete.id;
|
|
2977
3050
|
}
|
|
2978
3051
|
else {
|
|
2979
|
-
|
|
3052
|
+
frontCompleteAudit[`${request.activityNode}_completedByLabel`] =
|
|
2980
3053
|
'System';
|
|
2981
3054
|
}
|
|
2982
3055
|
state = {
|
|
2983
3056
|
...state,
|
|
2984
3057
|
variables: {
|
|
2985
3058
|
...state.variables,
|
|
2986
|
-
...
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
...backendAuditComplete,
|
|
3059
|
+
...output, // Merge frontend output into variables
|
|
3060
|
+
...formResultPromotions, // Promote capture-form fields (e.g. effectiveDate) onto variables
|
|
3061
|
+
...variableUpdatesFromOutput, // Map output paths to variable names (e.g. id → leaveRequestId)
|
|
3062
|
+
...frontCompleteAudit,
|
|
2991
3063
|
},
|
|
2992
3064
|
lastActivityOutput,
|
|
2993
|
-
currentStepId: currentActivity.id,
|
|
2994
3065
|
lastUpdated: new Date(),
|
|
2995
3066
|
};
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
if (!nextConn) {
|
|
3067
|
+
// Find next activity based on outcome from frontend (use normalized outcome)
|
|
3068
|
+
const nextConnection = this.findWorkflowConnection(connections, request.activityNode, outcome);
|
|
3069
|
+
if (!nextConnection) {
|
|
3000
3070
|
// No next activity → workflow completed
|
|
3001
3071
|
state = {
|
|
3002
3072
|
...state,
|
|
3003
3073
|
status: 'completed',
|
|
3004
3074
|
currentStepId: undefined,
|
|
3005
3075
|
output: state.variables,
|
|
3006
|
-
lastActivityOutput,
|
|
3007
3076
|
};
|
|
3008
3077
|
await this.updateInstanceFromState(request.instanceId, state);
|
|
3009
3078
|
return {
|
|
3010
|
-
output:
|
|
3011
|
-
outcomes: {
|
|
3079
|
+
output: { outcome, userInput: request.output },
|
|
3080
|
+
outcomes: { [outcome]: true },
|
|
3012
3081
|
nextTask: null,
|
|
3013
3082
|
state,
|
|
3014
3083
|
};
|
|
3015
3084
|
}
|
|
3016
|
-
//
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3085
|
+
// 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
|
|
3086
|
+
let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
|
|
3087
|
+
let executionCount = 0;
|
|
3088
|
+
const maxExecutions = 500;
|
|
3089
|
+
if (!currentActivity) {
|
|
3090
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
|
|
3020
3091
|
state = {
|
|
3021
3092
|
...state,
|
|
3022
3093
|
status: 'error',
|
|
3023
3094
|
currentStepId: undefined,
|
|
3024
3095
|
};
|
|
3025
3096
|
await this.updateInstanceFromState(request.instanceId, state);
|
|
3026
|
-
throw new Error(`Next activity '${
|
|
3097
|
+
throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
|
|
3027
3098
|
}
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3099
|
+
// Only lastActivityOutput is persisted; used as scope.outputs for expressions.
|
|
3100
|
+
while (currentActivity && executionCount < maxExecutions) {
|
|
3101
|
+
executionCount++;
|
|
3102
|
+
// Map activity name (registry key) to activity type
|
|
3103
|
+
const activityType = currentActivity.name;
|
|
3104
|
+
// Determine execution mode (async)
|
|
3105
|
+
const executionMode = await this.determineExecutionMode(activityType, currentActivity);
|
|
3106
|
+
// Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
|
|
3107
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
3108
|
+
let taskTypeComplete = currentActivity.metadata?.['taskType'] ??
|
|
3109
|
+
activityDef?.taskType ??
|
|
3110
|
+
'ui-activity';
|
|
3111
|
+
// Defensive: human-task and cartable must suspend (same as run())
|
|
3112
|
+
if (this.isTaskBoardSuspendGraphActivity(activityType)) {
|
|
3113
|
+
if (activityDef?.taskType === 'human-task')
|
|
3114
|
+
taskTypeComplete = 'human-task';
|
|
3115
|
+
}
|
|
3116
|
+
const effectiveExecutionModeComplete = taskTypeComplete === 'human-task' && executionMode === 'backend'
|
|
3117
|
+
? 'frontend'
|
|
3118
|
+
: this.isTaskBoardSuspendGraphActivity(activityType) &&
|
|
3119
|
+
(activityDef?.taskType === 'human-task' ||
|
|
3120
|
+
taskTypeComplete === 'human-task')
|
|
3121
|
+
? executionMode === 'backend'
|
|
3122
|
+
? 'frontend'
|
|
3123
|
+
: executionMode
|
|
3124
|
+
: executionMode;
|
|
3125
|
+
// If frontend activity → stop and return as nextTask
|
|
3126
|
+
if (effectiveExecutionModeComplete === 'frontend' ||
|
|
3127
|
+
effectiveExecutionModeComplete === 'both') {
|
|
3128
|
+
const pendingTask = await this.createTaskFromActivity(currentActivity, state);
|
|
3129
|
+
const resolvedTaskTypeComplete = taskTypeComplete === 'human-task' ||
|
|
3130
|
+
pendingTask.taskType === 'human-task'
|
|
3131
|
+
? 'human-task'
|
|
3132
|
+
: pendingTask.taskType;
|
|
3133
|
+
// Extract assignment and task metadata from activity inputs (for human-task activities)
|
|
3134
|
+
let bookmarkPayload = {
|
|
3135
|
+
activityType,
|
|
3136
|
+
activityName: currentActivity.name,
|
|
3137
|
+
taskToken: pendingTask.taskToken, // Store taskToken for resume
|
|
3138
|
+
taskType: resolvedTaskTypeComplete,
|
|
3139
|
+
};
|
|
3140
|
+
// For human-task activities, extract assignment and task metadata from inputs
|
|
3141
|
+
if (resolvedTaskTypeComplete === 'human-task' && pendingTask.input) {
|
|
3142
|
+
const inputs = pendingTask.input;
|
|
3143
|
+
// Assignment metadata (normalize to array; expression may return single id)
|
|
3144
|
+
bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
|
|
3145
|
+
bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
|
|
3146
|
+
bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
|
|
3147
|
+
// Task metadata
|
|
3148
|
+
bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
|
|
3149
|
+
bookmarkPayload['description'] = inputs['description'] || null;
|
|
3150
|
+
bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
|
|
3151
|
+
bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
|
|
3152
|
+
// UI configuration
|
|
3153
|
+
bookmarkPayload['layout'] = inputs['layout'] || null;
|
|
3154
|
+
bookmarkPayload['actions'] = inputs['actions'] || null;
|
|
3155
|
+
bookmarkPayload['context'] = inputs['context'] || {};
|
|
3156
|
+
}
|
|
3157
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
|
|
3158
|
+
bookmarkPayload['assignedUserIds'] = [];
|
|
3159
|
+
}
|
|
3160
|
+
if (resolvedTaskTypeComplete === 'human-task') {
|
|
3161
|
+
const htInputs = pendingTask.input;
|
|
3162
|
+
this.logHumanTaskBookmarkAssignmentTrace({
|
|
3163
|
+
phase: 'engine-frontActivityComplete',
|
|
3164
|
+
instanceId: request.instanceId,
|
|
3165
|
+
workflowDefinitionName: workflow.name,
|
|
3166
|
+
activityId: currentActivity.id,
|
|
3167
|
+
activityType,
|
|
3168
|
+
rawAssignedUserIds: htInputs?.['assignedUserIds'],
|
|
3169
|
+
normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
|
|
3170
|
+
variables: state.variables,
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
// Create bookmark for frontend activity
|
|
3174
|
+
// Store all metadata in bookmark payload (replaces Work Item)
|
|
3175
|
+
const bookmarkId = await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
|
|
3176
|
+
// Update state: task-board activities → suspended, else running (form/popup)
|
|
3177
|
+
const isHumanTaskComplete = this.isTaskBoardSuspendGraphActivity(activityType);
|
|
3178
|
+
state = {
|
|
3179
|
+
...state,
|
|
3180
|
+
lastActivityOutput,
|
|
3181
|
+
status: isHumanTaskComplete ? 'suspended' : 'running',
|
|
3182
|
+
currentStepId: currentActivity.id,
|
|
3183
|
+
};
|
|
3184
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
3185
|
+
return {
|
|
3186
|
+
output: { outcome, userInput: request.output },
|
|
3187
|
+
outcomes: { [outcome]: true },
|
|
3188
|
+
nextTask: pendingTask,
|
|
3189
|
+
state,
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
const parallelWrites = await this.runIndependentEntityWriteBatch({
|
|
3193
|
+
workflow,
|
|
3194
|
+
connections,
|
|
3195
|
+
graph: workflow.graph,
|
|
3196
|
+
instanceId: request.instanceId,
|
|
3197
|
+
activity: currentActivity,
|
|
3198
|
+
state,
|
|
3199
|
+
lastActivityOutput,
|
|
3200
|
+
});
|
|
3201
|
+
if (parallelWrites) {
|
|
3202
|
+
state = parallelWrites.state;
|
|
3203
|
+
lastActivityOutput = parallelWrites.lastActivityOutput;
|
|
3204
|
+
executionCount += parallelWrites.executedCount - 1;
|
|
3205
|
+
if (!parallelWrites.nextActivity) {
|
|
3206
|
+
return {
|
|
3207
|
+
output: state.variables,
|
|
3208
|
+
outcomes: { Done: true },
|
|
3209
|
+
nextTask: null,
|
|
3210
|
+
state,
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
currentActivity = parallelWrites.nextActivity;
|
|
3214
|
+
continue;
|
|
3215
|
+
}
|
|
3216
|
+
// Backend activity → execute it
|
|
3217
|
+
const lastOutputBeforeBackendComplete = lastActivityOutput;
|
|
3218
|
+
const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutput);
|
|
3219
|
+
lastActivityOutput = executionResult.output;
|
|
3220
|
+
await this.maybeLinkCreatedEntityRecord(request.instanceId, currentActivity, state, lastOutputBeforeBackendComplete, executionResult.output);
|
|
3221
|
+
// set-variable activity writes variables directly in output; it has no outputToVariables
|
|
3222
|
+
const isSetVariableComplete = currentActivity.name === 'workflow-activity:set-variable';
|
|
3223
|
+
const variableUpdatesComplete = isSetVariableComplete
|
|
3224
|
+
? executionResult.output
|
|
3225
|
+
: this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
|
|
3226
|
+
const backendOutcome = Object.keys(executionResult.outcomes)[0] || 'Done';
|
|
3227
|
+
const sessionUserComplete = this.getSessionUser();
|
|
3228
|
+
const backendAuditComplete = {
|
|
3229
|
+
[`${currentActivity.id}_outcome`]: backendOutcome,
|
|
3230
|
+
[`${currentActivity.id}_activityOutput`]: executionResult.output,
|
|
3231
|
+
};
|
|
3232
|
+
if (sessionUserComplete?.id) {
|
|
3233
|
+
backendAuditComplete[`${currentActivity.id}_completedByUserId`] =
|
|
3234
|
+
sessionUserComplete.id;
|
|
3235
|
+
backendAuditComplete[`${currentActivity.id}_completedByLabel`] =
|
|
3236
|
+
sessionUserComplete.title ||
|
|
3237
|
+
sessionUserComplete.name ||
|
|
3238
|
+
sessionUserComplete.id;
|
|
3239
|
+
}
|
|
3240
|
+
else {
|
|
3241
|
+
backendAuditComplete[`${currentActivity.id}_completedByLabel`] =
|
|
3242
|
+
'System';
|
|
3243
|
+
}
|
|
3244
|
+
state = {
|
|
3245
|
+
...state,
|
|
3246
|
+
variables: {
|
|
3247
|
+
...state.variables,
|
|
3248
|
+
...(typeof variableUpdatesComplete === 'object' &&
|
|
3249
|
+
variableUpdatesComplete !== null
|
|
3250
|
+
? variableUpdatesComplete
|
|
3251
|
+
: {}),
|
|
3252
|
+
...backendAuditComplete,
|
|
3253
|
+
},
|
|
3254
|
+
lastActivityOutput,
|
|
3255
|
+
currentStepId: currentActivity.id,
|
|
3256
|
+
lastUpdated: new Date(),
|
|
3257
|
+
};
|
|
3258
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
3259
|
+
// Find next activity based on outcome (backend execution result)
|
|
3260
|
+
const nextConn = this.findWorkflowConnection(connections, currentActivity.id, backendOutcome);
|
|
3261
|
+
if (!nextConn) {
|
|
3262
|
+
// No next activity → workflow completed
|
|
3263
|
+
state = {
|
|
3264
|
+
...state,
|
|
3265
|
+
status: 'completed',
|
|
3266
|
+
currentStepId: undefined,
|
|
3267
|
+
output: state.variables,
|
|
3268
|
+
lastActivityOutput,
|
|
3269
|
+
};
|
|
3270
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
3271
|
+
return {
|
|
3272
|
+
output: state.variables,
|
|
3273
|
+
outcomes: { Done: true },
|
|
3274
|
+
nextTask: null,
|
|
3275
|
+
state,
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
// Find next activity
|
|
3279
|
+
const nextActivity = this.findActivityInGraph(workflow.graph, nextConn.target.activtyName);
|
|
3280
|
+
if (!nextActivity) {
|
|
3281
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConn.target.activtyName}' not found in graph`);
|
|
3282
|
+
state = {
|
|
3283
|
+
...state,
|
|
3284
|
+
status: 'error',
|
|
3285
|
+
currentStepId: undefined,
|
|
3286
|
+
};
|
|
3287
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
3288
|
+
throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
|
|
3289
|
+
}
|
|
3290
|
+
currentActivity = nextActivity;
|
|
3291
|
+
}
|
|
3292
|
+
// If we exit loop due to max executions, mark as error
|
|
3293
|
+
if (executionCount >= maxExecutions) {
|
|
3294
|
+
console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
|
|
3295
|
+
state = {
|
|
3296
|
+
...state,
|
|
3297
|
+
status: 'error',
|
|
3298
|
+
currentStepId: undefined,
|
|
3299
|
+
};
|
|
3300
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
3301
|
+
throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
|
|
3302
|
+
}
|
|
3303
|
+
// If we exit loop, workflow is complete
|
|
3033
3304
|
state = {
|
|
3034
3305
|
...state,
|
|
3035
|
-
status: '
|
|
3306
|
+
status: 'completed',
|
|
3036
3307
|
currentStepId: undefined,
|
|
3308
|
+
output: state.variables,
|
|
3309
|
+
lastActivityOutput,
|
|
3037
3310
|
};
|
|
3038
3311
|
await this.updateInstanceFromState(request.instanceId, state);
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
output: state.variables,
|
|
3047
|
-
lastActivityOutput,
|
|
3048
|
-
};
|
|
3049
|
-
await this.updateInstanceFromState(request.instanceId, state);
|
|
3050
|
-
return {
|
|
3051
|
-
output: state.variables,
|
|
3052
|
-
outcomes: { Done: true },
|
|
3053
|
-
nextTask: null,
|
|
3054
|
-
state,
|
|
3055
|
-
};
|
|
3312
|
+
return {
|
|
3313
|
+
output: state.variables,
|
|
3314
|
+
outcomes: { Done: true },
|
|
3315
|
+
nextTask: null,
|
|
3316
|
+
state,
|
|
3317
|
+
};
|
|
3318
|
+
}));
|
|
3056
3319
|
}
|
|
3057
3320
|
catch (error) {
|
|
3058
3321
|
console.error('[BackendWorkflowEngineService] ❌ Error completing front activity:', error);
|
|
3059
3322
|
throw error; // Re-throw to let caller handle
|
|
3060
3323
|
}
|
|
3324
|
+
finally {
|
|
3325
|
+
if (dirty) {
|
|
3326
|
+
const current = await this.store.getInstance(request.instanceId);
|
|
3327
|
+
if (current) {
|
|
3328
|
+
await this.writeInstanceProjection(current, workflowForFlush, false);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3061
3332
|
}
|
|
3062
3333
|
//#endregion
|
|
3063
3334
|
//#region ---- Stale suspension cleanup (app startup) ----
|
|
@@ -3139,6 +3410,10 @@ exports.BackendWorkflowEngineService = BackendWorkflowEngineService;
|
|
|
3139
3410
|
* Default outcome used when the requested outcome is not in the allowed list (standard scenario).
|
|
3140
3411
|
*/
|
|
3141
3412
|
BackendWorkflowEngineService.DEFAULT_UNMATCHED_OUTCOME = 'Done';
|
|
3413
|
+
BackendWorkflowEngineService.ENTITY_WRITE_ACTIVITY_NAMES = new Set([
|
|
3414
|
+
'workflow-activity:entity-create',
|
|
3415
|
+
'workflow-activity:entity-update',
|
|
3416
|
+
]);
|
|
3142
3417
|
exports.BackendWorkflowEngineService = BackendWorkflowEngineService = BackendWorkflowEngineService_1 = tslib_1.__decorate([
|
|
3143
3418
|
(0, common_1.Injectable)(),
|
|
3144
3419
|
tslib_1.__metadata("design:paramtypes", [workflow_execution_store_service_1.WorkflowExecutionStoreService,
|