@acorex-platform/framework-server 0.0.10
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/README.md +17 -0
- package/core/package.json +15 -0
- package/core/src/index.d.ts +2 -0
- package/core/src/index.js +6 -0
- package/core/src/index.js.map +1 -0
- package/core/src/lib/core.module.d.ts +2 -0
- package/core/src/lib/core.module.js +16 -0
- package/core/src/lib/core.module.js.map +1 -0
- package/core/src/lib/entity-storage/backends/logging.entity-storage.backend.d.ts +17 -0
- package/core/src/lib/entity-storage/backends/logging.entity-storage.backend.js +57 -0
- package/core/src/lib/entity-storage/backends/logging.entity-storage.backend.js.map +1 -0
- package/core/src/lib/entity-storage/entity-storage-backend.interface.d.ts +1 -0
- package/core/src/lib/entity-storage/entity-storage-backend.interface.js +3 -0
- package/core/src/lib/entity-storage/entity-storage-backend.interface.js.map +1 -0
- package/core/src/lib/entity-storage/entity-storage-logging.module.d.ts +4 -0
- package/core/src/lib/entity-storage/entity-storage-logging.module.js +28 -0
- package/core/src/lib/entity-storage/entity-storage-logging.module.js.map +1 -0
- package/core/src/lib/entity-storage/entity-storage-middleware.service.d.ts +45 -0
- package/core/src/lib/entity-storage/entity-storage-middleware.service.js +247 -0
- package/core/src/lib/entity-storage/entity-storage-middleware.service.js.map +1 -0
- package/core/src/lib/entity-storage/entity-storage-request-context.d.ts +6 -0
- package/core/src/lib/entity-storage/entity-storage-request-context.js +25 -0
- package/core/src/lib/entity-storage/entity-storage-request-context.js.map +1 -0
- package/core/src/lib/entity-storage/entity-storage.controller.d.ts +14 -0
- package/core/src/lib/entity-storage/entity-storage.controller.js +137 -0
- package/core/src/lib/entity-storage/entity-storage.controller.js.map +1 -0
- package/core/src/lib/entity-storage/entity-storage.module.d.ts +8 -0
- package/core/src/lib/entity-storage/entity-storage.module.js +47 -0
- package/core/src/lib/entity-storage/entity-storage.module.js.map +1 -0
- package/core/src/lib/entity-storage/entity-storage.tokens.d.ts +2 -0
- package/core/src/lib/entity-storage/entity-storage.tokens.js +7 -0
- package/core/src/lib/entity-storage/entity-storage.tokens.js.map +1 -0
- package/core/src/lib/entity-storage/entity-storage.types.d.ts +41 -0
- package/core/src/lib/entity-storage/entity-storage.types.js +4 -0
- package/core/src/lib/entity-storage/entity-storage.types.js.map +1 -0
- package/core/src/lib/entity-storage/errors/middleware-abort.error.d.ts +18 -0
- package/core/src/lib/entity-storage/errors/middleware-abort.error.js +36 -0
- package/core/src/lib/entity-storage/errors/middleware-abort.error.js.map +1 -0
- package/core/src/lib/entity-storage/index.d.ts +14 -0
- package/core/src/lib/entity-storage/index.js +18 -0
- package/core/src/lib/entity-storage/index.js.map +1 -0
- package/core/src/lib/entity-storage/middlewares/category-hierarchy-filter.middleware.d.ts +10 -0
- package/core/src/lib/entity-storage/middlewares/category-hierarchy-filter.middleware.js +57 -0
- package/core/src/lib/entity-storage/middlewares/category-hierarchy-filter.middleware.js.map +1 -0
- package/core/src/lib/entity-storage/middlewares/entity-storage-request-context.middleware.d.ts +9 -0
- package/core/src/lib/entity-storage/middlewares/entity-storage-request-context.middleware.js +22 -0
- package/core/src/lib/entity-storage/middlewares/entity-storage-request-context.middleware.js.map +1 -0
- package/core/src/lib/entity-storage/middlewares/request-log.middleware.d.ts +11 -0
- package/core/src/lib/entity-storage/middlewares/request-log.middleware.js +26 -0
- package/core/src/lib/entity-storage/middlewares/request-log.middleware.js.map +1 -0
- package/core/src/lib/entity-storage/utils/entity-route.util.d.ts +24 -0
- package/core/src/lib/entity-storage/utils/entity-route.util.js +96 -0
- package/core/src/lib/entity-storage/utils/entity-route.util.js.map +1 -0
- package/domain/abstract/package.json +11 -0
- package/domain/abstract/src/index.d.ts +17 -0
- package/domain/abstract/src/index.js +30 -0
- package/domain/abstract/src/index.js.map +1 -0
- package/domain/abstract/src/lib/definitions/aggregate-definition.types.d.ts +13 -0
- package/domain/abstract/src/lib/definitions/aggregate-definition.types.js +3 -0
- package/domain/abstract/src/lib/definitions/aggregate-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/base.types.d.ts +32 -0
- package/domain/abstract/src/lib/definitions/base.types.js +17 -0
- package/domain/abstract/src/lib/definitions/base.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/command-registry.types.d.ts +16 -0
- package/domain/abstract/src/lib/definitions/command-registry.types.js +3 -0
- package/domain/abstract/src/lib/definitions/command-registry.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/entity-definition.types.d.ts +66 -0
- package/domain/abstract/src/lib/definitions/entity-definition.types.js +10 -0
- package/domain/abstract/src/lib/definitions/entity-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/interface-definition.types.d.ts +27 -0
- package/domain/abstract/src/lib/definitions/interface-definition.types.js +3 -0
- package/domain/abstract/src/lib/definitions/interface-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/menu-definition.types.d.ts +35 -0
- package/domain/abstract/src/lib/definitions/menu-definition.types.js +3 -0
- package/domain/abstract/src/lib/definitions/menu-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/module-definition.types.d.ts +6 -0
- package/domain/abstract/src/lib/definitions/module-definition.types.js +3 -0
- package/domain/abstract/src/lib/definitions/module-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/permission-definition.types.d.ts +24 -0
- package/domain/abstract/src/lib/definitions/permission-definition.types.js +3 -0
- package/domain/abstract/src/lib/definitions/permission-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/plugin-definition.types.d.ts +14 -0
- package/domain/abstract/src/lib/definitions/plugin-definition.types.js +3 -0
- package/domain/abstract/src/lib/definitions/plugin-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/property-definition.types.d.ts +32 -0
- package/domain/abstract/src/lib/definitions/property-definition.types.js +3 -0
- package/domain/abstract/src/lib/definitions/property-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/query-registry.types.d.ts +15 -0
- package/domain/abstract/src/lib/definitions/query-registry.types.js +3 -0
- package/domain/abstract/src/lib/definitions/query-registry.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/relation-definition.types.d.ts +26 -0
- package/domain/abstract/src/lib/definitions/relation-definition.types.js +16 -0
- package/domain/abstract/src/lib/definitions/relation-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/validation-definition.types.d.ts +19 -0
- package/domain/abstract/src/lib/definitions/validation-definition.types.js +3 -0
- package/domain/abstract/src/lib/definitions/validation-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/definitions/widget-catalog.types.d.ts +7 -0
- package/domain/abstract/src/lib/definitions/widget-catalog.types.js +3 -0
- package/domain/abstract/src/lib/definitions/widget-catalog.types.js.map +1 -0
- package/domain/abstract/src/lib/interfaces/domain-definition-loader.interface.d.ts +10 -0
- package/domain/abstract/src/lib/interfaces/domain-definition-loader.interface.js +9 -0
- package/domain/abstract/src/lib/interfaces/domain-definition-loader.interface.js.map +1 -0
- package/domain/abstract/src/lib/interfaces/domain-definition.types.d.ts +12 -0
- package/domain/abstract/src/lib/interfaces/domain-definition.types.js +3 -0
- package/domain/abstract/src/lib/interfaces/domain-definition.types.js.map +1 -0
- package/domain/abstract/src/lib/types/domain-core.types.d.ts +15 -0
- package/domain/abstract/src/lib/types/domain-core.types.js +5 -0
- package/domain/abstract/src/lib/types/domain-core.types.js.map +1 -0
- package/domain/providers/package.json +19 -0
- package/domain/providers/src/index.d.ts +22 -0
- package/domain/providers/src/index.js +26 -0
- package/domain/providers/src/index.js.map +1 -0
- package/domain/providers/src/lib/domain-definition-jql-schema.d.ts +17 -0
- package/domain/providers/src/lib/domain-definition-jql-schema.js +95 -0
- package/domain/providers/src/lib/domain-definition-jql-schema.js.map +1 -0
- package/domain/providers/src/lib/domain-definition-jql-sources.d.ts +10 -0
- package/domain/providers/src/lib/domain-definition-jql-sources.js +82 -0
- package/domain/providers/src/lib/domain-definition-jql-sources.js.map +1 -0
- package/domain/providers/src/lib/domain-definition-jql.loader.d.ts +37 -0
- package/domain/providers/src/lib/domain-definition-jql.loader.js +219 -0
- package/domain/providers/src/lib/domain-definition-jql.loader.js.map +1 -0
- package/domain/providers/src/lib/domain-definition-store.constants.d.ts +17 -0
- package/domain/providers/src/lib/domain-definition-store.constants.js +22 -0
- package/domain/providers/src/lib/domain-definition-store.constants.js.map +1 -0
- package/domain/providers/src/lib/domain-entity-schema.loader.d.ts +11 -0
- package/domain/providers/src/lib/domain-entity-schema.loader.js +38 -0
- package/domain/providers/src/lib/domain-entity-schema.loader.js.map +1 -0
- package/domain/providers/src/lib/domain-entity-validator.module.d.ts +6 -0
- package/domain/providers/src/lib/domain-entity-validator.module.js +22 -0
- package/domain/providers/src/lib/domain-entity-validator.module.js.map +1 -0
- package/domain/providers/src/lib/domain-jql-instance.resolve.d.ts +6 -0
- package/domain/providers/src/lib/domain-jql-instance.resolve.js +18 -0
- package/domain/providers/src/lib/domain-jql-instance.resolve.js.map +1 -0
- package/domain/providers/src/lib/domain-middleware-entity-storage.bootstrap.d.ts +33 -0
- package/domain/providers/src/lib/domain-middleware-entity-storage.bootstrap.js +73 -0
- package/domain/providers/src/lib/domain-middleware-entity-storage.bootstrap.js.map +1 -0
- package/domain/providers/src/lib/domain-middleware.module.d.ts +6 -0
- package/domain/providers/src/lib/domain-middleware.module.js +80 -0
- package/domain/providers/src/lib/domain-middleware.module.js.map +1 -0
- package/domain/providers/src/lib/domain-provider.module.d.ts +7 -0
- package/domain/providers/src/lib/domain-provider.module.js +62 -0
- package/domain/providers/src/lib/domain-provider.module.js.map +1 -0
- package/domain/providers/src/lib/domain-provider.resolve.d.ts +6 -0
- package/domain/providers/src/lib/domain-provider.resolve.js +31 -0
- package/domain/providers/src/lib/domain-provider.resolve.js.map +1 -0
- package/domain/providers/src/lib/domain-provider.types.d.ts +1 -0
- package/domain/providers/src/lib/domain-provider.types.js +5 -0
- package/domain/providers/src/lib/domain-provider.types.js.map +1 -0
- package/domain/providers/src/lib/middlewares/category-resolver.middleware.d.ts +25 -0
- package/domain/providers/src/lib/middlewares/category-resolver.middleware.js +159 -0
- package/domain/providers/src/lib/middlewares/category-resolver.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/child-count.middleware.d.ts +14 -0
- package/domain/providers/src/lib/middlewares/child-count.middleware.js +74 -0
- package/domain/providers/src/lib/middlewares/child-count.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/domain-validation.middleware.d.ts +15 -0
- package/domain/providers/src/lib/middlewares/domain-validation.middleware.js +58 -0
- package/domain/providers/src/lib/middlewares/domain-validation.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/entity-get-by-diagnostic.middleware.d.ts +9 -0
- package/domain/providers/src/lib/middlewares/entity-get-by-diagnostic.middleware.js +27 -0
- package/domain/providers/src/lib/middlewares/entity-get-by-diagnostic.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/entity-mutation-diagnostic.middleware.d.ts +9 -0
- package/domain/providers/src/lib/middlewares/entity-mutation-diagnostic.middleware.js +27 -0
- package/domain/providers/src/lib/middlewares/entity-mutation-diagnostic.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/entity-query-filter-diagnostic.middleware.d.ts +9 -0
- package/domain/providers/src/lib/middlewares/entity-query-filter-diagnostic.middleware.js +28 -0
- package/domain/providers/src/lib/middlewares/entity-query-filter-diagnostic.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/lookup-resolver.middleware.d.ts +15 -0
- package/domain/providers/src/lib/middlewares/lookup-resolver.middleware.js +99 -0
- package/domain/providers/src/lib/middlewares/lookup-resolver.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/merge-detail-relation.middleware.d.ts +20 -0
- package/domain/providers/src/lib/middlewares/merge-detail-relation.middleware.js +263 -0
- package/domain/providers/src/lib/middlewares/merge-detail-relation.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/primary.middleware.d.ts +15 -0
- package/domain/providers/src/lib/middlewares/primary.middleware.js +94 -0
- package/domain/providers/src/lib/middlewares/primary.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/related-entity-cascade-delete.middleware.d.ts +15 -0
- package/domain/providers/src/lib/middlewares/related-entity-cascade-delete.middleware.js +47 -0
- package/domain/providers/src/lib/middlewares/related-entity-cascade-delete.middleware.js.map +1 -0
- package/domain/providers/src/lib/middlewares/status-default.middleware.d.ts +15 -0
- package/domain/providers/src/lib/middlewares/status-default.middleware.js +61 -0
- package/domain/providers/src/lib/middlewares/status-default.middleware.js.map +1 -0
- package/domain/providers/src/lib/services/related-entity-cascade.service.d.ts +26 -0
- package/domain/providers/src/lib/services/related-entity-cascade.service.js +166 -0
- package/domain/providers/src/lib/services/related-entity-cascade.service.js.map +1 -0
- package/domain/providers/src/lib/utils/domain-path.util.d.ts +4 -0
- package/domain/providers/src/lib/utils/domain-path.util.js +42 -0
- package/domain/providers/src/lib/utils/domain-path.util.js.map +1 -0
- package/domain/providers/src/lib/utils/domain-record.util.d.ts +4 -0
- package/domain/providers/src/lib/utils/domain-record.util.js +52 -0
- package/domain/providers/src/lib/utils/domain-record.util.js.map +1 -0
- package/domain/providers/src/lib/utils/expression-evaluator.util.d.ts +11 -0
- package/domain/providers/src/lib/utils/expression-evaluator.util.js +74 -0
- package/domain/providers/src/lib/utils/expression-evaluator.util.js.map +1 -0
- package/domain/providers/src/lib/utils/in-memory-query.util.d.ts +13 -0
- package/domain/providers/src/lib/utils/in-memory-query.util.js +91 -0
- package/domain/providers/src/lib/utils/in-memory-query.util.js.map +1 -0
- package/domain/providers/src/lib/utils/merge-detail-query.util.d.ts +28 -0
- package/domain/providers/src/lib/utils/merge-detail-query.util.js +119 -0
- package/domain/providers/src/lib/utils/merge-detail-query.util.js.map +1 -0
- package/domain/providers/src/lib/utils/parse-related-entity-cascade.util.d.ts +16 -0
- package/domain/providers/src/lib/utils/parse-related-entity-cascade.util.js +109 -0
- package/domain/providers/src/lib/utils/parse-related-entity-cascade.util.js.map +1 -0
- package/domain/providers/src/lib/utils/policy-field.util.d.ts +3 -0
- package/domain/providers/src/lib/utils/policy-field.util.js +95 -0
- package/domain/providers/src/lib/utils/policy-field.util.js.map +1 -0
- package/domain/providers/src/lib/validators/domain-entity.validator.d.ts +32 -0
- package/domain/providers/src/lib/validators/domain-entity.validator.js +281 -0
- package/domain/providers/src/lib/validators/domain-entity.validator.js.map +1 -0
- package/domain/seed/package.json +17 -0
- package/domain/seed/src/index.d.ts +5 -0
- package/domain/seed/src/index.js +9 -0
- package/domain/seed/src/index.js.map +1 -0
- package/domain/seed/src/lib/axp-entity-to-definition.converter.d.ts +9 -0
- package/domain/seed/src/lib/axp-entity-to-definition.converter.js +64 -0
- package/domain/seed/src/lib/axp-entity-to-definition.converter.js.map +1 -0
- package/domain/seed/src/lib/category-companion-entity.builder.d.ts +30 -0
- package/domain/seed/src/lib/category-companion-entity.builder.js +178 -0
- package/domain/seed/src/lib/category-companion-entity.builder.js.map +1 -0
- package/domain/seed/src/lib/collect-entity-factories.util.d.ts +7 -0
- package/domain/seed/src/lib/collect-entity-factories.util.js +25 -0
- package/domain/seed/src/lib/collect-entity-factories.util.js.map +1 -0
- package/domain/seed/src/lib/domain-definition-seed.provider.d.ts +13 -0
- package/domain/seed/src/lib/domain-definition-seed.provider.js +75 -0
- package/domain/seed/src/lib/domain-definition-seed.provider.js.map +1 -0
- package/domain/seed/src/lib/domain-seed.types.d.ts +8 -0
- package/domain/seed/src/lib/domain-seed.types.js +4 -0
- package/domain/seed/src/lib/domain-seed.types.js.map +1 -0
- package/file-storage/abstract/package.json +15 -0
- package/file-storage/abstract/src/index.d.ts +5 -0
- package/file-storage/abstract/src/index.js +9 -0
- package/file-storage/abstract/src/index.js.map +1 -0
- package/file-storage/abstract/src/lib/file-storage-backend.interface.d.ts +12 -0
- package/file-storage/abstract/src/lib/file-storage-backend.interface.js +4 -0
- package/file-storage/abstract/src/lib/file-storage-backend.interface.js.map +1 -0
- package/file-storage/abstract/src/lib/file-storage-engine.interface.d.ts +14 -0
- package/file-storage/abstract/src/lib/file-storage-engine.interface.js +5 -0
- package/file-storage/abstract/src/lib/file-storage-engine.interface.js.map +1 -0
- package/file-storage/abstract/src/lib/file-storage.mapper.d.ts +8 -0
- package/file-storage/abstract/src/lib/file-storage.mapper.js +54 -0
- package/file-storage/abstract/src/lib/file-storage.mapper.js.map +1 -0
- package/file-storage/abstract/src/lib/file-storage.tokens.d.ts +10 -0
- package/file-storage/abstract/src/lib/file-storage.tokens.js +16 -0
- package/file-storage/abstract/src/lib/file-storage.tokens.js.map +1 -0
- package/file-storage/abstract/src/lib/file-storage.types.d.ts +86 -0
- package/file-storage/abstract/src/lib/file-storage.types.js +4 -0
- package/file-storage/abstract/src/lib/file-storage.types.js.map +1 -0
- package/file-storage/database/package.json +17 -0
- package/file-storage/database/src/index.d.ts +3 -0
- package/file-storage/database/src/index.js +7 -0
- package/file-storage/database/src/index.js.map +1 -0
- package/file-storage/database/src/lib/database-file-storage-engine.module.d.ts +2 -0
- package/file-storage/database/src/lib/database-file-storage-engine.module.js +18 -0
- package/file-storage/database/src/lib/database-file-storage-engine.module.js.map +1 -0
- package/file-storage/database/src/lib/file-binary-blob-jql-source.d.ts +5 -0
- package/file-storage/database/src/lib/file-binary-blob-jql-source.js +40 -0
- package/file-storage/database/src/lib/file-binary-blob-jql-source.js.map +1 -0
- package/file-storage/database/src/lib/jql-binary-file-storage.engine.d.ts +13 -0
- package/file-storage/database/src/lib/jql-binary-file-storage.engine.js +59 -0
- package/file-storage/database/src/lib/jql-binary-file-storage.engine.js.map +1 -0
- package/file-storage/domain/package.json +14 -0
- package/file-storage/domain/src/index.d.ts +1 -0
- package/file-storage/domain/src/index.js +5 -0
- package/file-storage/domain/src/index.js.map +1 -0
- package/file-storage/domain/src/lib/contracts/root-config.d.ts +21 -0
- package/file-storage/domain/src/lib/contracts/root-config.js +26 -0
- package/file-storage/domain/src/lib/contracts/root-config.js.map +1 -0
- package/file-storage/domain/src/lib/file/file.entity.d.ts +2 -0
- package/file-storage/domain/src/lib/file/file.entity.js +145 -0
- package/file-storage/domain/src/lib/file/file.entity.js.map +1 -0
- package/file-storage/domain/src/lib/file/index.d.ts +1 -0
- package/file-storage/domain/src/lib/file/index.js +5 -0
- package/file-storage/domain/src/lib/file/index.js.map +1 -0
- package/file-storage/domain/src/lib/index.d.ts +2 -0
- package/file-storage/domain/src/lib/index.js +6 -0
- package/file-storage/domain/src/lib/index.js.map +1 -0
- package/file-storage/entity/package.json +19 -0
- package/file-storage/entity/src/index.d.ts +7 -0
- package/file-storage/entity/src/index.js +11 -0
- package/file-storage/entity/src/index.js.map +1 -0
- package/file-storage/entity/src/lib/entity-file-storage.module.d.ts +2 -0
- package/file-storage/entity/src/lib/entity-file-storage.module.js +19 -0
- package/file-storage/entity/src/lib/entity-file-storage.module.js.map +1 -0
- package/file-storage/entity/src/lib/file-entity-jql-source.d.ts +6 -0
- package/file-storage/entity/src/lib/file-entity-jql-source.js +55 -0
- package/file-storage/entity/src/lib/file-entity-jql-source.js.map +1 -0
- package/file-storage/entity/src/lib/file-entity.mapper.d.ts +3 -0
- package/file-storage/entity/src/lib/file-entity.mapper.js +52 -0
- package/file-storage/entity/src/lib/file-entity.mapper.js.map +1 -0
- package/file-storage/entity/src/lib/file-entity.repository.d.ts +15 -0
- package/file-storage/entity/src/lib/file-entity.repository.js +105 -0
- package/file-storage/entity/src/lib/file-entity.repository.js.map +1 -0
- package/file-storage/entity/src/lib/file-metadata.service.d.ts +15 -0
- package/file-storage/entity/src/lib/file-metadata.service.js +47 -0
- package/file-storage/entity/src/lib/file-metadata.service.js.map +1 -0
- package/file-storage/entity/src/lib/file-storage-engine.registry.d.ts +10 -0
- package/file-storage/entity/src/lib/file-storage-engine.registry.js +42 -0
- package/file-storage/entity/src/lib/file-storage-engine.registry.js.map +1 -0
- package/file-storage/entity/src/lib/file-storage-infrastructure.service.d.ts +24 -0
- package/file-storage/entity/src/lib/file-storage-infrastructure.service.js +145 -0
- package/file-storage/entity/src/lib/file-storage-infrastructure.service.js.map +1 -0
- package/file-storage/http/package.json +18 -0
- package/file-storage/http/src/index.d.ts +3 -0
- package/file-storage/http/src/index.js +7 -0
- package/file-storage/http/src/index.js.map +1 -0
- package/file-storage/http/src/lib/file-storage-facade.service.d.ts +37 -0
- package/file-storage/http/src/lib/file-storage-facade.service.js +58 -0
- package/file-storage/http/src/lib/file-storage-facade.service.js.map +1 -0
- package/file-storage/http/src/lib/file-storage-http.module.d.ts +2 -0
- package/file-storage/http/src/lib/file-storage-http.module.js +20 -0
- package/file-storage/http/src/lib/file-storage-http.module.js.map +1 -0
- package/file-storage/http/src/lib/file-storage.controller.d.ts +25 -0
- package/file-storage/http/src/lib/file-storage.controller.js +188 -0
- package/file-storage/http/src/lib/file-storage.controller.js.map +1 -0
- package/file-storage/local/package.json +16 -0
- package/file-storage/local/src/index.d.ts +4 -0
- package/file-storage/local/src/index.js +8 -0
- package/file-storage/local/src/index.js.map +1 -0
- package/file-storage/local/src/lib/filename.util.d.ts +14 -0
- package/file-storage/local/src/lib/filename.util.js +71 -0
- package/file-storage/local/src/lib/filename.util.js.map +1 -0
- package/file-storage/local/src/lib/local-disk-file-storage.engine.d.ts +9 -0
- package/file-storage/local/src/lib/local-disk-file-storage.engine.js +49 -0
- package/file-storage/local/src/lib/local-disk-file-storage.engine.js.map +1 -0
- package/file-storage/local/src/lib/local-file-storage-engine.module.d.ts +2 -0
- package/file-storage/local/src/lib/local-file-storage-engine.module.js +18 -0
- package/file-storage/local/src/lib/local-file-storage-engine.module.js.map +1 -0
- package/file-storage/local/src/lib/local-file-storage.config.d.ts +1 -0
- package/file-storage/local/src/lib/local-file-storage.config.js +10 -0
- package/file-storage/local/src/lib/local-file-storage.config.js.map +1 -0
- package/file-storage/providers/package.json +21 -0
- package/file-storage/providers/src/index.d.ts +5 -0
- package/file-storage/providers/src/index.js +8 -0
- package/file-storage/providers/src/index.js.map +1 -0
- package/file-storage/providers/src/lib/file-storage-jql-instance.resolve.d.ts +5 -0
- package/file-storage/providers/src/lib/file-storage-jql-instance.resolve.js +16 -0
- package/file-storage/providers/src/lib/file-storage-jql-instance.resolve.js.map +1 -0
- package/file-storage/providers/src/lib/file-storage-provider.module.d.ts +8 -0
- package/file-storage/providers/src/lib/file-storage-provider.module.js +70 -0
- package/file-storage/providers/src/lib/file-storage-provider.module.js.map +1 -0
- package/file-storage/providers/src/lib/file-storage-provider.resolve.d.ts +5 -0
- package/file-storage/providers/src/lib/file-storage-provider.resolve.js +20 -0
- package/file-storage/providers/src/lib/file-storage-provider.resolve.js.map +1 -0
- package/file-storage/providers/src/lib/file-storage-provider.types.d.ts +1 -0
- package/file-storage/providers/src/lib/file-storage-provider.types.js +5 -0
- package/file-storage/providers/src/lib/file-storage-provider.types.js.map +1 -0
- package/identifier/package.json +17 -0
- package/identifier/src/index.d.ts +5 -0
- package/identifier/src/index.js +9 -0
- package/identifier/src/index.js.map +1 -0
- package/identifier/src/lib/identifier-engine.service.d.ts +26 -0
- package/identifier/src/lib/identifier-engine.service.js +198 -0
- package/identifier/src/lib/identifier-engine.service.js.map +1 -0
- package/identifier/src/lib/identifier-rule.registry.d.ts +17 -0
- package/identifier/src/lib/identifier-rule.registry.js +84 -0
- package/identifier/src/lib/identifier-rule.registry.js.map +1 -0
- package/identifier/src/lib/identifier-sequence.store.d.ts +24 -0
- package/identifier/src/lib/identifier-sequence.store.js +40 -0
- package/identifier/src/lib/identifier-sequence.store.js.map +1 -0
- package/identifier/src/lib/identifier.module.d.ts +5 -0
- package/identifier/src/lib/identifier.module.js +58 -0
- package/identifier/src/lib/identifier.module.js.map +1 -0
- package/identifier/src/lib/identifier.providers.d.ts +25 -0
- package/identifier/src/lib/identifier.providers.js +108 -0
- package/identifier/src/lib/identifier.providers.js.map +1 -0
- package/identifier/src/lib/identifier.types.d.ts +31 -0
- package/identifier/src/lib/identifier.types.js +5 -0
- package/identifier/src/lib/identifier.types.js.map +1 -0
- package/identifier/src/lib/pattern-engine.d.ts +20 -0
- package/identifier/src/lib/pattern-engine.js +70 -0
- package/identifier/src/lib/pattern-engine.js.map +1 -0
- package/identifier/src/lib/token-registry.service.d.ts +8 -0
- package/identifier/src/lib/token-registry.service.js +27 -0
- package/identifier/src/lib/token-registry.service.js.map +1 -0
- package/identifier/src/lib/tokens-default.d.ts +33 -0
- package/identifier/src/lib/tokens-default.js +99 -0
- package/identifier/src/lib/tokens-default.js.map +1 -0
- package/identifier/src/lib/tokens.types.d.ts +54 -0
- package/identifier/src/lib/tokens.types.js +8 -0
- package/identifier/src/lib/tokens.types.js.map +1 -0
- package/identifier/src/lib/utils.d.ts +2 -0
- package/identifier/src/lib/utils.js +17 -0
- package/identifier/src/lib/utils.js.map +1 -0
- package/jql/abstract/package.json +14 -0
- package/jql/abstract/src/index.d.ts +26 -0
- package/jql/abstract/src/index.js +30 -0
- package/jql/abstract/src/index.js.map +1 -0
- package/jql/abstract/src/lib/entity-schema/entity-schema-naming.util.d.ts +6 -0
- package/jql/abstract/src/lib/entity-schema/entity-schema-naming.util.js +31 -0
- package/jql/abstract/src/lib/entity-schema/entity-schema-naming.util.js.map +1 -0
- package/jql/abstract/src/lib/entity-schema/entity-storage-source.util.d.ts +2 -0
- package/jql/abstract/src/lib/entity-schema/entity-storage-source.util.js +26 -0
- package/jql/abstract/src/lib/entity-schema/entity-storage-source.util.js.map +1 -0
- package/jql/abstract/src/lib/entity-schema/jql-entity-schema-not-found.error.d.ts +3 -0
- package/jql/abstract/src/lib/entity-schema/jql-entity-schema-not-found.error.js +13 -0
- package/jql/abstract/src/lib/entity-schema/jql-entity-schema-not-found.error.js.map +1 -0
- package/jql/abstract/src/lib/entity-schema/jql-entity-schema.types.d.ts +35 -0
- package/jql/abstract/src/lib/entity-schema/jql-entity-schema.types.js +5 -0
- package/jql/abstract/src/lib/entity-schema/jql-entity-schema.types.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/category-hierarchy-filter.util.d.ts +37 -0
- package/jql/abstract/src/lib/entity-store/category-hierarchy-filter.util.js +138 -0
- package/jql/abstract/src/lib/entity-store/category-hierarchy-filter.util.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-fulltext-search-paths.util.d.ts +25 -0
- package/jql/abstract/src/lib/entity-store/entity-fulltext-search-paths.util.js +128 -0
- package/jql/abstract/src/lib/entity-store/entity-fulltext-search-paths.util.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-fulltext-search.util.d.ts +44 -0
- package/jql/abstract/src/lib/entity-store/entity-fulltext-search.util.js +193 -0
- package/jql/abstract/src/lib/entity-store/entity-fulltext-search.util.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-store-aggregate.util.d.ts +6 -0
- package/jql/abstract/src/lib/entity-store/entity-store-aggregate.util.js +45 -0
- package/jql/abstract/src/lib/entity-store/entity-store-aggregate.util.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-store-backend.interface.d.ts +16 -0
- package/jql/abstract/src/lib/entity-store/entity-store-backend.interface.js +4 -0
- package/jql/abstract/src/lib/entity-store/entity-store-backend.interface.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-store-initial.types.d.ts +6 -0
- package/jql/abstract/src/lib/entity-store/entity-store-initial.types.js +5 -0
- package/jql/abstract/src/lib/entity-store/entity-store-initial.types.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-store-request.util.d.ts +18 -0
- package/jql/abstract/src/lib/entity-store/entity-store-request.util.js +144 -0
- package/jql/abstract/src/lib/entity-store/entity-store-request.util.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-store.service.d.ts +34 -0
- package/jql/abstract/src/lib/entity-store/entity-store.service.js +181 -0
- package/jql/abstract/src/lib/entity-store/entity-store.service.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-store.tokens.d.ts +6 -0
- package/jql/abstract/src/lib/entity-store/entity-store.tokens.js +12 -0
- package/jql/abstract/src/lib/entity-store/entity-store.tokens.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-store.types.d.ts +20 -0
- package/jql/abstract/src/lib/entity-store/entity-store.types.js +5 -0
- package/jql/abstract/src/lib/entity-store/entity-store.types.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/entity-visibility-filter.util.d.ts +22 -0
- package/jql/abstract/src/lib/entity-store/entity-visibility-filter.util.js +99 -0
- package/jql/abstract/src/lib/entity-store/entity-visibility-filter.util.js.map +1 -0
- package/jql/abstract/src/lib/entity-store/json-store-record-id.util.d.ts +2 -0
- package/jql/abstract/src/lib/entity-store/json-store-record-id.util.js +23 -0
- package/jql/abstract/src/lib/entity-store/json-store-record-id.util.js.map +1 -0
- package/jql/abstract/src/lib/jql-engine.base.d.ts +43 -0
- package/jql/abstract/src/lib/jql-engine.base.js +79 -0
- package/jql/abstract/src/lib/jql-engine.base.js.map +1 -0
- package/jql/abstract/src/lib/jql-engine.interface.d.ts +20 -0
- package/jql/abstract/src/lib/jql-engine.interface.js +4 -0
- package/jql/abstract/src/lib/jql-engine.interface.js.map +1 -0
- package/jql/abstract/src/lib/jql-filter.util.d.ts +5 -0
- package/jql/abstract/src/lib/jql-filter.util.js +37 -0
- package/jql/abstract/src/lib/jql-filter.util.js.map +1 -0
- package/jql/abstract/src/lib/jql-operator.util.d.ts +3 -0
- package/jql/abstract/src/lib/jql-operator.util.js +39 -0
- package/jql/abstract/src/lib/jql-operator.util.js.map +1 -0
- package/jql/abstract/src/lib/jql-source-registry.d.ts +10 -0
- package/jql/abstract/src/lib/jql-source-registry.js +41 -0
- package/jql/abstract/src/lib/jql-source-registry.js.map +1 -0
- package/jql/abstract/src/lib/jql-source.types.d.ts +35 -0
- package/jql/abstract/src/lib/jql-source.types.js +5 -0
- package/jql/abstract/src/lib/jql-source.types.js.map +1 -0
- package/jql/abstract/src/lib/jql.tokens.d.ts +10 -0
- package/jql/abstract/src/lib/jql.tokens.js +22 -0
- package/jql/abstract/src/lib/jql.tokens.js.map +1 -0
- package/jql/abstract/src/lib/jql.types.d.ts +64 -0
- package/jql/abstract/src/lib/jql.types.js +11 -0
- package/jql/abstract/src/lib/jql.types.js.map +1 -0
- package/jql/abstract/src/lib/odata/odata-query.parser.d.ts +15 -0
- package/jql/abstract/src/lib/odata/odata-query.parser.js +318 -0
- package/jql/abstract/src/lib/odata/odata-query.parser.js.map +1 -0
- package/jql/abstract/src/lib/odata/odata-query.types.d.ts +21 -0
- package/jql/abstract/src/lib/odata/odata-query.types.js +4 -0
- package/jql/abstract/src/lib/odata/odata-query.types.js.map +1 -0
- package/jql/json/package.json +15 -0
- package/jql/json/src/index.d.ts +4 -0
- package/jql/json/src/index.js +8 -0
- package/jql/json/src/index.js.map +1 -0
- package/jql/json/src/lib/jql-json.config.d.ts +6 -0
- package/jql/json/src/lib/jql-json.config.js +20 -0
- package/jql/json/src/lib/jql-json.config.js.map +1 -0
- package/jql/json/src/lib/jql-json.engine.d.ts +22 -0
- package/jql/json/src/lib/jql-json.engine.js +187 -0
- package/jql/json/src/lib/jql-json.engine.js.map +1 -0
- package/jql/json/src/lib/jql-json.filter.d.ts +2 -0
- package/jql/json/src/lib/jql-json.filter.js +175 -0
- package/jql/json/src/lib/jql-json.filter.js.map +1 -0
- package/jql/json/src/lib/jql-json.module.d.ts +6 -0
- package/jql/json/src/lib/jql-json.module.js +49 -0
- package/jql/json/src/lib/jql-json.module.js.map +1 -0
- package/jql/mongodb/package.json +17 -0
- package/jql/mongodb/src/index.d.ts +5 -0
- package/jql/mongodb/src/index.js +9 -0
- package/jql/mongodb/src/index.js.map +1 -0
- package/jql/mongodb/src/lib/compile/filter-compiler.d.ts +3 -0
- package/jql/mongodb/src/lib/compile/filter-compiler.js +70 -0
- package/jql/mongodb/src/lib/compile/filter-compiler.js.map +1 -0
- package/jql/mongodb/src/lib/compile/sort-builder.d.ts +3 -0
- package/jql/mongodb/src/lib/compile/sort-builder.js +16 -0
- package/jql/mongodb/src/lib/compile/sort-builder.js.map +1 -0
- package/jql/mongodb/src/lib/document-mapper.d.ts +3 -0
- package/jql/mongodb/src/lib/document-mapper.js +14 -0
- package/jql/mongodb/src/lib/document-mapper.js.map +1 -0
- package/jql/mongodb/src/lib/jql-mongodb.config.d.ts +5 -0
- package/jql/mongodb/src/lib/jql-mongodb.config.js +10 -0
- package/jql/mongodb/src/lib/jql-mongodb.config.js.map +1 -0
- package/jql/mongodb/src/lib/jql-mongodb.engine.d.ts +33 -0
- package/jql/mongodb/src/lib/jql-mongodb.engine.js +259 -0
- package/jql/mongodb/src/lib/jql-mongodb.engine.js.map +1 -0
- package/jql/mongodb/src/lib/jql-mongodb.module.d.ts +7 -0
- package/jql/mongodb/src/lib/jql-mongodb.module.js +58 -0
- package/jql/mongodb/src/lib/jql-mongodb.module.js.map +1 -0
- package/jql/mongodb/src/lib/jql-mongodb.tokens.d.ts +1 -0
- package/jql/mongodb/src/lib/jql-mongodb.tokens.js +7 -0
- package/jql/mongodb/src/lib/jql-mongodb.tokens.js.map +1 -0
- package/jql/postgres/package.json +17 -0
- package/jql/postgres/src/index.d.ts +5 -0
- package/jql/postgres/src/index.js +9 -0
- package/jql/postgres/src/index.js.map +1 -0
- package/jql/postgres/src/lib/compile/aggregate-builder.d.ts +5 -0
- package/jql/postgres/src/lib/compile/aggregate-builder.js +48 -0
- package/jql/postgres/src/lib/compile/aggregate-builder.js.map +1 -0
- package/jql/postgres/src/lib/compile/field-resolver.d.ts +4 -0
- package/jql/postgres/src/lib/compile/field-resolver.js +66 -0
- package/jql/postgres/src/lib/compile/field-resolver.js.map +1 -0
- package/jql/postgres/src/lib/compile/filter-compiler.d.ts +7 -0
- package/jql/postgres/src/lib/compile/filter-compiler.js +129 -0
- package/jql/postgres/src/lib/compile/filter-compiler.js.map +1 -0
- package/jql/postgres/src/lib/compile/pg-placeholders.util.d.ts +4 -0
- package/jql/postgres/src/lib/compile/pg-placeholders.util.js +16 -0
- package/jql/postgres/src/lib/compile/pg-placeholders.util.js.map +1 -0
- package/jql/postgres/src/lib/compile/query-builder.d.ts +7 -0
- package/jql/postgres/src/lib/compile/query-builder.js +58 -0
- package/jql/postgres/src/lib/compile/query-builder.js.map +1 -0
- package/jql/postgres/src/lib/compile/sql-where.types.d.ts +8 -0
- package/jql/postgres/src/lib/compile/sql-where.types.js +5 -0
- package/jql/postgres/src/lib/compile/sql-where.types.js.map +1 -0
- package/jql/postgres/src/lib/document-mapper.d.ts +2 -0
- package/jql/postgres/src/lib/document-mapper.js +21 -0
- package/jql/postgres/src/lib/document-mapper.js.map +1 -0
- package/jql/postgres/src/lib/jql-postgres.config.d.ts +5 -0
- package/jql/postgres/src/lib/jql-postgres.config.js +10 -0
- package/jql/postgres/src/lib/jql-postgres.config.js.map +1 -0
- package/jql/postgres/src/lib/jql-postgres.engine.d.ts +32 -0
- package/jql/postgres/src/lib/jql-postgres.engine.js +286 -0
- package/jql/postgres/src/lib/jql-postgres.engine.js.map +1 -0
- package/jql/postgres/src/lib/jql-postgres.module.d.ts +7 -0
- package/jql/postgres/src/lib/jql-postgres.module.js +61 -0
- package/jql/postgres/src/lib/jql-postgres.module.js.map +1 -0
- package/jql/postgres/src/lib/jql-postgres.tokens.d.ts +1 -0
- package/jql/postgres/src/lib/jql-postgres.tokens.js +7 -0
- package/jql/postgres/src/lib/jql-postgres.tokens.js.map +1 -0
- package/jql/postgres/src/lib/postgres-connection.util.d.ts +5 -0
- package/jql/postgres/src/lib/postgres-connection.util.js +18 -0
- package/jql/postgres/src/lib/postgres-connection.util.js.map +1 -0
- package/jql/postgres/src/lib/structured-document-mapper.d.ts +6 -0
- package/jql/postgres/src/lib/structured-document-mapper.js +84 -0
- package/jql/postgres/src/lib/structured-document-mapper.js.map +1 -0
- package/jql/providers/package.json +20 -0
- package/jql/providers/src/index.d.ts +8 -0
- package/jql/providers/src/index.js +11 -0
- package/jql/providers/src/index.js.map +1 -0
- package/jql/providers/src/lib/entity-storage-instance.resolve.d.ts +5 -0
- package/jql/providers/src/lib/entity-storage-instance.resolve.js +16 -0
- package/jql/providers/src/lib/entity-storage-instance.resolve.js.map +1 -0
- package/jql/providers/src/lib/jql-entity-store.config.d.ts +6 -0
- package/jql/providers/src/lib/jql-entity-store.config.js +17 -0
- package/jql/providers/src/lib/jql-entity-store.config.js.map +1 -0
- package/jql/providers/src/lib/jql-instance-db.resolve.d.ts +6 -0
- package/jql/providers/src/lib/jql-instance-db.resolve.js +21 -0
- package/jql/providers/src/lib/jql-instance-db.resolve.js.map +1 -0
- package/jql/providers/src/lib/jql-instance.types.d.ts +12 -0
- package/jql/providers/src/lib/jql-instance.types.js +5 -0
- package/jql/providers/src/lib/jql-instance.types.js.map +1 -0
- package/jql/providers/src/lib/jql-provider.module.d.ts +8 -0
- package/jql/providers/src/lib/jql-provider.module.js +54 -0
- package/jql/providers/src/lib/jql-provider.module.js.map +1 -0
- package/jql/providers/src/lib/jql-provider.resolve.d.ts +5 -0
- package/jql/providers/src/lib/jql-provider.resolve.js +26 -0
- package/jql/providers/src/lib/jql-provider.resolve.js.map +1 -0
- package/jql/providers/src/lib/jql-provider.types.d.ts +1 -0
- package/jql/providers/src/lib/jql-provider.types.js +5 -0
- package/jql/providers/src/lib/jql-provider.types.js.map +1 -0
- package/jql/providers/src/lib/jql.module.d.ts +6 -0
- package/jql/providers/src/lib/jql.module.js +134 -0
- package/jql/providers/src/lib/jql.module.js.map +1 -0
- package/jql/schema/package.json +18 -0
- package/jql/schema/src/index.d.ts +9 -0
- package/jql/schema/src/index.js +13 -0
- package/jql/schema/src/index.js.map +1 -0
- package/jql/schema/src/lib/domain-data-type.mapper.d.ts +6 -0
- package/jql/schema/src/lib/domain-data-type.mapper.js +54 -0
- package/jql/schema/src/lib/domain-data-type.mapper.js.map +1 -0
- package/jql/schema/src/lib/entity-schema-builder.d.ts +14 -0
- package/jql/schema/src/lib/entity-schema-builder.js +147 -0
- package/jql/schema/src/lib/entity-schema-builder.js.map +1 -0
- package/jql/schema/src/lib/entity-schema-jql-source.factory.d.ts +5 -0
- package/jql/schema/src/lib/entity-schema-jql-source.factory.js +75 -0
- package/jql/schema/src/lib/entity-schema-jql-source.factory.js.map +1 -0
- package/jql/schema/src/lib/jql-auto-migrator.d.ts +17 -0
- package/jql/schema/src/lib/jql-auto-migrator.js +106 -0
- package/jql/schema/src/lib/jql-auto-migrator.js.map +1 -0
- package/jql/schema/src/lib/jql-schema-history.hydrator.d.ts +4 -0
- package/jql/schema/src/lib/jql-schema-history.hydrator.js +113 -0
- package/jql/schema/src/lib/jql-schema-history.hydrator.js.map +1 -0
- package/jql/schema/src/lib/jql-schema-registry.service.d.ts +8 -0
- package/jql/schema/src/lib/jql-schema-registry.service.js +29 -0
- package/jql/schema/src/lib/jql-schema-registry.service.js.map +1 -0
- package/jql/schema/src/lib/jql-schema.controller.d.ts +14 -0
- package/jql/schema/src/lib/jql-schema.controller.js +28 -0
- package/jql/schema/src/lib/jql-schema.controller.js.map +1 -0
- package/jql/schema/src/lib/jql-schema.module.d.ts +4 -0
- package/jql/schema/src/lib/jql-schema.module.js +35 -0
- package/jql/schema/src/lib/jql-schema.module.js.map +1 -0
- package/jql/schema/src/lib/jql-schema.service.d.ts +30 -0
- package/jql/schema/src/lib/jql-schema.service.js +121 -0
- package/jql/schema/src/lib/jql-schema.service.js.map +1 -0
- package/jql/seed/package.json +18 -0
- package/jql/seed/src/index.d.ts +13 -0
- package/jql/seed/src/index.js +17 -0
- package/jql/seed/src/index.js.map +1 -0
- package/jql/seed/src/lib/entity-data-seed.provider.d.ts +14 -0
- package/jql/seed/src/lib/entity-data-seed.provider.js +45 -0
- package/jql/seed/src/lib/entity-data-seed.provider.js.map +1 -0
- package/jql/seed/src/lib/entity-seed-storage.adapter.d.ts +3 -0
- package/jql/seed/src/lib/entity-seed-storage.adapter.js +25 -0
- package/jql/seed/src/lib/entity-seed-storage.adapter.js.map +1 -0
- package/jql/seed/src/lib/jql-auto-seed.resolve.d.ts +4 -0
- package/jql/seed/src/lib/jql-auto-seed.resolve.js +13 -0
- package/jql/seed/src/lib/jql-auto-seed.resolve.js.map +1 -0
- package/jql/seed/src/lib/jql-auto-seed.service.d.ts +8 -0
- package/jql/seed/src/lib/jql-auto-seed.service.js +29 -0
- package/jql/seed/src/lib/jql-auto-seed.service.js.map +1 -0
- package/jql/seed/src/lib/jql-engine-registry.service.d.ts +7 -0
- package/jql/seed/src/lib/jql-engine-registry.service.js +31 -0
- package/jql/seed/src/lib/jql-engine-registry.service.js.map +1 -0
- package/jql/seed/src/lib/jql-multi-seed-runner.service.d.ts +11 -0
- package/jql/seed/src/lib/jql-multi-seed-runner.service.js +56 -0
- package/jql/seed/src/lib/jql-multi-seed-runner.service.js.map +1 -0
- package/jql/seed/src/lib/jql-seed-phase.resolve.d.ts +10 -0
- package/jql/seed/src/lib/jql-seed-phase.resolve.js +36 -0
- package/jql/seed/src/lib/jql-seed-phase.resolve.js.map +1 -0
- package/jql/seed/src/lib/jql-seed-provider.interface.d.ts +10 -0
- package/jql/seed/src/lib/jql-seed-provider.interface.js +4 -0
- package/jql/seed/src/lib/jql-seed-provider.interface.js.map +1 -0
- package/jql/seed/src/lib/jql-seed-runner.service.d.ts +19 -0
- package/jql/seed/src/lib/jql-seed-runner.service.js +50 -0
- package/jql/seed/src/lib/jql-seed-runner.service.js.map +1 -0
- package/jql/seed/src/lib/jql-seed-target.types.d.ts +28 -0
- package/jql/seed/src/lib/jql-seed-target.types.js +4 -0
- package/jql/seed/src/lib/jql-seed-target.types.js.map +1 -0
- package/jql/seed/src/lib/jql-seed.module.d.ts +14 -0
- package/jql/seed/src/lib/jql-seed.module.js +68 -0
- package/jql/seed/src/lib/jql-seed.module.js.map +1 -0
- package/jql/seed/src/lib/jql-seed.tokens.d.ts +5 -0
- package/jql/seed/src/lib/jql-seed.tokens.js +11 -0
- package/jql/seed/src/lib/jql-seed.tokens.js.map +1 -0
- package/jql/seed/src/lib/jql-store-seed.writer.d.ts +11 -0
- package/jql/seed/src/lib/jql-store-seed.writer.js +55 -0
- package/jql/seed/src/lib/jql-store-seed.writer.js.map +1 -0
- package/jql/sqlite/package.json +17 -0
- package/jql/sqlite/src/index.d.ts +3 -0
- package/jql/sqlite/src/index.js +7 -0
- package/jql/sqlite/src/index.js.map +1 -0
- package/jql/sqlite/src/lib/jql-sqlite.config.d.ts +6 -0
- package/jql/sqlite/src/lib/jql-sqlite.config.js +18 -0
- package/jql/sqlite/src/lib/jql-sqlite.config.js.map +1 -0
- package/jql/sqlite/src/lib/jql-sqlite.engine.d.ts +29 -0
- package/jql/sqlite/src/lib/jql-sqlite.engine.js +237 -0
- package/jql/sqlite/src/lib/jql-sqlite.engine.js.map +1 -0
- package/jql/sqlite/src/lib/jql-sqlite.module.d.ts +7 -0
- package/jql/sqlite/src/lib/jql-sqlite.module.js +51 -0
- package/jql/sqlite/src/lib/jql-sqlite.module.js.map +1 -0
- package/package.json +108 -0
- package/report/package.json +17 -0
- package/report/src/index.d.ts +8 -0
- package/report/src/index.js +12 -0
- package/report/src/index.js.map +1 -0
- package/report/src/lib/report-definition.registry.d.ts +20 -0
- package/report/src/lib/report-definition.registry.js +134 -0
- package/report/src/lib/report-definition.registry.js.map +1 -0
- package/report/src/lib/report-execute.service.d.ts +14 -0
- package/report/src/lib/report-execute.service.js +115 -0
- package/report/src/lib/report-execute.service.js.map +1 -0
- package/report/src/lib/report-query.registry.d.ts +19 -0
- package/report/src/lib/report-query.registry.js +73 -0
- package/report/src/lib/report-query.registry.js.map +1 -0
- package/report/src/lib/report-result-normalizer.d.ts +6 -0
- package/report/src/lib/report-result-normalizer.js +108 -0
- package/report/src/lib/report-result-normalizer.js.map +1 -0
- package/report/src/lib/report-summary.util.d.ts +9 -0
- package/report/src/lib/report-summary.util.js +56 -0
- package/report/src/lib/report-summary.util.js.map +1 -0
- package/report/src/lib/report.decorators.d.ts +6 -0
- package/report/src/lib/report.decorators.js +15 -0
- package/report/src/lib/report.decorators.js.map +1 -0
- package/report/src/lib/report.module.d.ts +2 -0
- package/report/src/lib/report.module.js +32 -0
- package/report/src/lib/report.module.js.map +1 -0
- package/report/src/lib/report.types.d.ts +74 -0
- package/report/src/lib/report.types.js +10 -0
- package/report/src/lib/report.types.js.map +1 -0
- package/report/src/lib/report.util.d.ts +1 -0
- package/report/src/lib/report.util.js +19 -0
- package/report/src/lib/report.util.js.map +1 -0
- package/security/package.json +17 -0
- package/security/src/index.d.ts +13 -0
- package/security/src/index.js +17 -0
- package/security/src/index.js.map +1 -0
- package/security/src/lib/auth/auth.controller.d.ts +10 -0
- package/security/src/lib/auth/auth.controller.js +117 -0
- package/security/src/lib/auth/auth.controller.js.map +1 -0
- package/security/src/lib/auth/auth.service.d.ts +22 -0
- package/security/src/lib/auth/auth.service.js +238 -0
- package/security/src/lib/auth/auth.service.js.map +1 -0
- package/security/src/lib/auth/auth.types.d.ts +69 -0
- package/security/src/lib/auth/auth.types.js +5 -0
- package/security/src/lib/auth/auth.types.js.map +1 -0
- package/security/src/lib/auth/jwt-auth.guard.d.ts +14 -0
- package/security/src/lib/auth/jwt-auth.guard.js +68 -0
- package/security/src/lib/auth/jwt-auth.guard.js.map +1 -0
- package/security/src/lib/auth/public.decorator.d.ts +3 -0
- package/security/src/lib/auth/public.decorator.js +11 -0
- package/security/src/lib/auth/public.decorator.js.map +1 -0
- package/security/src/lib/authorization/platform-root-user.util.d.ts +18 -0
- package/security/src/lib/authorization/platform-root-user.util.js +51 -0
- package/security/src/lib/authorization/platform-root-user.util.js.map +1 -0
- package/security/src/lib/entity-storage/policy-enforce.middleware.d.ts +26 -0
- package/security/src/lib/entity-storage/policy-enforce.middleware.js +207 -0
- package/security/src/lib/entity-storage/policy-enforce.middleware.js.map +1 -0
- package/security/src/lib/entity-storage/policy-expression-evaluator.util.d.ts +7 -0
- package/security/src/lib/entity-storage/policy-expression-evaluator.util.js +70 -0
- package/security/src/lib/entity-storage/policy-expression-evaluator.util.js.map +1 -0
- package/security/src/lib/entity-storage/policy-field.util.d.ts +3 -0
- package/security/src/lib/entity-storage/policy-field.util.js +95 -0
- package/security/src/lib/entity-storage/policy-field.util.js.map +1 -0
- package/security/src/lib/entity-storage/visibility-filter.middleware.d.ts +29 -0
- package/security/src/lib/entity-storage/visibility-filter.middleware.js +84 -0
- package/security/src/lib/entity-storage/visibility-filter.middleware.js.map +1 -0
- package/security/src/lib/security.module.d.ts +8 -0
- package/security/src/lib/security.module.js +65 -0
- package/security/src/lib/security.module.js.map +1 -0
- package/security/src/lib/session/abp.controller.d.ts +9 -0
- package/security/src/lib/session/abp.controller.js +38 -0
- package/security/src/lib/session/abp.controller.js.map +1 -0
- package/security/src/lib/session/applications.controller.d.ts +9 -0
- package/security/src/lib/session/applications.controller.js +38 -0
- package/security/src/lib/session/applications.controller.js.map +1 -0
- package/security/src/lib/session/backend-session.service.d.ts +21 -0
- package/security/src/lib/session/backend-session.service.js +51 -0
- package/security/src/lib/session/backend-session.service.js.map +1 -0
- package/security/src/lib/session/backend-session.types.d.ts +7 -0
- package/security/src/lib/session/backend-session.types.js +5 -0
- package/security/src/lib/session/backend-session.types.js.map +1 -0
- package/security/src/lib/session/current-session.decorator.d.ts +2 -0
- package/security/src/lib/session/current-session.decorator.js +12 -0
- package/security/src/lib/session/current-session.decorator.js.map +1 -0
- package/security/src/lib/session/optional-jwt-session.middleware.d.ts +22 -0
- package/security/src/lib/session/optional-jwt-session.middleware.js +59 -0
- package/security/src/lib/session/optional-jwt-session.middleware.js.map +1 -0
- package/security/src/lib/session/session-context.service.d.ts +21 -0
- package/security/src/lib/session/session-context.service.js +204 -0
- package/security/src/lib/session/session-context.service.js.map +1 -0
- package/security/src/lib/session/session-context.types.d.ts +31 -0
- package/security/src/lib/session/session-context.types.js +5 -0
- package/security/src/lib/session/session-context.types.js.map +1 -0
- package/security/src/lib/session/tenants.controller.d.ts +9 -0
- package/security/src/lib/session/tenants.controller.js +38 -0
- package/security/src/lib/session/tenants.controller.js.map +1 -0
- package/swagger/package.json +18 -0
- package/swagger/src/index.d.ts +6 -0
- package/swagger/src/index.js +10 -0
- package/swagger/src/index.js.map +1 -0
- package/swagger/src/lib/entity-storage-swagger.module.d.ts +2 -0
- package/swagger/src/lib/entity-storage-swagger.module.js +18 -0
- package/swagger/src/lib/entity-storage-swagger.module.js.map +1 -0
- package/swagger/src/lib/entity-storage-swagger.service.d.ts +11 -0
- package/swagger/src/lib/entity-storage-swagger.service.js +189 -0
- package/swagger/src/lib/entity-storage-swagger.service.js.map +1 -0
- package/swagger/src/lib/swagger.helpers.d.ts +3 -0
- package/swagger/src/lib/swagger.helpers.js +14 -0
- package/swagger/src/lib/swagger.helpers.js.map +1 -0
- package/swagger/src/lib/swagger.setup.d.ts +11 -0
- package/swagger/src/lib/swagger.setup.js +77 -0
- package/swagger/src/lib/swagger.setup.js.map +1 -0
- package/swagger/src/lib/swagger.tokens.d.ts +1 -0
- package/swagger/src/lib/swagger.tokens.js +7 -0
- package/swagger/src/lib/swagger.tokens.js.map +1 -0
- package/swagger/src/lib/swagger.types.d.ts +29 -0
- package/swagger/src/lib/swagger.types.js +5 -0
- package/swagger/src/lib/swagger.types.js.map +1 -0
- package/swagger/src/lib/utils/openapi-schema.util.d.ts +4 -0
- package/swagger/src/lib/utils/openapi-schema.util.js +64 -0
- package/swagger/src/lib/utils/openapi-schema.util.js.map +1 -0
- package/workflow/package.json +20 -0
- package/workflow/src/index.d.ts +7 -0
- package/workflow/src/index.js +11 -0
- package/workflow/src/index.js.map +1 -0
- package/workflow/src/lib/activities/handlers/assign-to-manager.activity.d.ts +9 -0
- package/workflow/src/lib/activities/handlers/assign-to-manager.activity.js +164 -0
- package/workflow/src/lib/activities/handlers/assign-to-manager.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/built-in.activities.d.ts +13 -0
- package/workflow/src/lib/activities/handlers/built-in.activities.js +166 -0
- package/workflow/src/lib/activities/handlers/built-in.activities.js.map +1 -0
- package/workflow/src/lib/activities/handlers/check-permission.activity.d.ts +8 -0
- package/workflow/src/lib/activities/handlers/check-permission.activity.js +36 -0
- package/workflow/src/lib/activities/handlers/check-permission.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/distribution-record.activity.d.ts +5 -0
- package/workflow/src/lib/activities/handlers/distribution-record.activity.js +65 -0
- package/workflow/src/lib/activities/handlers/distribution-record.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/entity-create.activity.d.ts +5 -0
- package/workflow/src/lib/activities/handlers/entity-create.activity.js +55 -0
- package/workflow/src/lib/activities/handlers/entity-create.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/entity-delete.activity.d.ts +5 -0
- package/workflow/src/lib/activities/handlers/entity-delete.activity.js +48 -0
- package/workflow/src/lib/activities/handlers/entity-delete.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/entity-read.activity.d.ts +5 -0
- package/workflow/src/lib/activities/handlers/entity-read.activity.js +54 -0
- package/workflow/src/lib/activities/handlers/entity-read.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/entity-update.activity.d.ts +5 -0
- package/workflow/src/lib/activities/handlers/entity-update.activity.js +55 -0
- package/workflow/src/lib/activities/handlers/entity-update.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/get-current-session-user.activity.d.ts +5 -0
- package/workflow/src/lib/activities/handlers/get-current-session-user.activity.js +24 -0
- package/workflow/src/lib/activities/handlers/get-current-session-user.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/get-current-user-manager.activity.d.ts +5 -0
- package/workflow/src/lib/activities/handlers/get-current-user-manager.activity.js +50 -0
- package/workflow/src/lib/activities/handlers/get-current-user-manager.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/if.activity.d.ts +5 -0
- package/workflow/src/lib/activities/handlers/if.activity.js +25 -0
- package/workflow/src/lib/activities/handlers/if.activity.js.map +1 -0
- package/workflow/src/lib/activities/handlers/index.d.ts +11 -0
- package/workflow/src/lib/activities/handlers/index.js +15 -0
- package/workflow/src/lib/activities/handlers/index.js.map +1 -0
- package/workflow/src/lib/activities/index.d.ts +7 -0
- package/workflow/src/lib/activities/index.js +11 -0
- package/workflow/src/lib/activities/index.js.map +1 -0
- package/workflow/src/lib/activities/workflow-activity-execution.types.d.ts +17 -0
- package/workflow/src/lib/activities/workflow-activity-execution.types.js +4 -0
- package/workflow/src/lib/activities/workflow-activity-execution.types.js.map +1 -0
- package/workflow/src/lib/activities/workflow-activity.constants.d.ts +4 -0
- package/workflow/src/lib/activities/workflow-activity.constants.js +10 -0
- package/workflow/src/lib/activities/workflow-activity.constants.js.map +1 -0
- package/workflow/src/lib/activities/workflow-activity.registry.d.ts +15 -0
- package/workflow/src/lib/activities/workflow-activity.registry.js +78 -0
- package/workflow/src/lib/activities/workflow-activity.registry.js.map +1 -0
- package/workflow/src/lib/activities/workflow-activity.utils.d.ts +24 -0
- package/workflow/src/lib/activities/workflow-activity.utils.js +108 -0
- package/workflow/src/lib/activities/workflow-activity.utils.js.map +1 -0
- package/workflow/src/lib/activities/workflow-permission.service.d.ts +21 -0
- package/workflow/src/lib/activities/workflow-permission.service.js +99 -0
- package/workflow/src/lib/activities/workflow-permission.service.js.map +1 -0
- package/workflow/src/lib/activities/workflow-request-session.adapter.d.ts +9 -0
- package/workflow/src/lib/activities/workflow-request-session.adapter.js +29 -0
- package/workflow/src/lib/activities/workflow-request-session.adapter.js.map +1 -0
- package/workflow/src/lib/activity/activity.types.d.ts +130 -0
- package/workflow/src/lib/activity/activity.types.js +8 -0
- package/workflow/src/lib/activity/activity.types.js.map +1 -0
- package/workflow/src/lib/authorization/platform-root-user.util.d.ts +1 -0
- package/workflow/src/lib/authorization/platform-root-user.util.js +16 -0
- package/workflow/src/lib/authorization/platform-root-user.util.js.map +1 -0
- package/workflow/src/lib/jobs/workflow-job-dispatcher.service.d.ts +20 -0
- package/workflow/src/lib/jobs/workflow-job-dispatcher.service.js +59 -0
- package/workflow/src/lib/jobs/workflow-job-dispatcher.service.js.map +1 -0
- package/workflow/src/lib/jobs/workflow-jobs.constants.d.ts +2 -0
- package/workflow/src/lib/jobs/workflow-jobs.constants.js +8 -0
- package/workflow/src/lib/jobs/workflow-jobs.constants.js.map +1 -0
- package/workflow/src/lib/jobs/workflow-jobs.module.d.ts +9 -0
- package/workflow/src/lib/jobs/workflow-jobs.module.js +53 -0
- package/workflow/src/lib/jobs/workflow-jobs.module.js.map +1 -0
- package/workflow/src/lib/jobs/workflow-maintenance.scheduler.d.ts +10 -0
- package/workflow/src/lib/jobs/workflow-maintenance.scheduler.js +43 -0
- package/workflow/src/lib/jobs/workflow-maintenance.scheduler.js.map +1 -0
- package/workflow/src/lib/jobs/workflow-redis.config.d.ts +6 -0
- package/workflow/src/lib/jobs/workflow-redis.config.js +18 -0
- package/workflow/src/lib/jobs/workflow-redis.config.js.map +1 -0
- package/workflow/src/lib/jobs/workflow-start-job.types.d.ts +6 -0
- package/workflow/src/lib/jobs/workflow-start-job.types.js +4 -0
- package/workflow/src/lib/jobs/workflow-start-job.types.js.map +1 -0
- package/workflow/src/lib/jobs/workflow-start.processor.d.ts +10 -0
- package/workflow/src/lib/jobs/workflow-start.processor.js +31 -0
- package/workflow/src/lib/jobs/workflow-start.processor.js.map +1 -0
- package/workflow/src/lib/middlewares/automation-command.middleware.d.ts +13 -0
- package/workflow/src/lib/middlewares/automation-command.middleware.js +38 -0
- package/workflow/src/lib/middlewares/automation-command.middleware.js.map +1 -0
- package/workflow/src/lib/middlewares/document-workflow-trigger.middleware.d.ts +17 -0
- package/workflow/src/lib/middlewares/document-workflow-trigger.middleware.js +104 -0
- package/workflow/src/lib/middlewares/document-workflow-trigger.middleware.js.map +1 -0
- package/workflow/src/lib/middlewares/workflow-trigger-guard.d.ts +2 -0
- package/workflow/src/lib/middlewares/workflow-trigger-guard.js +16 -0
- package/workflow/src/lib/middlewares/workflow-trigger-guard.js.map +1 -0
- package/workflow/src/lib/middlewares/workflow-trigger.middleware.d.ts +18 -0
- package/workflow/src/lib/middlewares/workflow-trigger.middleware.js +190 -0
- package/workflow/src/lib/middlewares/workflow-trigger.middleware.js.map +1 -0
- package/workflow/src/lib/runtime/entity-workflow-info.sync.d.ts +14 -0
- package/workflow/src/lib/runtime/entity-workflow-info.sync.js +43 -0
- package/workflow/src/lib/runtime/entity-workflow-info.sync.js.map +1 -0
- package/workflow/src/lib/runtime/jql-workflow-execution-store.service.d.ts +27 -0
- package/workflow/src/lib/runtime/jql-workflow-execution-store.service.js +212 -0
- package/workflow/src/lib/runtime/jql-workflow-execution-store.service.js.map +1 -0
- package/workflow/src/lib/runtime/platform-core.types.d.ts +18 -0
- package/workflow/src/lib/runtime/platform-core.types.js +5 -0
- package/workflow/src/lib/runtime/platform-core.types.js.map +1 -0
- package/workflow/src/lib/runtime/record-workflow-info.util.d.ts +12 -0
- package/workflow/src/lib/runtime/record-workflow-info.util.js +14 -0
- package/workflow/src/lib/runtime/record-workflow-info.util.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-activity-definition.service.d.ts +8 -0
- package/workflow/src/lib/runtime/workflow-activity-definition.service.js +45 -0
- package/workflow/src/lib/runtime/workflow-activity-definition.service.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-definition.types.d.ts +164 -0
- package/workflow/src/lib/runtime/workflow-definition.types.js +4 -0
- package/workflow/src/lib/runtime/workflow-definition.types.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-engine.errors.d.ts +28 -0
- package/workflow/src/lib/runtime/workflow-engine.errors.js +51 -0
- package/workflow/src/lib/runtime/workflow-engine.errors.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-engine.service.d.ts +373 -0
- package/workflow/src/lib/runtime/workflow-engine.service.js +2922 -0
- package/workflow/src/lib/runtime/workflow-engine.service.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-execution-store.service.d.ts +35 -0
- package/workflow/src/lib/runtime/workflow-execution-store.service.js +57 -0
- package/workflow/src/lib/runtime/workflow-execution-store.service.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-expression-evaluator.service.d.ts +7 -0
- package/workflow/src/lib/runtime/workflow-expression-evaluator.service.js +80 -0
- package/workflow/src/lib/runtime/workflow-expression-evaluator.service.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-expression-scope.service.d.ts +101 -0
- package/workflow/src/lib/runtime/workflow-expression-scope.service.js +214 -0
- package/workflow/src/lib/runtime/workflow-expression-scope.service.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-instance.types.d.ts +374 -0
- package/workflow/src/lib/runtime/workflow-instance.types.js +8 -0
- package/workflow/src/lib/runtime/workflow-instance.types.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-management.const.d.ts +6 -0
- package/workflow/src/lib/runtime/workflow-management.const.js +12 -0
- package/workflow/src/lib/runtime/workflow-management.const.js.map +1 -0
- package/workflow/src/lib/runtime/workflow-runtime.types.d.ts +263 -0
- package/workflow/src/lib/runtime/workflow-runtime.types.js +15 -0
- package/workflow/src/lib/runtime/workflow-runtime.types.js.map +1 -0
- package/workflow/src/lib/utils/multilanguage.util.d.ts +2 -0
- package/workflow/src/lib/utils/multilanguage.util.js +21 -0
- package/workflow/src/lib/utils/multilanguage.util.js.map +1 -0
- package/workflow/src/lib/utils/uuid.util.d.ts +1 -0
- package/workflow/src/lib/utils/uuid.util.js +10 -0
- package/workflow/src/lib/utils/uuid.util.js.map +1 -0
- package/workflow/src/lib/workflow.module.d.ts +2 -0
- package/workflow/src/lib/workflow.module.js +92 -0
- package/workflow/src/lib/workflow.module.js.map +1 -0
|
@@ -0,0 +1,2922 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var BackendWorkflowEngineService_1;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.BackendWorkflowEngineService = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const core_1 = require("@acorex-platform/framework-server/core");
|
|
8
|
+
const workflow_request_session_adapter_1 = require("../activities/workflow-request-session.adapter");
|
|
9
|
+
const multilanguage_util_1 = require("../utils/multilanguage.util");
|
|
10
|
+
const uuid_util_1 = require("../utils/uuid.util");
|
|
11
|
+
const workflow_management_const_1 = require("./workflow-management.const");
|
|
12
|
+
const workflow_engine_errors_1 = require("./workflow-engine.errors");
|
|
13
|
+
const workflow_activity_definition_service_1 = require("./workflow-activity-definition.service");
|
|
14
|
+
const workflow_activity_registry_1 = require("../activities/workflow-activity.registry");
|
|
15
|
+
const workflow_expression_evaluator_service_1 = require("./workflow-expression-evaluator.service");
|
|
16
|
+
const workflow_expression_scope_service_1 = require("./workflow-expression-scope.service");
|
|
17
|
+
const workflow_execution_store_service_1 = require("./workflow-execution-store.service");
|
|
18
|
+
const entity_workflow_info_sync_1 = require("./entity-workflow-info.sync");
|
|
19
|
+
/** Task context key for composed human-task titles (workflow-management). */
|
|
20
|
+
const AXP_WORKFLOW_TASK_CONTEXT_TITLE_PARTS_KEY = '_taskTitleParts';
|
|
21
|
+
/**
|
|
22
|
+
* Backend workflow runtime engine (ported from mock connectivity).
|
|
23
|
+
* Persists instances/bookmarks via {@link WorkflowExecutionStoreService}.
|
|
24
|
+
*/
|
|
25
|
+
let BackendWorkflowEngineService = BackendWorkflowEngineService_1 = class BackendWorkflowEngineService {
|
|
26
|
+
//#region ---- Services & Dependencies ----
|
|
27
|
+
constructor(store, entityStorageService, activityDefinitionService, expressionScopeService, expressionEvaluator, sessionService, activityRegistry) {
|
|
28
|
+
this.store = store;
|
|
29
|
+
this.entityStorageService = entityStorageService;
|
|
30
|
+
this.activityDefinitionService = activityDefinitionService;
|
|
31
|
+
this.expressionScopeService = expressionScopeService;
|
|
32
|
+
this.expressionEvaluator = expressionEvaluator;
|
|
33
|
+
this.sessionService = sessionService;
|
|
34
|
+
this.activityRegistry = activityRegistry;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region ---- Task Token Management ----
|
|
37
|
+
/**
|
|
38
|
+
* Secure task token storage.
|
|
39
|
+
* Key: token (UUID)
|
|
40
|
+
* Value: { instanceId, activityId, expiresAt }
|
|
41
|
+
*/
|
|
42
|
+
this.taskTokens = new Map();
|
|
43
|
+
/**
|
|
44
|
+
* Token expiration time: 30 minutes.
|
|
45
|
+
* Tasks in task board (human-task) may wait days; resume is allowed via bookmark fallback when token is expired.
|
|
46
|
+
*/
|
|
47
|
+
this.TOKEN_EXPIRATION_MS = 30 * 60 * 1000;
|
|
48
|
+
//#region ---- Execution Mode Detection ----
|
|
49
|
+
/**
|
|
50
|
+
* Cache for activity definitions to avoid repeated queries.
|
|
51
|
+
*/
|
|
52
|
+
this.activityDefinitionCache = new Map();
|
|
53
|
+
/**
|
|
54
|
+
* Cache TTL: 5 minutes
|
|
55
|
+
*/
|
|
56
|
+
this.CACHE_TTL_MS = 5 * 60 * 1000;
|
|
57
|
+
}
|
|
58
|
+
/** Minimal session user shape used by workflow runtime. */
|
|
59
|
+
getSessionUser() {
|
|
60
|
+
const userId = this.sessionService?.userId;
|
|
61
|
+
if (!userId) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return { id: userId, title: userId, name: userId };
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region ---- Definition Loading ----
|
|
68
|
+
/**
|
|
69
|
+
* Load workflow definition by name from entity storage.
|
|
70
|
+
*/
|
|
71
|
+
async getDefinition(workflowId) {
|
|
72
|
+
console.log(`[BackendWorkflowRuntime] 🔍 Loading workflow definition: ${workflowId}`);
|
|
73
|
+
// Query entity storage by name field (not id)
|
|
74
|
+
console.log(`[BackendWorkflowRuntime] 🔄 Fallback: Querying entity storage by name field: ${workflowId}`);
|
|
75
|
+
try {
|
|
76
|
+
const result = await this.entityStorageService.query('WorkflowManagement.WorkflowDefinition', {
|
|
77
|
+
skip: 0,
|
|
78
|
+
take: 1,
|
|
79
|
+
filter: {
|
|
80
|
+
field: 'name',
|
|
81
|
+
operator: { type: 'equal' },
|
|
82
|
+
value: workflowId,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
if (result.items && result.items.length > 0) {
|
|
86
|
+
const entity = result.items[0];
|
|
87
|
+
console.log(`[BackendWorkflowRuntime] ✅ Found definition via entity storage: ${workflowId}`);
|
|
88
|
+
return entity;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error(`[BackendWorkflowRuntime] ❌ Error querying entity storage:`, error);
|
|
93
|
+
}
|
|
94
|
+
console.error(`[BackendWorkflowRuntime] ❌ Workflow definition not found: ${workflowId}`);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate secure, non-guessable task token (UUID).
|
|
99
|
+
*/
|
|
100
|
+
generateTaskToken(instanceId, activityId) {
|
|
101
|
+
const token = (0, uuid_util_1.generateUuid)();
|
|
102
|
+
const expiresAt = new Date(Date.now() + this.TOKEN_EXPIRATION_MS);
|
|
103
|
+
this.taskTokens.set(token, {
|
|
104
|
+
instanceId,
|
|
105
|
+
activityId,
|
|
106
|
+
expiresAt,
|
|
107
|
+
});
|
|
108
|
+
// Cleanup expired tokens periodically (lightweight)
|
|
109
|
+
this.cleanupExpiredTokens();
|
|
110
|
+
console.log(`[BackendWorkflowEngineService] 🔐 Generated task token for ${instanceId}:${activityId}`);
|
|
111
|
+
return token;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Validate task token.
|
|
115
|
+
* Returns true if token exists, not expired, and matches instanceId + activityId.
|
|
116
|
+
*/
|
|
117
|
+
validateTaskToken(token, instanceId, activityId) {
|
|
118
|
+
const tokenInfo = this.taskTokens.get(token);
|
|
119
|
+
if (!tokenInfo) {
|
|
120
|
+
console.warn(`[BackendWorkflowEngineService] ❌ Task token not found: ${token}`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
if (tokenInfo.expiresAt < new Date()) {
|
|
124
|
+
console.warn(`[BackendWorkflowEngineService] ❌ Task token expired: ${token}`);
|
|
125
|
+
this.taskTokens.delete(token);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (tokenInfo.instanceId !== instanceId || tokenInfo.activityId !== activityId) {
|
|
129
|
+
console.warn(`[BackendWorkflowEngineService] ❌ Task token mismatch: expected ${instanceId}:${activityId}, got ${tokenInfo.instanceId}:${tokenInfo.activityId}`);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Allow resume when token is missing/expired but instance is suspended and has an active bookmark for this step.
|
|
136
|
+
* Used for task board tasks that may wait days (token is in-memory and expires after 30 min).
|
|
137
|
+
*/
|
|
138
|
+
async canResumeByBookmark(instanceId, stepId) {
|
|
139
|
+
const instance = await this.getInstance(instanceId);
|
|
140
|
+
if (!instance || instance.subStatus !== 'Suspended') {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const bookmarks = await this.getActiveBookmarks(instanceId);
|
|
144
|
+
return bookmarks.some((b) => b.activityId === stepId);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Remove task token after completion to prevent replay.
|
|
148
|
+
*/
|
|
149
|
+
removeTaskToken(token) {
|
|
150
|
+
this.taskTokens.delete(token);
|
|
151
|
+
console.log(`[BackendWorkflowEngineService] 🗑️ Removed task token: ${token}`);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Cleanup expired tokens (lightweight).
|
|
155
|
+
*/
|
|
156
|
+
cleanupExpiredTokens() {
|
|
157
|
+
const now = new Date();
|
|
158
|
+
let cleaned = 0;
|
|
159
|
+
for (const [token, info] of this.taskTokens.entries()) {
|
|
160
|
+
if (info.expiresAt < now) {
|
|
161
|
+
this.taskTokens.delete(token);
|
|
162
|
+
cleaned++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (cleaned > 0) {
|
|
166
|
+
console.log(`[BackendWorkflowEngineService] 🧹 Cleaned up ${cleaned} expired task tokens`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
/**
|
|
171
|
+
* Start a new workflow instance.
|
|
172
|
+
* Creates a workflow instance and stores it in IndexedDB.
|
|
173
|
+
*/
|
|
174
|
+
async start(request) {
|
|
175
|
+
console.log(`[BackendWorkflowEngineService] 🚀 Starting workflow: ${request.workflowId}`);
|
|
176
|
+
// Load workflow definition using definition loader (queries by name field)
|
|
177
|
+
// This ensures consistency with Workflow Management UI which also uses name field
|
|
178
|
+
const workflow = await this.getDefinition(request.workflowId);
|
|
179
|
+
if (!workflow) {
|
|
180
|
+
console.error(`[BackendWorkflowEngineService] ❌ Workflow definition not found: ${request.workflowId}`);
|
|
181
|
+
throw new Error(`Workflow definition not found: ${request.workflowId}`);
|
|
182
|
+
}
|
|
183
|
+
// Validate workflow structure
|
|
184
|
+
const graph = workflow.graph;
|
|
185
|
+
if (!graph) {
|
|
186
|
+
throw new Error(`Workflow definition has no graph: ${request.workflowId}`);
|
|
187
|
+
}
|
|
188
|
+
const activities = graph.activities || [];
|
|
189
|
+
const connections = graph.connections || [];
|
|
190
|
+
if (activities.length === 0) {
|
|
191
|
+
throw new Error(`Workflow definition has no activities: ${request.workflowId}`);
|
|
192
|
+
}
|
|
193
|
+
// Validate startActivityId exists
|
|
194
|
+
if (graph.startActivityId) {
|
|
195
|
+
const startActivity = activities.find((a) => a.id === graph.startActivityId);
|
|
196
|
+
if (!startActivity) {
|
|
197
|
+
throw new Error(`Start activity '${graph.startActivityId}' not found in workflow: ${request.workflowId}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Validate connections are structurally valid (no circular references in simple check)
|
|
201
|
+
const activityIds = new Set(activities.map((a) => a.id));
|
|
202
|
+
for (const conn of connections) {
|
|
203
|
+
if (!activityIds.has(conn.source.activtyName) || !activityIds.has(conn.target.activtyName)) {
|
|
204
|
+
console.warn(`[BackendWorkflowEngineService] ⚠️ Connection references non-existent activity: ${conn.source.activtyName} -> ${conn.target.activtyName}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const instanceId = (0, uuid_util_1.generateUuid)();
|
|
208
|
+
const now = new Date().toISOString();
|
|
209
|
+
// Build connection map: target -> source[]
|
|
210
|
+
const incomingConnections = new Map();
|
|
211
|
+
connections.forEach((conn) => {
|
|
212
|
+
const targetId = conn.target.activtyName;
|
|
213
|
+
if (!incomingConnections.has(targetId)) {
|
|
214
|
+
incomingConnections.set(targetId, []);
|
|
215
|
+
}
|
|
216
|
+
incomingConnections.get(targetId).push(conn.source.activtyName);
|
|
217
|
+
});
|
|
218
|
+
// Find root activity (use startActivityId or activity with no incoming connections, or first activity)
|
|
219
|
+
let firstActivity = null;
|
|
220
|
+
if (graph.startActivityId) {
|
|
221
|
+
firstActivity = activities.find((a) => a.id === graph.startActivityId) || null;
|
|
222
|
+
}
|
|
223
|
+
if (!firstActivity) {
|
|
224
|
+
for (const activity of activities) {
|
|
225
|
+
if (!incomingConnections.has(activity.id)) {
|
|
226
|
+
firstActivity = activity;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// If no root found, use first activity
|
|
232
|
+
if (!firstActivity && activities.length > 0) {
|
|
233
|
+
firstActivity = activities[0];
|
|
234
|
+
}
|
|
235
|
+
const firstStepId = firstActivity?.id || 'root';
|
|
236
|
+
// Extract correlationId from input if provided (remove it from input to avoid pollution)
|
|
237
|
+
const correlationId = request.input?.['correlationId'] || null;
|
|
238
|
+
const entityRefId = request.input?.['refId'] || null;
|
|
239
|
+
const entityRefType = request.input?.['refType'] || null;
|
|
240
|
+
const workflowInput = { ...request.input };
|
|
241
|
+
if (correlationId && workflowInput['correlationId']) {
|
|
242
|
+
delete workflowInput['correlationId'];
|
|
243
|
+
}
|
|
244
|
+
// Remove refId and refType from workflow input (they're stored in instance metadata)
|
|
245
|
+
if (entityRefId && workflowInput['refId']) {
|
|
246
|
+
delete workflowInput['refId'];
|
|
247
|
+
}
|
|
248
|
+
if (entityRefType && workflowInput['refType']) {
|
|
249
|
+
delete workflowInput['refType'];
|
|
250
|
+
}
|
|
251
|
+
// Create workflow state (totalSteps for overview progress display)
|
|
252
|
+
const totalSteps = activities.length;
|
|
253
|
+
const workflowState = {
|
|
254
|
+
id: `state-${instanceId}`,
|
|
255
|
+
definitionId: workflow.name, // Use name as definitionId (backward compatibility)
|
|
256
|
+
//definitionVersion: workflow.version,
|
|
257
|
+
definitionVersion: 1,
|
|
258
|
+
correlationId,
|
|
259
|
+
status: 'Running',
|
|
260
|
+
subStatus: 'Executing',
|
|
261
|
+
bookmarks: [],
|
|
262
|
+
incidents: [],
|
|
263
|
+
fault: null,
|
|
264
|
+
completionCallbacks: [],
|
|
265
|
+
activityExecutionContexts: firstActivity
|
|
266
|
+
? [
|
|
267
|
+
{
|
|
268
|
+
id: `ctx-${firstStepId}`,
|
|
269
|
+
parentContextId: null,
|
|
270
|
+
scheduledActivityNodeId: firstStepId,
|
|
271
|
+
ownerActivityNodeId: null,
|
|
272
|
+
properties: {},
|
|
273
|
+
activityState: null,
|
|
274
|
+
dynamicVariables: [],
|
|
275
|
+
status: 'Running',
|
|
276
|
+
isExecuting: false,
|
|
277
|
+
faultCount: 0,
|
|
278
|
+
startedAt: now,
|
|
279
|
+
completedAt: null,
|
|
280
|
+
tag: null,
|
|
281
|
+
},
|
|
282
|
+
]
|
|
283
|
+
: [],
|
|
284
|
+
input: { ...workflowInput, _workflowInstanceId: instanceId },
|
|
285
|
+
output: {},
|
|
286
|
+
properties: {},
|
|
287
|
+
createdAt: now,
|
|
288
|
+
updatedAt: now,
|
|
289
|
+
finishedAt: null,
|
|
290
|
+
totalSteps,
|
|
291
|
+
};
|
|
292
|
+
// Create workflow instance
|
|
293
|
+
const instance = {
|
|
294
|
+
$schema: 'https://elsaworkflows.io/schemas/workflow-instance/v3.0.0/schema.json',
|
|
295
|
+
id: instanceId,
|
|
296
|
+
definitionId: workflow.name, // Use name as definitionId (backward compatibility)
|
|
297
|
+
//definitionVersionId: workflow.id,
|
|
298
|
+
//version: workflow.version,
|
|
299
|
+
version: 1,
|
|
300
|
+
definitionVersionId: '1',
|
|
301
|
+
parentWorkflowInstanceId: null,
|
|
302
|
+
workflowState,
|
|
303
|
+
status: 'Running',
|
|
304
|
+
subStatus: 'Executing',
|
|
305
|
+
correlationId,
|
|
306
|
+
name: (0, multilanguage_util_1.resolveMultiLanguageString)(workflow.title) || workflow.name || null,
|
|
307
|
+
incidentCount: 0,
|
|
308
|
+
createdAt: now,
|
|
309
|
+
updatedAt: now,
|
|
310
|
+
finishedAt: null,
|
|
311
|
+
// Store entity reference for work item creation
|
|
312
|
+
entityRefId: entityRefId || null,
|
|
313
|
+
entityRefType: entityRefType || null,
|
|
314
|
+
};
|
|
315
|
+
await this.store.addInstance(instance);
|
|
316
|
+
console.log(`[BackendWorkflowEngineService] 💾 Stored workflow instance:`, {
|
|
317
|
+
id: instance.id,
|
|
318
|
+
definitionId: instance.definitionId,
|
|
319
|
+
name: instance.name,
|
|
320
|
+
correlationId: instance.correlationId,
|
|
321
|
+
entityRefId: instance.entityRefId,
|
|
322
|
+
entityRefType: instance.entityRefType,
|
|
323
|
+
status: instance.status,
|
|
324
|
+
subStatus: instance.subStatus,
|
|
325
|
+
});
|
|
326
|
+
// 🎯 Also store in entity service for entity-based access
|
|
327
|
+
await this.saveInstanceToEntityService(instance);
|
|
328
|
+
// Convert to instance state for response (async - needs bookmark lookup)
|
|
329
|
+
let state = await this.instanceToInstanceState(instance);
|
|
330
|
+
// 🎯 KEY LOGIC: Execute all backend activities until we reach a frontend activity
|
|
331
|
+
let currentActivity = firstActivity;
|
|
332
|
+
let executionCount = 0;
|
|
333
|
+
const maxExecutions = 500; // Prevent infinite loops
|
|
334
|
+
// Only last activity output is kept; each step overwrites it. Expression scope uses outputs = lastActivityOutput.
|
|
335
|
+
let lastActivityOutput = state.lastActivityOutput;
|
|
336
|
+
while (currentActivity && executionCount < maxExecutions) {
|
|
337
|
+
executionCount++;
|
|
338
|
+
const activityType = currentActivity.name;
|
|
339
|
+
if (request.workflowId === 'documentReview') {
|
|
340
|
+
console.log('[WFT-ENG] documentReview step', executionCount, 'activityId=', currentActivity.id, 'type=', activityType);
|
|
341
|
+
}
|
|
342
|
+
// Determine execution mode (async)
|
|
343
|
+
const executionMode = await this.determineExecutionMode(activityType, currentActivity);
|
|
344
|
+
// Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
|
|
345
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
346
|
+
let taskType = currentActivity.metadata?.['taskType'] ?? activityDef?.taskType ?? 'ui-activity';
|
|
347
|
+
// Defensive: human-task and cartable must suspend for task board
|
|
348
|
+
if (this.isTaskBoardSuspendGraphActivity(activityType)) {
|
|
349
|
+
if (activityDef?.taskType === 'human-task') {
|
|
350
|
+
taskType = 'human-task';
|
|
351
|
+
}
|
|
352
|
+
if (executionMode === 'backend') {
|
|
353
|
+
console.warn(`[BackendWorkflowEngineService] ⚠️ ${activityType} had executionMode=backend (definition or cache). Forcing frontend so workflow suspends at this step.`, {
|
|
354
|
+
activityId: currentActivity.id,
|
|
355
|
+
definitionExecutionMode: activityDef?.executionMode,
|
|
356
|
+
definitionTaskType: activityDef?.taskType,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const effectiveExecutionMode = taskType === 'human-task' && executionMode === 'backend'
|
|
361
|
+
? 'frontend'
|
|
362
|
+
: this.isTaskBoardSuspendGraphActivity(activityType) &&
|
|
363
|
+
(activityDef?.taskType === 'human-task' || taskType === 'human-task')
|
|
364
|
+
? executionMode === 'backend'
|
|
365
|
+
? 'frontend'
|
|
366
|
+
: executionMode
|
|
367
|
+
: executionMode;
|
|
368
|
+
// If frontend activity → stop and return as pendingTask
|
|
369
|
+
if (effectiveExecutionMode === 'frontend' || effectiveExecutionMode === 'both') {
|
|
370
|
+
const pendingTask = await this.createTaskFromActivity(currentActivity, state);
|
|
371
|
+
// Use local taskType (may be forced to human-task for workflow-activity:human-task) so bookmark and UI see correct type
|
|
372
|
+
const resolvedTaskTypeForBookmark = taskType === 'human-task' || pendingTask.taskType === 'human-task' ? 'human-task' : pendingTask.taskType;
|
|
373
|
+
// Extract assignment and task metadata from activity inputs (for human-task activities)
|
|
374
|
+
let bookmarkPayload = {
|
|
375
|
+
activityType,
|
|
376
|
+
activityName: currentActivity.name,
|
|
377
|
+
taskToken: pendingTask.taskToken, // Store taskToken for resume
|
|
378
|
+
taskType: resolvedTaskTypeForBookmark,
|
|
379
|
+
};
|
|
380
|
+
// For human-task activities, extract assignment and task metadata from inputs
|
|
381
|
+
if (resolvedTaskTypeForBookmark === 'human-task' && pendingTask.input) {
|
|
382
|
+
const inputs = pendingTask.input;
|
|
383
|
+
// Assignment metadata (normalize to array so task board filter works; expression may return single id)
|
|
384
|
+
bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
|
|
385
|
+
bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
|
|
386
|
+
bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
|
|
387
|
+
// Task metadata
|
|
388
|
+
bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
|
|
389
|
+
bookmarkPayload['description'] = inputs['description'] || null;
|
|
390
|
+
bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
|
|
391
|
+
bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
|
|
392
|
+
// UI configuration
|
|
393
|
+
bookmarkPayload['layout'] = inputs['layout'] || null;
|
|
394
|
+
bookmarkPayload['actions'] = inputs['actions'] || null;
|
|
395
|
+
bookmarkPayload['context'] = inputs['context'] || {};
|
|
396
|
+
}
|
|
397
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
|
|
398
|
+
bookmarkPayload['assignedUserIds'] = [];
|
|
399
|
+
}
|
|
400
|
+
if (resolvedTaskTypeForBookmark === 'human-task') {
|
|
401
|
+
const htInputs = pendingTask.input;
|
|
402
|
+
this.logHumanTaskBookmarkAssignmentTrace({
|
|
403
|
+
phase: 'engine-start',
|
|
404
|
+
instanceId,
|
|
405
|
+
workflowDefinitionName: workflow.name,
|
|
406
|
+
activityId: currentActivity.id,
|
|
407
|
+
activityType,
|
|
408
|
+
rawAssignedUserIds: htInputs?.['assignedUserIds'],
|
|
409
|
+
normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
|
|
410
|
+
variables: state.variables,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
const bookmarkId = await this.createBookmark(instanceId, currentActivity.id, 'user-task', bookmarkPayload);
|
|
414
|
+
const isTaskBoardSuspend = this.isTaskBoardSuspendGraphActivity(activityType);
|
|
415
|
+
if (isTaskBoardSuspend) {
|
|
416
|
+
console.log('[WFT-ENG] task-board bookmark created instanceId=', instanceId, 'activityId=', currentActivity.id, 'bookmarkId=', bookmarkId, 'type=', activityType);
|
|
417
|
+
}
|
|
418
|
+
const newStatus = isTaskBoardSuspend ? 'suspended' : 'running';
|
|
419
|
+
state = {
|
|
420
|
+
...state,
|
|
421
|
+
lastActivityOutput,
|
|
422
|
+
status: newStatus,
|
|
423
|
+
currentStepId: currentActivity.id,
|
|
424
|
+
};
|
|
425
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
426
|
+
return {
|
|
427
|
+
instanceId: instanceId,
|
|
428
|
+
state,
|
|
429
|
+
pendingTask,
|
|
430
|
+
lastActivityOutput,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
// Backend activity → execute it
|
|
434
|
+
const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutput);
|
|
435
|
+
lastActivityOutput = executionResult.output;
|
|
436
|
+
// set-variable writes workflow variables directly into output map (no outputToVariables).
|
|
437
|
+
const isSetVariableRun = currentActivity.name === 'workflow-activity:set-variable';
|
|
438
|
+
const variableUpdates = isSetVariableRun
|
|
439
|
+
? executionResult.output
|
|
440
|
+
: this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
|
|
441
|
+
const outcome = Object.keys(executionResult.outcomes)[0] || 'Done';
|
|
442
|
+
const sessionUser = this.getSessionUser();
|
|
443
|
+
const backendStepAudit = {
|
|
444
|
+
[`${currentActivity.id}_outcome`]: outcome,
|
|
445
|
+
[`${currentActivity.id}_activityOutput`]: executionResult.output,
|
|
446
|
+
};
|
|
447
|
+
if (sessionUser?.id) {
|
|
448
|
+
backendStepAudit[`${currentActivity.id}_completedByUserId`] = sessionUser.id;
|
|
449
|
+
backendStepAudit[`${currentActivity.id}_completedByLabel`] =
|
|
450
|
+
sessionUser.title || sessionUser.name || sessionUser.id;
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
backendStepAudit[`${currentActivity.id}_completedByLabel`] = 'System';
|
|
454
|
+
}
|
|
455
|
+
state = {
|
|
456
|
+
...state,
|
|
457
|
+
variables: {
|
|
458
|
+
...state.variables,
|
|
459
|
+
...variableUpdates,
|
|
460
|
+
...backendStepAudit,
|
|
461
|
+
},
|
|
462
|
+
lastActivityOutput,
|
|
463
|
+
currentStepId: currentActivity.id,
|
|
464
|
+
lastUpdated: new Date(),
|
|
465
|
+
};
|
|
466
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
467
|
+
// Find next activity based on outcome
|
|
468
|
+
const nextConnection = this.findWorkflowConnection(connections, currentActivity.id, outcome);
|
|
469
|
+
if (!nextConnection) {
|
|
470
|
+
// No next activity → workflow completed
|
|
471
|
+
state = {
|
|
472
|
+
...state,
|
|
473
|
+
status: 'completed',
|
|
474
|
+
currentStepId: undefined,
|
|
475
|
+
output: state.variables,
|
|
476
|
+
};
|
|
477
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
478
|
+
return {
|
|
479
|
+
instanceId: instanceId,
|
|
480
|
+
state,
|
|
481
|
+
pendingTask: null,
|
|
482
|
+
lastActivityOutput,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
// Find next activity
|
|
486
|
+
const nextActivity = this.findActivityInGraph(graph, nextConnection.target.activtyName);
|
|
487
|
+
if (!nextActivity) {
|
|
488
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
|
|
489
|
+
state = {
|
|
490
|
+
...state,
|
|
491
|
+
status: 'error',
|
|
492
|
+
currentStepId: undefined,
|
|
493
|
+
};
|
|
494
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
495
|
+
throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
|
|
496
|
+
}
|
|
497
|
+
currentActivity = nextActivity;
|
|
498
|
+
}
|
|
499
|
+
// If we exit loop due to max executions, mark as error
|
|
500
|
+
if (executionCount >= maxExecutions) {
|
|
501
|
+
console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
|
|
502
|
+
state = {
|
|
503
|
+
...state,
|
|
504
|
+
status: 'error',
|
|
505
|
+
currentStepId: undefined,
|
|
506
|
+
};
|
|
507
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
508
|
+
throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
|
|
509
|
+
}
|
|
510
|
+
// If we exit loop without frontend activity, workflow is complete
|
|
511
|
+
state = {
|
|
512
|
+
...state,
|
|
513
|
+
status: 'completed',
|
|
514
|
+
currentStepId: undefined,
|
|
515
|
+
output: state.variables,
|
|
516
|
+
lastActivityOutput,
|
|
517
|
+
};
|
|
518
|
+
await this.updateInstanceFromState(instanceId, state);
|
|
519
|
+
return {
|
|
520
|
+
instanceId: instanceId,
|
|
521
|
+
state,
|
|
522
|
+
pendingTask: null,
|
|
523
|
+
lastActivityOutput,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Terminates a non-finished workflow instance (e.g. superseded lifecycle run).
|
|
528
|
+
*/
|
|
529
|
+
async cancelInstance(instanceId, reason) {
|
|
530
|
+
const instance = await this.getInstance(instanceId);
|
|
531
|
+
if (!instance || instance.status === 'Finished') {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const now = new Date().toISOString();
|
|
535
|
+
instance.status = 'Finished';
|
|
536
|
+
instance.subStatus = 'Cancelled';
|
|
537
|
+
instance.updatedAt = now;
|
|
538
|
+
instance.finishedAt = now;
|
|
539
|
+
instance.workflowState.status = 'Finished';
|
|
540
|
+
instance.workflowState.subStatus = 'Cancelled';
|
|
541
|
+
instance.workflowState.updatedAt = now;
|
|
542
|
+
instance.workflowState.finishedAt = now;
|
|
543
|
+
if (reason) {
|
|
544
|
+
instance.workflowState.properties = {
|
|
545
|
+
...(instance.workflowState.properties ?? {}),
|
|
546
|
+
cancelReason: reason,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
await this.store.updateInstance(instance);
|
|
550
|
+
await this.updateInstanceInEntityService(instance);
|
|
551
|
+
console.log(`[BackendWorkflowEngineService] 🛑 Cancelled workflow instance: ${instanceId}`, { reason });
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Find activity in Graph by ID.
|
|
555
|
+
*/
|
|
556
|
+
findActivityInGraph(graph, activityId) {
|
|
557
|
+
const activities = graph.activities || [];
|
|
558
|
+
return activities.find((a) => a.id === activityId) || null;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Resolve allowed outcomes for the current activity from the workflow definition.
|
|
562
|
+
* - First: use outgoing connection ports from the workflow graph (so workflow-defined ports like 'Submitted'/'Cancelled' are accepted).
|
|
563
|
+
* - For human-task: parse activity instance inputs.actions (prefix/suffix) and use action names as outcomes.
|
|
564
|
+
* - Otherwise: use activity definition outcomes or ['Done'].
|
|
565
|
+
*/
|
|
566
|
+
async getActivityAllowedOutcomes(workflow, stepId) {
|
|
567
|
+
const activityInstance = this.findActivityInGraph(workflow.graph, stepId);
|
|
568
|
+
if (!activityInstance)
|
|
569
|
+
return [BackendWorkflowEngineService_1.DEFAULT_UNMATCHED_OUTCOME];
|
|
570
|
+
// Prefer outcomes from workflow graph: outgoing connection ports for this step (e.g. leaveRequestForm → Submitted, Cancelled)
|
|
571
|
+
const connections = workflow.graph?.connections ?? [];
|
|
572
|
+
const outgoingPorts = connections
|
|
573
|
+
.filter((c) => c.source?.activtyName === stepId)
|
|
574
|
+
.map((c) => c.source?.port ?? BackendWorkflowEngineService_1.DEFAULT_UNMATCHED_OUTCOME);
|
|
575
|
+
const uniquePorts = this.normalizeAllowedOutcomeList(outgoingPorts);
|
|
576
|
+
if (uniquePorts.length > 0)
|
|
577
|
+
return uniquePorts;
|
|
578
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityInstance.name);
|
|
579
|
+
const taskType = activityDef?.taskType ?? 'ui-activity';
|
|
580
|
+
// Resolve outcomes from instance inputs.actions (human-task, show-layout-popup, etc.)
|
|
581
|
+
if (activityInstance.inputs?.['actions']) {
|
|
582
|
+
const actions = activityInstance.inputs['actions'];
|
|
583
|
+
const names = [];
|
|
584
|
+
try {
|
|
585
|
+
const parsed = typeof actions === 'string' ? JSON.parse(actions) : actions;
|
|
586
|
+
const prefix = parsed?.prefix ?? [];
|
|
587
|
+
const suffix = parsed?.suffix ?? [];
|
|
588
|
+
for (const item of [...prefix, ...suffix]) {
|
|
589
|
+
const name = item?.command?.name ?? item?.name;
|
|
590
|
+
if (name && typeof name === 'string' && !names.includes(name))
|
|
591
|
+
names.push(name);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
// ignore parse error
|
|
596
|
+
}
|
|
597
|
+
if (names.length > 0)
|
|
598
|
+
return names;
|
|
599
|
+
}
|
|
600
|
+
const staticOutcomes = activityDef?.outcomes;
|
|
601
|
+
if (Array.isArray(staticOutcomes) && staticOutcomes.length > 0) {
|
|
602
|
+
const normalized = this.normalizeAllowedOutcomeList(staticOutcomes);
|
|
603
|
+
if (normalized.length > 0) {
|
|
604
|
+
return normalized;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return [BackendWorkflowEngineService_1.DEFAULT_UNMATCHED_OUTCOME];
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Coerces workflow outcome/port values (string or legacy object shapes) to a plain label.
|
|
611
|
+
*/
|
|
612
|
+
resolveOutcomeLabel(value) {
|
|
613
|
+
if (value == null || value === '') {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
if (typeof value === 'string') {
|
|
617
|
+
const trimmed = value.trim();
|
|
618
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
619
|
+
}
|
|
620
|
+
if (typeof value === 'object') {
|
|
621
|
+
const record = value;
|
|
622
|
+
for (const key of ['name', 'value', 'outcome', 'port', 'id']) {
|
|
623
|
+
const resolved = this.resolveOutcomeLabel(record[key]);
|
|
624
|
+
if (resolved) {
|
|
625
|
+
return resolved;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
normalizeAllowedOutcomeList(values) {
|
|
632
|
+
const names = [];
|
|
633
|
+
for (const value of values) {
|
|
634
|
+
const label = this.resolveOutcomeLabel(value);
|
|
635
|
+
if (label && !names.includes(label)) {
|
|
636
|
+
names.push(label);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return names;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Normalize outcome: if it is in the allowed list, return as-is; otherwise use standard fallback and log.
|
|
643
|
+
*/
|
|
644
|
+
normalizeOutcome(requestedOutcome, allowedOutcomes, stepId) {
|
|
645
|
+
const allowed = this.normalizeAllowedOutcomeList(allowedOutcomes);
|
|
646
|
+
const requested = this.resolveOutcomeLabel(requestedOutcome) ?? BackendWorkflowEngineService_1.DEFAULT_UNMATCHED_OUTCOME;
|
|
647
|
+
if (allowed.includes(requested)) {
|
|
648
|
+
return requested;
|
|
649
|
+
}
|
|
650
|
+
const caseInsensitive = allowed.find((allowedOutcome) => allowedOutcome.toLowerCase() === requested.toLowerCase());
|
|
651
|
+
if (caseInsensitive) {
|
|
652
|
+
return caseInsensitive;
|
|
653
|
+
}
|
|
654
|
+
console.warn(`[BackendWorkflowEngineService] ⚠️ Outcome "${requested}" not in allowed list [${allowed.join(', ')}] for step ${stepId}. Using "${BackendWorkflowEngineService_1.DEFAULT_UNMATCHED_OUTCOME}".`);
|
|
655
|
+
return BackendWorkflowEngineService_1.DEFAULT_UNMATCHED_OUTCOME;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Maps mistaken `Done` outcomes from `create-entity-form` when `persistInWorkflow` is true
|
|
659
|
+
* but command metadata was lost on the client (payload still contains `output.result`).
|
|
660
|
+
*/
|
|
661
|
+
coerceCreateEntityFormOutcome(activity, requestedOutcome, output) {
|
|
662
|
+
if (!activity || activity.name !== 'workflow-activity:create-entity-form') {
|
|
663
|
+
return requestedOutcome;
|
|
664
|
+
}
|
|
665
|
+
if (requestedOutcome === 'Submitted' || requestedOutcome === 'Cancelled') {
|
|
666
|
+
return requestedOutcome;
|
|
667
|
+
}
|
|
668
|
+
const persistFlag = activity.inputs?.['persistInWorkflow'];
|
|
669
|
+
const persistInWorkflow = persistFlag === true || persistFlag === 'true';
|
|
670
|
+
if (!persistInWorkflow) {
|
|
671
|
+
return requestedOutcome;
|
|
672
|
+
}
|
|
673
|
+
if (requestedOutcome === 'Done' && this.extractPersistedEntityFormResult(output)) {
|
|
674
|
+
return 'Submitted';
|
|
675
|
+
}
|
|
676
|
+
return requestedOutcome;
|
|
677
|
+
}
|
|
678
|
+
findWorkflowConnection(connections, sourceStepId, outcome) {
|
|
679
|
+
const normalizedOutcome = this.resolveOutcomeLabel(outcome) ?? BackendWorkflowEngineService_1.DEFAULT_UNMATCHED_OUTCOME;
|
|
680
|
+
return connections.find((conn) => {
|
|
681
|
+
if (conn.source.activtyName !== sourceStepId) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
const port = this.resolveOutcomeLabel(conn.source.port);
|
|
685
|
+
if (!port) {
|
|
686
|
+
return normalizedOutcome === 'Done';
|
|
687
|
+
}
|
|
688
|
+
return port === normalizedOutcome || port.toLowerCase() === normalizedOutcome.toLowerCase();
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Normalize value to a plain object for use as scope.outputs.
|
|
693
|
+
* Used when building expression scope so that outputs.* (e.g. outputs.result, outputs.action) is available.
|
|
694
|
+
*/
|
|
695
|
+
ensureOutputsForScope(value) {
|
|
696
|
+
if (value == null)
|
|
697
|
+
return {};
|
|
698
|
+
if (typeof value === 'object' && !Array.isArray(value))
|
|
699
|
+
return { ...value };
|
|
700
|
+
return { value };
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Extracts persisted create-entity-form payload (`data.result`) from a frontend step output.
|
|
704
|
+
*/
|
|
705
|
+
extractPersistedEntityFormResult(output) {
|
|
706
|
+
const result = output['result'];
|
|
707
|
+
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
708
|
+
return result;
|
|
709
|
+
}
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Coerces date-like workflow values to a {@link Date} (local calendar date when input is `yyyy-mm-dd`).
|
|
714
|
+
*/
|
|
715
|
+
coerceWorkflowDateValue(value) {
|
|
716
|
+
if (value == null) {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
if (value instanceof Date) {
|
|
720
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
721
|
+
}
|
|
722
|
+
if (typeof value === 'string') {
|
|
723
|
+
const trimmed = value.trim();
|
|
724
|
+
const dateOnlyMatch = /^(\d{4})-(\d{2})-(\d{2})/.exec(trimmed);
|
|
725
|
+
if (dateOnlyMatch) {
|
|
726
|
+
const year = Number(dateOnlyMatch[1]);
|
|
727
|
+
const month = Number(dateOnlyMatch[2]);
|
|
728
|
+
const day = Number(dateOnlyMatch[3]);
|
|
729
|
+
const local = new Date(year, month - 1, day);
|
|
730
|
+
return Number.isNaN(local.getTime()) ? null : local;
|
|
731
|
+
}
|
|
732
|
+
const parsed = new Date(trimmed);
|
|
733
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
734
|
+
}
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Promotes `create-entity-form` submit payload (`output.result`) onto workflow variables
|
|
739
|
+
* so downstream steps and human tasks see form values (e.g. effectiveDate), not start inputs.
|
|
740
|
+
*/
|
|
741
|
+
buildPersistedEntityFormVariablePromotions(output, activityName, outcome) {
|
|
742
|
+
if (activityName !== 'workflow-activity:create-entity-form' || outcome !== 'Submitted') {
|
|
743
|
+
return {};
|
|
744
|
+
}
|
|
745
|
+
const form = this.extractPersistedEntityFormResult(output);
|
|
746
|
+
if (!form) {
|
|
747
|
+
return {};
|
|
748
|
+
}
|
|
749
|
+
const promoted = {};
|
|
750
|
+
for (const [key, value] of Object.entries(form)) {
|
|
751
|
+
if (value == null || key === 'id') {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (key === 'effectiveDate') {
|
|
755
|
+
const coerced = this.coerceWorkflowDateValue(value);
|
|
756
|
+
if (coerced) {
|
|
757
|
+
promoted[key] = coerced;
|
|
758
|
+
}
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
762
|
+
promoted[key] = value;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return promoted;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Resume a suspended workflow instance.
|
|
769
|
+
*
|
|
770
|
+
* After frontend completes its task:
|
|
771
|
+
* 1. Validates taskToken (secure, non-guessable)
|
|
772
|
+
* 2. Verifies resumed activity matches currentStepId/bookmark
|
|
773
|
+
* 3. Marks related bookmark as consumed
|
|
774
|
+
* 4. Applies frontend outputs into workflow state
|
|
775
|
+
* 5. Traverses to next activity based on outcomes
|
|
776
|
+
* 6. Executes ALL backend activities until hitting frontend activity or completion
|
|
777
|
+
* 7. Returns either pendingTask (with new secure token) or completed status
|
|
778
|
+
*/
|
|
779
|
+
async resume(request) {
|
|
780
|
+
console.log(`[BackendWorkflowEngineService] 🔄 Resuming workflow: ${request.instanceId}`, {
|
|
781
|
+
stepId: request.stepId,
|
|
782
|
+
outcome: request.outcome,
|
|
783
|
+
});
|
|
784
|
+
// Get current state
|
|
785
|
+
let state = await this.getState({
|
|
786
|
+
instanceId: request.instanceId,
|
|
787
|
+
});
|
|
788
|
+
if (!state) {
|
|
789
|
+
throw new Error(`Workflow instance not found: ${request.instanceId}`);
|
|
790
|
+
}
|
|
791
|
+
if (state.status !== 'suspended') {
|
|
792
|
+
throw new Error(`Cannot resume workflow with status: ${state.status}. Expected: suspended`);
|
|
793
|
+
}
|
|
794
|
+
// 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).
|
|
795
|
+
const tokenValid = request.taskToken && this.validateTaskToken(request.taskToken, request.instanceId, request.stepId);
|
|
796
|
+
if (!tokenValid) {
|
|
797
|
+
const allowedByBookmark = await this.canResumeByBookmark(request.instanceId, request.stepId);
|
|
798
|
+
if (!allowedByBookmark) {
|
|
799
|
+
throw new Error(request.taskToken ? 'Invalid or expired task token' : 'Missing taskToken for resume operation');
|
|
800
|
+
}
|
|
801
|
+
console.log(`[BackendWorkflowEngineService] ✅ Resume allowed by bookmark (token expired/missing): ${request.instanceId}:${request.stepId}`);
|
|
802
|
+
}
|
|
803
|
+
// Verify stepId matches current step
|
|
804
|
+
if (state.currentStepId !== request.stepId) {
|
|
805
|
+
throw new Error(`Step mismatch: expected '${state.currentStepId}', got '${request.stepId}'`);
|
|
806
|
+
}
|
|
807
|
+
// Enforce claim / assignee for task-board bookmarks
|
|
808
|
+
const resumeBookmarks = await this.store.getBookmarksByInstanceAndActivity(request.instanceId, request.stepId, true);
|
|
809
|
+
const resumePayloadSource = resumeBookmarks[0];
|
|
810
|
+
const resumePayload = resumePayloadSource
|
|
811
|
+
? this.parseBookmarkPayloadJson(resumePayloadSource.payloadJson)
|
|
812
|
+
: null;
|
|
813
|
+
if (resumePayload) {
|
|
814
|
+
const payloadActivityType = String(resumePayload['activityType'] ?? '');
|
|
815
|
+
const assigned = this.normalizeUserIds(resumePayload['assignedUserIds']) ?? [];
|
|
816
|
+
const candidates = this.normalizeUserIds(resumePayload['candidateUserIds']) ?? [];
|
|
817
|
+
const isCartablePayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable;
|
|
818
|
+
const isPooledHumanPayload = payloadActivityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask &&
|
|
819
|
+
assigned.length === 0 &&
|
|
820
|
+
candidates.length > 0;
|
|
821
|
+
if (isCartablePayload || isPooledHumanPayload) {
|
|
822
|
+
if (assigned.length === 0) {
|
|
823
|
+
throw new workflow_engine_errors_1.AXPWorkflowEngineError('Claim the task before completing it', workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.CLAIM_REQUIRED);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (assigned.length > 0) {
|
|
827
|
+
const uid = this.getSessionUser()?.id;
|
|
828
|
+
if (uid && !assigned.includes(String(uid))) {
|
|
829
|
+
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 });
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
// Mark bookmark as consumed
|
|
834
|
+
await this.consumeBookmark(request.instanceId, request.stepId);
|
|
835
|
+
// Remove task token after validation (prevent replay); no-op if token was already expired/missing
|
|
836
|
+
if (request.taskToken) {
|
|
837
|
+
this.removeTaskToken(request.taskToken);
|
|
838
|
+
}
|
|
839
|
+
// Load workflow definition using definition loader (queries by name field)
|
|
840
|
+
const workflow = await this.getDefinition(state.workflowId);
|
|
841
|
+
if (!workflow) {
|
|
842
|
+
console.error(`[BackendWorkflowRuntime] ❌ Workflow definition not found: ${state.workflowId}`);
|
|
843
|
+
throw new Error(`Workflow definition not found: ${state.workflowId}`);
|
|
844
|
+
}
|
|
845
|
+
const connections = workflow.graph.connections || [];
|
|
846
|
+
// Resolve allowed outcomes from workflow definition (e.g. human-task from instance inputs.actions)
|
|
847
|
+
const allowedOutcomes = await this.getActivityAllowedOutcomes(workflow, request.stepId);
|
|
848
|
+
const outcome = this.normalizeOutcome(request.outcome ?? 'Done', allowedOutcomes, request.stepId);
|
|
849
|
+
// Apply frontend output: last activity output for expressions (outputs.*); outcome/output/completer go into variables for audit UI
|
|
850
|
+
const userInput = request.userInput || {};
|
|
851
|
+
const lastActivityOutput = userInput;
|
|
852
|
+
const variableUpdatesResume = this.getVariableUpdatesFromOutput(workflow, request.stepId, userInput);
|
|
853
|
+
const sessionUser = this.getSessionUser();
|
|
854
|
+
const completerId = sessionUser?.id;
|
|
855
|
+
const completerLabel = sessionUser?.title ?? sessionUser?.name ?? completerId;
|
|
856
|
+
const resumeVariablePatch = {
|
|
857
|
+
...variableUpdatesResume,
|
|
858
|
+
[`${request.stepId}_outcome`]: outcome,
|
|
859
|
+
[`${request.stepId}_activityOutput`]: userInput,
|
|
860
|
+
};
|
|
861
|
+
if (completerId) {
|
|
862
|
+
resumeVariablePatch[`${request.stepId}_completedByUserId`] = completerId;
|
|
863
|
+
}
|
|
864
|
+
if (completerLabel) {
|
|
865
|
+
resumeVariablePatch[`${request.stepId}_completedByLabel`] = completerLabel;
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
resumeVariablePatch[`${request.stepId}_completedByLabel`] = completerId ? String(completerId) : 'System';
|
|
869
|
+
}
|
|
870
|
+
state = {
|
|
871
|
+
...state,
|
|
872
|
+
variables: {
|
|
873
|
+
...state.variables,
|
|
874
|
+
...resumeVariablePatch,
|
|
875
|
+
},
|
|
876
|
+
lastActivityOutput,
|
|
877
|
+
lastUpdated: new Date(),
|
|
878
|
+
};
|
|
879
|
+
// Find next activity based on outcome from frontend (use normalized outcome)
|
|
880
|
+
const nextConnection = this.findWorkflowConnection(connections, request.stepId, outcome);
|
|
881
|
+
if (!nextConnection) {
|
|
882
|
+
// No next activity → workflow completed
|
|
883
|
+
console.log('[AXCWorkflowExecutionService] ✅ No next activity found - workflow completed');
|
|
884
|
+
state = {
|
|
885
|
+
...state,
|
|
886
|
+
status: 'completed',
|
|
887
|
+
currentStepId: undefined,
|
|
888
|
+
output: state.variables,
|
|
889
|
+
lastActivityOutput,
|
|
890
|
+
};
|
|
891
|
+
console.log('[AXCWorkflowExecutionService] 📝 Updating instance to completed state');
|
|
892
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
893
|
+
return {
|
|
894
|
+
output: { outcome, userInput: request.userInput },
|
|
895
|
+
outcomes: { [outcome]: true },
|
|
896
|
+
nextTask: null,
|
|
897
|
+
state,
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
// 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
|
|
901
|
+
let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
|
|
902
|
+
let lastActivityOutputResume = lastActivityOutput;
|
|
903
|
+
let executionCount = 0;
|
|
904
|
+
const maxExecutions = 500;
|
|
905
|
+
if (!currentActivity) {
|
|
906
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
|
|
907
|
+
state = {
|
|
908
|
+
...state,
|
|
909
|
+
status: 'error',
|
|
910
|
+
currentStepId: undefined,
|
|
911
|
+
};
|
|
912
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
913
|
+
throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
|
|
914
|
+
}
|
|
915
|
+
while (currentActivity && executionCount < maxExecutions) {
|
|
916
|
+
executionCount++;
|
|
917
|
+
// Map activity name (registry key) to activity type
|
|
918
|
+
const activityType = currentActivity.name;
|
|
919
|
+
// Determine execution mode (async)
|
|
920
|
+
const executionMode = await this.determineExecutionMode(activityType, currentActivity);
|
|
921
|
+
// Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
|
|
922
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
923
|
+
let taskType = currentActivity.metadata?.['taskType'] ?? activityDef?.taskType ?? 'ui-activity';
|
|
924
|
+
// Defensive: human-task and cartable must suspend (same as run())
|
|
925
|
+
if (this.isTaskBoardSuspendGraphActivity(activityType)) {
|
|
926
|
+
if (activityDef?.taskType === 'human-task')
|
|
927
|
+
taskType = 'human-task';
|
|
928
|
+
}
|
|
929
|
+
const effectiveExecutionModeResume = taskType === 'human-task' && executionMode === 'backend'
|
|
930
|
+
? 'frontend'
|
|
931
|
+
: this.isTaskBoardSuspendGraphActivity(activityType) &&
|
|
932
|
+
(activityDef?.taskType === 'human-task' || taskType === 'human-task')
|
|
933
|
+
? executionMode === 'backend'
|
|
934
|
+
? 'frontend'
|
|
935
|
+
: executionMode
|
|
936
|
+
: executionMode;
|
|
937
|
+
// If frontend activity → stop and return as pendingTask
|
|
938
|
+
if (effectiveExecutionModeResume === 'frontend' || effectiveExecutionModeResume === 'both') {
|
|
939
|
+
const pendingTask = await this.createTaskFromActivity(currentActivity, state);
|
|
940
|
+
// Extract assignment and task metadata from activity inputs (for human-task activities)
|
|
941
|
+
const resolvedTaskType = taskType === 'human-task' || pendingTask.taskType === 'human-task' ? 'human-task' : pendingTask.taskType;
|
|
942
|
+
let bookmarkPayload = {
|
|
943
|
+
activityType,
|
|
944
|
+
activityName: currentActivity.name,
|
|
945
|
+
taskToken: pendingTask.taskToken, // Store taskToken for resume
|
|
946
|
+
taskType: resolvedTaskType, // Add taskType to payload
|
|
947
|
+
};
|
|
948
|
+
// For human-task activities, extract assignment and task metadata from inputs
|
|
949
|
+
if (resolvedTaskType === 'human-task' && pendingTask.input) {
|
|
950
|
+
const inputs = pendingTask.input;
|
|
951
|
+
// Assignment metadata (normalize to array; expression may return single id e.g. {{ variables.managerId }})
|
|
952
|
+
bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
|
|
953
|
+
bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
|
|
954
|
+
bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
|
|
955
|
+
// Task metadata
|
|
956
|
+
bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
|
|
957
|
+
bookmarkPayload['description'] = inputs['description'] || null;
|
|
958
|
+
bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
|
|
959
|
+
bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
|
|
960
|
+
// UI configuration
|
|
961
|
+
bookmarkPayload['layout'] = inputs['layout'] || null;
|
|
962
|
+
bookmarkPayload['actions'] = inputs['actions'] || null;
|
|
963
|
+
bookmarkPayload['context'] = inputs['context'] || {};
|
|
964
|
+
}
|
|
965
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
|
|
966
|
+
bookmarkPayload['assignedUserIds'] = [];
|
|
967
|
+
}
|
|
968
|
+
if (resolvedTaskType === 'human-task') {
|
|
969
|
+
const htInputs = pendingTask.input;
|
|
970
|
+
this.logHumanTaskBookmarkAssignmentTrace({
|
|
971
|
+
phase: 'engine-resume',
|
|
972
|
+
instanceId: request.instanceId,
|
|
973
|
+
workflowDefinitionName: workflow.name,
|
|
974
|
+
activityId: currentActivity.id,
|
|
975
|
+
activityType,
|
|
976
|
+
rawAssignedUserIds: htInputs?.['assignedUserIds'],
|
|
977
|
+
normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
|
|
978
|
+
variables: state.variables,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
// Create bookmark for frontend activity
|
|
982
|
+
// Store all metadata in bookmark payload (replaces Work Item)
|
|
983
|
+
await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
|
|
984
|
+
// Note: Work Item creation removed - all metadata stored in bookmark payload
|
|
985
|
+
// Set status: task-board activities → suspended, else running (form/popup)
|
|
986
|
+
const isTaskBoardSuspendResume = this.isTaskBoardSuspendGraphActivity(activityType);
|
|
987
|
+
state = {
|
|
988
|
+
...state,
|
|
989
|
+
lastActivityOutput: lastActivityOutputResume,
|
|
990
|
+
status: isTaskBoardSuspendResume ? 'suspended' : 'running',
|
|
991
|
+
currentStepId: currentActivity.id,
|
|
992
|
+
};
|
|
993
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
994
|
+
console.log(`[BackendWorkflowEngineService] ✅ Workflow suspended at frontend activity: ${activityType} (taskType: ${taskType}, status: ${state.status})`);
|
|
995
|
+
return {
|
|
996
|
+
output: { outcome, userInput: request.userInput },
|
|
997
|
+
outcomes: { [outcome]: true },
|
|
998
|
+
nextTask: pendingTask,
|
|
999
|
+
state,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
// Backend activity → execute it
|
|
1003
|
+
console.log(`[BackendWorkflowEngineService] ⚙️ Executing backend activity '${currentActivity.name || currentActivity.id}' (${activityType})`);
|
|
1004
|
+
const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutputResume);
|
|
1005
|
+
lastActivityOutputResume = executionResult.output;
|
|
1006
|
+
const isSetVariable = currentActivity.name === 'workflow-activity:set-variable';
|
|
1007
|
+
const variableUpdatesResumeLoop = isSetVariable
|
|
1008
|
+
? executionResult.output
|
|
1009
|
+
: this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
|
|
1010
|
+
const backendOutcome = Object.keys(executionResult.outcomes)[0] || 'Done';
|
|
1011
|
+
const sessionUserResume = this.getSessionUser();
|
|
1012
|
+
const backendAuditResume = {
|
|
1013
|
+
[`${currentActivity.id}_outcome`]: backendOutcome,
|
|
1014
|
+
[`${currentActivity.id}_activityOutput`]: executionResult.output,
|
|
1015
|
+
};
|
|
1016
|
+
if (sessionUserResume?.id) {
|
|
1017
|
+
backendAuditResume[`${currentActivity.id}_completedByUserId`] = sessionUserResume.id;
|
|
1018
|
+
backendAuditResume[`${currentActivity.id}_completedByLabel`] =
|
|
1019
|
+
sessionUserResume.title || sessionUserResume.name || sessionUserResume.id;
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
backendAuditResume[`${currentActivity.id}_completedByLabel`] = 'System';
|
|
1023
|
+
}
|
|
1024
|
+
state = {
|
|
1025
|
+
...state,
|
|
1026
|
+
variables: {
|
|
1027
|
+
...state.variables,
|
|
1028
|
+
...(typeof variableUpdatesResumeLoop === 'object' && variableUpdatesResumeLoop !== null
|
|
1029
|
+
? variableUpdatesResumeLoop
|
|
1030
|
+
: {}),
|
|
1031
|
+
...backendAuditResume,
|
|
1032
|
+
},
|
|
1033
|
+
lastActivityOutput: lastActivityOutputResume,
|
|
1034
|
+
currentStepId: currentActivity.id,
|
|
1035
|
+
lastUpdated: new Date(),
|
|
1036
|
+
};
|
|
1037
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1038
|
+
// Find next activity based on outcome (backend execution result)
|
|
1039
|
+
const nextConn = this.findWorkflowConnection(connections, currentActivity.id, backendOutcome);
|
|
1040
|
+
if (!nextConn) {
|
|
1041
|
+
// No next activity → workflow completed
|
|
1042
|
+
state = {
|
|
1043
|
+
...state,
|
|
1044
|
+
status: 'completed',
|
|
1045
|
+
currentStepId: undefined,
|
|
1046
|
+
output: state.variables,
|
|
1047
|
+
lastActivityOutput: lastActivityOutputResume,
|
|
1048
|
+
};
|
|
1049
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1050
|
+
return {
|
|
1051
|
+
output: state.variables,
|
|
1052
|
+
outcomes: { Done: true },
|
|
1053
|
+
nextTask: null,
|
|
1054
|
+
state,
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
// Find next activity
|
|
1058
|
+
const nextActivity = this.findActivityInGraph(workflow.graph, nextConn.target.activtyName);
|
|
1059
|
+
if (!nextActivity) {
|
|
1060
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConn.target.activtyName}' not found in graph`);
|
|
1061
|
+
state = {
|
|
1062
|
+
...state,
|
|
1063
|
+
status: 'error',
|
|
1064
|
+
currentStepId: undefined,
|
|
1065
|
+
};
|
|
1066
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1067
|
+
throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
|
|
1068
|
+
}
|
|
1069
|
+
currentActivity = nextActivity;
|
|
1070
|
+
}
|
|
1071
|
+
// If we exit loop due to max executions, mark as error
|
|
1072
|
+
if (executionCount >= maxExecutions) {
|
|
1073
|
+
console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
|
|
1074
|
+
state = {
|
|
1075
|
+
...state,
|
|
1076
|
+
status: 'error',
|
|
1077
|
+
currentStepId: undefined,
|
|
1078
|
+
};
|
|
1079
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1080
|
+
throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
|
|
1081
|
+
}
|
|
1082
|
+
// If we exit loop, workflow is complete
|
|
1083
|
+
state = {
|
|
1084
|
+
...state,
|
|
1085
|
+
status: 'completed',
|
|
1086
|
+
currentStepId: undefined,
|
|
1087
|
+
output: state.variables,
|
|
1088
|
+
lastActivityOutput: lastActivityOutputResume,
|
|
1089
|
+
};
|
|
1090
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
1091
|
+
return {
|
|
1092
|
+
output: state.variables,
|
|
1093
|
+
outcomes: { Done: true },
|
|
1094
|
+
nextTask: null,
|
|
1095
|
+
state,
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Get current workflow execution state.
|
|
1100
|
+
*/
|
|
1101
|
+
async getState(request) {
|
|
1102
|
+
// Get instance from IndexedDB
|
|
1103
|
+
const instance = await this.getInstance(request.instanceId);
|
|
1104
|
+
if (!instance) {
|
|
1105
|
+
throw new Error(`Workflow instance not found: ${request.instanceId}`);
|
|
1106
|
+
}
|
|
1107
|
+
// Convert instance to state (async - needs bookmark lookup)
|
|
1108
|
+
return await this.instanceToInstanceState(instance);
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Restores a suspended task-board step when the user declines a pre-interactive continuation prompt.
|
|
1112
|
+
* Re-opens the human-task bookmark on the task board and removes the aborted inline step bookmark.
|
|
1113
|
+
*/
|
|
1114
|
+
async revertResumeAfterDeclinedInteractiveContinuation(request) {
|
|
1115
|
+
try {
|
|
1116
|
+
const instance = await this.getInstance(request.instanceId);
|
|
1117
|
+
if (!instance) {
|
|
1118
|
+
return { success: false, error: `Workflow instance not found: ${request.instanceId}` };
|
|
1119
|
+
}
|
|
1120
|
+
if (request.abortedStepId) {
|
|
1121
|
+
const abortedBookmarks = await this.store.getBookmarksByInstanceAndActivity(request.instanceId, request.abortedStepId, false);
|
|
1122
|
+
for (const bookmark of abortedBookmarks) {
|
|
1123
|
+
if (!bookmark.isConsumed) {
|
|
1124
|
+
bookmark.isConsumed = true;
|
|
1125
|
+
await this.store.updateBookmark(bookmark);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
const suspendedBookmarks = await this.store.getBookmarksByInstanceAndActivity(request.instanceId, request.suspendedStepId, false);
|
|
1130
|
+
let restoredBookmark = (request.bookmarkId
|
|
1131
|
+
? suspendedBookmarks.find((bookmark) => bookmark.id === request.bookmarkId)
|
|
1132
|
+
: null) ?? suspendedBookmarks[0];
|
|
1133
|
+
if (!restoredBookmark) {
|
|
1134
|
+
return { success: false, error: 'Suspended step bookmark not found' };
|
|
1135
|
+
}
|
|
1136
|
+
const payload = this.parseBookmarkPayloadJson(restoredBookmark.payloadJson) ?? {};
|
|
1137
|
+
const taskToken = this.generateTaskToken(request.instanceId, request.suspendedStepId);
|
|
1138
|
+
payload['taskToken'] = taskToken;
|
|
1139
|
+
restoredBookmark.payloadJson = JSON.stringify(payload);
|
|
1140
|
+
restoredBookmark.isConsumed = false;
|
|
1141
|
+
await this.store.updateBookmark(restoredBookmark);
|
|
1142
|
+
const state = await this.getState({ instanceId: request.instanceId });
|
|
1143
|
+
const revertedState = {
|
|
1144
|
+
...state,
|
|
1145
|
+
status: 'suspended',
|
|
1146
|
+
currentStepId: request.suspendedStepId,
|
|
1147
|
+
lastUpdated: new Date(),
|
|
1148
|
+
};
|
|
1149
|
+
await this.updateInstanceFromState(request.instanceId, revertedState);
|
|
1150
|
+
return {
|
|
1151
|
+
success: true,
|
|
1152
|
+
state: revertedState,
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
catch (error) {
|
|
1156
|
+
const message = error instanceof Error ? error.message : 'Failed to revert workflow resume';
|
|
1157
|
+
return { success: false, error: message };
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Claim a pooled task (cartable or human-task with candidates only): assign current user without resuming.
|
|
1162
|
+
*/
|
|
1163
|
+
async claimTask(request) {
|
|
1164
|
+
const rawUserId = this.getSessionUser()?.id;
|
|
1165
|
+
if (rawUserId === undefined || rawUserId === null || rawUserId === '') {
|
|
1166
|
+
return { success: false, error: 'You must be signed in to claim a task' };
|
|
1167
|
+
}
|
|
1168
|
+
const uid = String(rawUserId);
|
|
1169
|
+
const dexieInstance = await this.getInstance(request.instanceId).catch(() => null);
|
|
1170
|
+
if (!dexieInstance) {
|
|
1171
|
+
return this.claimTaskOnWorkflowInstanceEntity(request, uid);
|
|
1172
|
+
}
|
|
1173
|
+
const state = await this.getState({ instanceId: request.instanceId }).catch(() => null);
|
|
1174
|
+
if (!state || state.status !== 'suspended') {
|
|
1175
|
+
return { success: false, error: 'Workflow instance is not suspended or was not found' };
|
|
1176
|
+
}
|
|
1177
|
+
if (state.currentStepId && state.currentStepId !== request.stepId) {
|
|
1178
|
+
return { success: false, error: 'Step does not match the current suspended step' };
|
|
1179
|
+
}
|
|
1180
|
+
const bookmarks = await this.getActiveBookmarks(request.instanceId);
|
|
1181
|
+
const bookmark = bookmarks.find((b) => b.id === request.bookmarkId && b.activityId === request.stepId);
|
|
1182
|
+
if (!bookmark) {
|
|
1183
|
+
return { success: false, error: 'Bookmark not found or already completed' };
|
|
1184
|
+
}
|
|
1185
|
+
const payload = this.parseBookmarkPayloadJson(bookmark.payloadJson);
|
|
1186
|
+
if (!payload) {
|
|
1187
|
+
return { success: false, error: 'Invalid bookmark payload' };
|
|
1188
|
+
}
|
|
1189
|
+
const activityType = String(payload['activityType'] ?? '');
|
|
1190
|
+
const assigned = this.normalizeUserIds(payload['assignedUserIds']) ?? [];
|
|
1191
|
+
const candidates = this.normalizeUserIds(payload['candidateUserIds']) ?? [];
|
|
1192
|
+
if (assigned.length > 0) {
|
|
1193
|
+
return { success: false, error: 'This task is already claimed' };
|
|
1194
|
+
}
|
|
1195
|
+
const isCartable = activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable;
|
|
1196
|
+
const isPooledHuman = activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask && candidates.length > 0;
|
|
1197
|
+
if (!isCartable && !isPooledHuman) {
|
|
1198
|
+
return { success: false, error: 'This task cannot be claimed' };
|
|
1199
|
+
}
|
|
1200
|
+
if (isCartable && candidates.length === 0) {
|
|
1201
|
+
return { success: false, error: 'Cartable task has no candidate users' };
|
|
1202
|
+
}
|
|
1203
|
+
const candidateSet = new Set(candidates.map((c) => String(c)));
|
|
1204
|
+
if (!candidateSet.has(uid)) {
|
|
1205
|
+
return { success: false, error: 'You are not in the candidate list for this task' };
|
|
1206
|
+
}
|
|
1207
|
+
payload['assignedUserIds'] = [uid];
|
|
1208
|
+
payload['candidateUserIds'] = [];
|
|
1209
|
+
bookmark.payloadJson = JSON.stringify(payload);
|
|
1210
|
+
await this.store.updateBookmark(bookmark);
|
|
1211
|
+
const instance = await this.getInstance(request.instanceId);
|
|
1212
|
+
if (instance) {
|
|
1213
|
+
await this.updateInstanceInEntityService(instance);
|
|
1214
|
+
}
|
|
1215
|
+
return { success: true };
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Claim when execution state lives only on `WorkflowManagement.WorkflowInstance` (seeded / entity-only rows).
|
|
1219
|
+
*/
|
|
1220
|
+
async claimTaskOnWorkflowInstanceEntity(request, uid) {
|
|
1221
|
+
const entityName = 'WorkflowManagement.WorkflowInstance';
|
|
1222
|
+
let row;
|
|
1223
|
+
try {
|
|
1224
|
+
row = (await this.entityStorageService.getOne(entityName, request.instanceId));
|
|
1225
|
+
}
|
|
1226
|
+
catch {
|
|
1227
|
+
return { success: false, error: 'Workflow instance is not suspended or was not found' };
|
|
1228
|
+
}
|
|
1229
|
+
const subStatus = String(row['subStatus'] ?? '');
|
|
1230
|
+
if (subStatus !== 'Suspended') {
|
|
1231
|
+
return { success: false, error: 'Workflow instance is not suspended or was not found' };
|
|
1232
|
+
}
|
|
1233
|
+
const wi = row['workflowInstance'];
|
|
1234
|
+
const bookmarksUnknown = wi?.['bookmarks'];
|
|
1235
|
+
const bookmarks = Array.isArray(bookmarksUnknown) ? [...bookmarksUnknown] : [];
|
|
1236
|
+
const idx = bookmarks.findIndex((b) => {
|
|
1237
|
+
if (typeof b !== 'object' || b === null) {
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
const br = b;
|
|
1241
|
+
return br['id'] === request.bookmarkId && br['activityId'] === request.stepId;
|
|
1242
|
+
});
|
|
1243
|
+
if (idx < 0) {
|
|
1244
|
+
return { success: false, error: 'Bookmark not found or already completed' };
|
|
1245
|
+
}
|
|
1246
|
+
const bookmarkRow = bookmarks[idx];
|
|
1247
|
+
const payloadJson = typeof bookmarkRow['payloadJson'] === 'string' ? bookmarkRow['payloadJson'] : null;
|
|
1248
|
+
let payload = payloadJson ? this.parseBookmarkPayloadJson(payloadJson) : null;
|
|
1249
|
+
if (!payload && bookmarkRow['payload'] && typeof bookmarkRow['payload'] === 'object') {
|
|
1250
|
+
payload = { ...bookmarkRow['payload'] };
|
|
1251
|
+
}
|
|
1252
|
+
if (!payload) {
|
|
1253
|
+
return { success: false, error: 'Invalid bookmark payload' };
|
|
1254
|
+
}
|
|
1255
|
+
const activityType = String(payload['activityType'] ?? '');
|
|
1256
|
+
const assigned = this.normalizeUserIds(payload['assignedUserIds']) ?? [];
|
|
1257
|
+
const candidates = this.normalizeUserIds(payload['candidateUserIds']) ?? [];
|
|
1258
|
+
if (assigned.length > 0) {
|
|
1259
|
+
return { success: false, error: 'This task is already claimed' };
|
|
1260
|
+
}
|
|
1261
|
+
const isCartable = activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable;
|
|
1262
|
+
const isPooledHuman = activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask && candidates.length > 0;
|
|
1263
|
+
if (!isCartable && !isPooledHuman) {
|
|
1264
|
+
return { success: false, error: 'This task cannot be claimed' };
|
|
1265
|
+
}
|
|
1266
|
+
if (isCartable && candidates.length === 0) {
|
|
1267
|
+
return { success: false, error: 'Cartable task has no candidate users' };
|
|
1268
|
+
}
|
|
1269
|
+
const candidateSet = new Set(candidates.map((c) => String(c)));
|
|
1270
|
+
if (!candidateSet.has(uid)) {
|
|
1271
|
+
return { success: false, error: 'You are not in the candidate list for this task' };
|
|
1272
|
+
}
|
|
1273
|
+
payload['assignedUserIds'] = [uid];
|
|
1274
|
+
payload['candidateUserIds'] = [];
|
|
1275
|
+
const nextPayloadJson = JSON.stringify(payload);
|
|
1276
|
+
const nextBookmark = {
|
|
1277
|
+
...bookmarkRow,
|
|
1278
|
+
payload,
|
|
1279
|
+
payloadJson: nextPayloadJson,
|
|
1280
|
+
};
|
|
1281
|
+
const nextBookmarks = [...bookmarks];
|
|
1282
|
+
nextBookmarks[idx] = nextBookmark;
|
|
1283
|
+
if (!wi) {
|
|
1284
|
+
return { success: false, error: 'Workflow instance is not suspended or was not found' };
|
|
1285
|
+
}
|
|
1286
|
+
const nextWi = {
|
|
1287
|
+
...wi,
|
|
1288
|
+
bookmarks: nextBookmarks,
|
|
1289
|
+
};
|
|
1290
|
+
try {
|
|
1291
|
+
await this.entityStorageService.updateOne(entityName, request.instanceId, {
|
|
1292
|
+
workflowInstance: nextWi,
|
|
1293
|
+
updatedAt: new Date().toISOString(),
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
catch (error) {
|
|
1297
|
+
console.warn('[BackendWorkflowEngineService] claimTask entity update failed', error);
|
|
1298
|
+
return { success: false, error: 'Failed to persist claim' };
|
|
1299
|
+
}
|
|
1300
|
+
return { success: true };
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Reassign a human-task bookmark to the current user (permission-gated; does not resume).
|
|
1304
|
+
*/
|
|
1305
|
+
async reassignTaskToSelf(request) {
|
|
1306
|
+
const rawUserId = this.getSessionUser()?.id;
|
|
1307
|
+
if (rawUserId === undefined || rawUserId === null || rawUserId === '') {
|
|
1308
|
+
return { success: false, error: 'You must be signed in to reassign a task' };
|
|
1309
|
+
}
|
|
1310
|
+
const uid = String(rawUserId);
|
|
1311
|
+
if (!this.canReassignTaskToSelf()) {
|
|
1312
|
+
return {
|
|
1313
|
+
success: false,
|
|
1314
|
+
error: 'You do not have permission to reassign this task to yourself',
|
|
1315
|
+
errorCode: workflow_engine_errors_1.AXP_WORKFLOW_ERROR_CODES.REASSIGN_NOT_ALLOWED,
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
const dexieInstance = await this.getInstance(request.instanceId).catch(() => null);
|
|
1319
|
+
if (!dexieInstance) {
|
|
1320
|
+
return this.reassignTaskOnWorkflowInstanceEntity(request, uid);
|
|
1321
|
+
}
|
|
1322
|
+
const state = await this.getState({ instanceId: request.instanceId }).catch(() => null);
|
|
1323
|
+
if (!state || state.status !== 'suspended') {
|
|
1324
|
+
return { success: false, error: 'Workflow instance is not suspended or was not found' };
|
|
1325
|
+
}
|
|
1326
|
+
if (state.currentStepId && state.currentStepId !== request.stepId) {
|
|
1327
|
+
return { success: false, error: 'Step does not match the current suspended step' };
|
|
1328
|
+
}
|
|
1329
|
+
const bookmarks = await this.getActiveBookmarks(request.instanceId);
|
|
1330
|
+
const bookmark = bookmarks.find((b) => b.id === request.bookmarkId && b.activityId === request.stepId);
|
|
1331
|
+
if (!bookmark) {
|
|
1332
|
+
return { success: false, error: 'Bookmark not found or already completed' };
|
|
1333
|
+
}
|
|
1334
|
+
const payload = this.parseBookmarkPayloadJson(bookmark.payloadJson);
|
|
1335
|
+
if (!payload) {
|
|
1336
|
+
return { success: false, error: 'Invalid bookmark payload' };
|
|
1337
|
+
}
|
|
1338
|
+
const validation = this.validateHumanTaskReassignPayload(payload, uid);
|
|
1339
|
+
if (validation.ok === false) {
|
|
1340
|
+
return {
|
|
1341
|
+
success: false,
|
|
1342
|
+
error: validation.error,
|
|
1343
|
+
errorCode: validation.errorCode,
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
const previousAssigned = validation.previousAssigned ?? [];
|
|
1347
|
+
payload['assignedUserIds'] = [uid];
|
|
1348
|
+
bookmark.payloadJson = JSON.stringify(payload);
|
|
1349
|
+
await this.store.updateBookmark(bookmark);
|
|
1350
|
+
const auditPatch = this.buildReassignAuditVariablePatch(request.stepId, previousAssigned, uid, state.variables);
|
|
1351
|
+
const nextState = {
|
|
1352
|
+
...state,
|
|
1353
|
+
variables: {
|
|
1354
|
+
...(state.variables || {}),
|
|
1355
|
+
...auditPatch,
|
|
1356
|
+
},
|
|
1357
|
+
lastUpdated: new Date(),
|
|
1358
|
+
};
|
|
1359
|
+
await this.updateInstanceFromState(request.instanceId, nextState);
|
|
1360
|
+
const instance = await this.getInstance(request.instanceId);
|
|
1361
|
+
if (instance) {
|
|
1362
|
+
await this.updateInstanceInEntityService(instance);
|
|
1363
|
+
}
|
|
1364
|
+
return { success: true };
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Reassign when execution state lives only on `WorkflowManagement.WorkflowInstance` (entity-only rows).
|
|
1368
|
+
*/
|
|
1369
|
+
async reassignTaskOnWorkflowInstanceEntity(request, uid) {
|
|
1370
|
+
const entityName = 'WorkflowManagement.WorkflowInstance';
|
|
1371
|
+
let row;
|
|
1372
|
+
try {
|
|
1373
|
+
row = (await this.entityStorageService.getOne(entityName, request.instanceId));
|
|
1374
|
+
}
|
|
1375
|
+
catch {
|
|
1376
|
+
return { success: false, error: 'Workflow instance is not suspended or was not found' };
|
|
1377
|
+
}
|
|
1378
|
+
const subStatus = String(row['subStatus'] ?? '');
|
|
1379
|
+
if (subStatus !== 'Suspended') {
|
|
1380
|
+
return { success: false, error: 'Workflow instance is not suspended or was not found' };
|
|
1381
|
+
}
|
|
1382
|
+
const wi = row['workflowInstance'];
|
|
1383
|
+
const bookmarksUnknown = wi?.['bookmarks'];
|
|
1384
|
+
const bookmarks = Array.isArray(bookmarksUnknown) ? [...bookmarksUnknown] : [];
|
|
1385
|
+
const idx = bookmarks.findIndex((b) => {
|
|
1386
|
+
if (typeof b !== 'object' || b === null) {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
const br = b;
|
|
1390
|
+
return br['id'] === request.bookmarkId && br['activityId'] === request.stepId;
|
|
1391
|
+
});
|
|
1392
|
+
if (idx < 0) {
|
|
1393
|
+
return { success: false, error: 'Bookmark not found or already completed' };
|
|
1394
|
+
}
|
|
1395
|
+
const bookmarkRow = bookmarks[idx];
|
|
1396
|
+
const payloadJson = typeof bookmarkRow['payloadJson'] === 'string' ? bookmarkRow['payloadJson'] : null;
|
|
1397
|
+
let payload = payloadJson ? this.parseBookmarkPayloadJson(payloadJson) : null;
|
|
1398
|
+
if (!payload && bookmarkRow['payload'] && typeof bookmarkRow['payload'] === 'object') {
|
|
1399
|
+
payload = { ...bookmarkRow['payload'] };
|
|
1400
|
+
}
|
|
1401
|
+
if (!payload) {
|
|
1402
|
+
return { success: false, error: 'Invalid bookmark payload' };
|
|
1403
|
+
}
|
|
1404
|
+
const validation = this.validateHumanTaskReassignPayload(payload, uid);
|
|
1405
|
+
if (validation.ok === false) {
|
|
1406
|
+
return {
|
|
1407
|
+
success: false,
|
|
1408
|
+
error: validation.error,
|
|
1409
|
+
errorCode: validation.errorCode,
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
const previousAssigned = validation.previousAssigned ?? [];
|
|
1413
|
+
payload['assignedUserIds'] = [uid];
|
|
1414
|
+
const nextPayloadJson = JSON.stringify(payload);
|
|
1415
|
+
const nextBookmark = {
|
|
1416
|
+
...bookmarkRow,
|
|
1417
|
+
payload,
|
|
1418
|
+
payloadJson: nextPayloadJson,
|
|
1419
|
+
};
|
|
1420
|
+
const nextBookmarks = [...bookmarks];
|
|
1421
|
+
nextBookmarks[idx] = nextBookmark;
|
|
1422
|
+
if (!wi) {
|
|
1423
|
+
return { success: false, error: 'Workflow instance is not suspended or was not found' };
|
|
1424
|
+
}
|
|
1425
|
+
const workflowState = wi['workflowState'] ?? {};
|
|
1426
|
+
const existingProperties = workflowState['properties'] ?? {};
|
|
1427
|
+
const auditPatch = this.buildReassignAuditVariablePatch(request.stepId, previousAssigned, uid, existingProperties);
|
|
1428
|
+
const properties = {
|
|
1429
|
+
...existingProperties,
|
|
1430
|
+
...auditPatch,
|
|
1431
|
+
};
|
|
1432
|
+
const nextWi = {
|
|
1433
|
+
...wi,
|
|
1434
|
+
bookmarks: nextBookmarks,
|
|
1435
|
+
workflowState: {
|
|
1436
|
+
...workflowState,
|
|
1437
|
+
properties,
|
|
1438
|
+
},
|
|
1439
|
+
};
|
|
1440
|
+
try {
|
|
1441
|
+
await this.entityStorageService.updateOne(entityName, request.instanceId, {
|
|
1442
|
+
workflowInstance: nextWi,
|
|
1443
|
+
updatedAt: new Date().toISOString(),
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
catch (error) {
|
|
1447
|
+
console.warn('[BackendWorkflowEngineService] reassignTaskToSelf entity update failed', error);
|
|
1448
|
+
return { success: false, error: 'Failed to persist reassignment' };
|
|
1449
|
+
}
|
|
1450
|
+
return { success: true };
|
|
1451
|
+
}
|
|
1452
|
+
canReassignTaskToSelf() {
|
|
1453
|
+
return !!this.getSessionUser()?.id;
|
|
1454
|
+
}
|
|
1455
|
+
validateHumanTaskReassignPayload(payload, uid) {
|
|
1456
|
+
const activityType = String(payload['activityType'] ?? '');
|
|
1457
|
+
if (activityType !== workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask) {
|
|
1458
|
+
return { ok: false, error: 'Only human tasks can be reassigned to yourself' };
|
|
1459
|
+
}
|
|
1460
|
+
const assigned = this.normalizeUserIds(payload['assignedUserIds']) ?? [];
|
|
1461
|
+
if (assigned.length === 0) {
|
|
1462
|
+
return { ok: false, error: 'This task has no assignee to reassign from' };
|
|
1463
|
+
}
|
|
1464
|
+
if (assigned.includes(uid)) {
|
|
1465
|
+
return { ok: false, error: 'You are already assigned to this task' };
|
|
1466
|
+
}
|
|
1467
|
+
// TODO(workflow): Require current user to be in candidateUserIds (or role candidates) before self-reassign.
|
|
1468
|
+
// Permission alone is sufficient for now; tighten assignee pool validation in a follow-up.
|
|
1469
|
+
return { ok: true, previousAssigned: assigned };
|
|
1470
|
+
}
|
|
1471
|
+
buildReassignAuditVariablePatch(stepId, fromUserIds, toUserId, existingVariables) {
|
|
1472
|
+
const at = new Date().toISOString();
|
|
1473
|
+
const entry = {
|
|
1474
|
+
stepId,
|
|
1475
|
+
fromUserIds,
|
|
1476
|
+
toUserId,
|
|
1477
|
+
at,
|
|
1478
|
+
};
|
|
1479
|
+
const priorHistory = existingVariables?.['taskReassignHistory'];
|
|
1480
|
+
const history = Array.isArray(priorHistory) ? [...priorHistory, entry] : [entry];
|
|
1481
|
+
return {
|
|
1482
|
+
[`${stepId}_reassignedToSelf`]: entry,
|
|
1483
|
+
taskReassignHistory: history,
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
//#region ---- Task-Based Helper Methods ----
|
|
1487
|
+
/**
|
|
1488
|
+
* Activities that suspend for the task board (inbox), same completion path as human-task.
|
|
1489
|
+
*/
|
|
1490
|
+
isTaskBoardSuspendGraphActivity(activityType) {
|
|
1491
|
+
return activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask || activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable;
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Determine execution mode for an activity (async).
|
|
1495
|
+
*
|
|
1496
|
+
* Priority (highest to lowest):
|
|
1497
|
+
* 1. Activity instance's executionMode (from AXPActivity in workflow definition)
|
|
1498
|
+
* 2. ActivityDefinition's executionMode (from ActivityDefinitionService)
|
|
1499
|
+
* 3. Default: 'frontend'
|
|
1500
|
+
*
|
|
1501
|
+
* @param activityType - Activity type (e.g., 'workflow-activity:show-toast')
|
|
1502
|
+
* @param activity - Activity from workflow definition (AXPActivity)
|
|
1503
|
+
* @returns Promise of execution mode: 'frontend', 'backend', or 'both'
|
|
1504
|
+
*/
|
|
1505
|
+
async determineExecutionMode(activityType, activity) {
|
|
1506
|
+
// 1. Check if executionMode is explicitly set in activity instance (highest priority)
|
|
1507
|
+
if (activity?.metadata && activity.metadata['executionMode']) {
|
|
1508
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask) {
|
|
1509
|
+
console.log(`[BackendWorkflowEngineService] determineExecutionMode(${activityType}): from instance metadata →`, activity.metadata['executionMode']);
|
|
1510
|
+
}
|
|
1511
|
+
return activity.metadata['executionMode'];
|
|
1512
|
+
}
|
|
1513
|
+
// 2. Check cache
|
|
1514
|
+
const cached = this.activityDefinitionCache.get(activityType);
|
|
1515
|
+
if (cached && Date.now() - cached.cachedAt.getTime() < this.CACHE_TTL_MS) {
|
|
1516
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask) {
|
|
1517
|
+
console.log(`[BackendWorkflowEngineService] determineExecutionMode(${activityType}): from cache →`, cached.executionMode);
|
|
1518
|
+
}
|
|
1519
|
+
return cached.executionMode;
|
|
1520
|
+
}
|
|
1521
|
+
// 3. Query ActivityDefinitionService
|
|
1522
|
+
try {
|
|
1523
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
1524
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask) {
|
|
1525
|
+
console.log(`[BackendWorkflowEngineService] determineExecutionMode(${activityType}): from definition →`, {
|
|
1526
|
+
definitionFound: !!activityDef,
|
|
1527
|
+
executionMode: activityDef?.executionMode,
|
|
1528
|
+
taskType: activityDef?.taskType,
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
if (activityDef?.executionMode) {
|
|
1532
|
+
this.activityDefinitionCache.set(activityType, {
|
|
1533
|
+
executionMode: activityDef.executionMode,
|
|
1534
|
+
cachedAt: new Date(),
|
|
1535
|
+
});
|
|
1536
|
+
return activityDef.executionMode;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
catch (error) {
|
|
1540
|
+
console.warn(`[BackendWorkflowEngineService] ⚠️ Could not get activity definition for ${activityType}:`, error);
|
|
1541
|
+
}
|
|
1542
|
+
// 4. Default to frontend
|
|
1543
|
+
const defaultMode = 'frontend';
|
|
1544
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask) {
|
|
1545
|
+
console.log(`[BackendWorkflowEngineService] determineExecutionMode(${activityType}): using default →`, defaultMode);
|
|
1546
|
+
}
|
|
1547
|
+
this.activityDefinitionCache.set(activityType, {
|
|
1548
|
+
executionMode: defaultMode,
|
|
1549
|
+
cachedAt: new Date(),
|
|
1550
|
+
});
|
|
1551
|
+
return defaultMode;
|
|
1552
|
+
}
|
|
1553
|
+
//#endregion
|
|
1554
|
+
/**
|
|
1555
|
+
* Create a task from a workflow activity.
|
|
1556
|
+
* Inspired by Temporal's Activity Task and Elsa's Bookmark pattern.
|
|
1557
|
+
* Uses same expression scope (inputs, variables, outputs, context.eval) as backend execution.
|
|
1558
|
+
*/
|
|
1559
|
+
async createTaskFromActivity(activity, state) {
|
|
1560
|
+
const rawInputs = activity.inputs || {};
|
|
1561
|
+
const scope = this.expressionScopeService.buildScopeFromState(state, this.ensureOutputsForScope(state.lastActivityOutput));
|
|
1562
|
+
const properties = await this.expressionEvaluator.evaluate(rawInputs, scope);
|
|
1563
|
+
if (rawInputs['persistInWorkflow'] !== undefined && properties['persistInWorkflow'] === undefined) {
|
|
1564
|
+
properties['persistInWorkflow'] = await this.expressionEvaluator.evaluate(rawInputs['persistInWorkflow'], scope);
|
|
1565
|
+
}
|
|
1566
|
+
// Activity name is the registry key (e.g., 'workflow-activity:show-toast')
|
|
1567
|
+
const activityType = activity.name;
|
|
1568
|
+
// Get executionMode from activity descriptor (async)
|
|
1569
|
+
const executionMode = await this.determineExecutionMode(activityType, activity);
|
|
1570
|
+
// Get activity definition for title and taskType (taskType === 'human-task' → suspend for cartable)
|
|
1571
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
1572
|
+
const taskType = activity.metadata?.['taskType'] ?? activityDef?.taskType ?? 'ui-activity';
|
|
1573
|
+
return {
|
|
1574
|
+
taskToken: this.generateTaskToken(state.instanceId, activity.id),
|
|
1575
|
+
activityId: activity.id,
|
|
1576
|
+
activityType,
|
|
1577
|
+
activityName: (0, multilanguage_util_1.resolveMultiLanguageString)(activityDef?.title) || activity.name || undefined,
|
|
1578
|
+
executionMode,
|
|
1579
|
+
taskType: taskType,
|
|
1580
|
+
input: executionMode === 'frontend' || executionMode === 'both' ? properties : undefined,
|
|
1581
|
+
config: executionMode === 'frontend' || executionMode === 'both' ? properties : undefined,
|
|
1582
|
+
continuation: activityDef?.workflowContinuation,
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Build variables table for entity: only variables defined in workflow definition (name, title, value).
|
|
1587
|
+
*/
|
|
1588
|
+
computeWorkflowVariablesTable(instance, definition) {
|
|
1589
|
+
const vars = instance.workflowState?.properties ?? {};
|
|
1590
|
+
const out = instance.workflowState?.output ?? {};
|
|
1591
|
+
const getValue = (key) => vars[key] ?? out[key];
|
|
1592
|
+
const formatValue = (v) => {
|
|
1593
|
+
if (v === null || v === undefined)
|
|
1594
|
+
return '';
|
|
1595
|
+
if (typeof v === 'object')
|
|
1596
|
+
return JSON.stringify(v);
|
|
1597
|
+
return String(v);
|
|
1598
|
+
};
|
|
1599
|
+
const defVars = definition?.variables ?? [];
|
|
1600
|
+
return defVars.map((v) => ({
|
|
1601
|
+
name: v.name,
|
|
1602
|
+
title: (0, multilanguage_util_1.resolveMultiLanguageString)(v.title, 'en-US') || v.name,
|
|
1603
|
+
value: formatValue(getValue(v.name)),
|
|
1604
|
+
}));
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Compute overview display fields for entity model (statusDisplay, step progress, etc.).
|
|
1608
|
+
*/
|
|
1609
|
+
computeOverviewDisplayFields(instance, definition) {
|
|
1610
|
+
const workflowTitle = instance.name || instance.definitionId || instance.id;
|
|
1611
|
+
const statusDisplay = this.getWorkflowStatusDisplay(instance.status, instance.subStatus);
|
|
1612
|
+
const contexts = instance.workflowState?.activityExecutionContexts ?? [];
|
|
1613
|
+
const graph = definition?.graph;
|
|
1614
|
+
const allActivities = graph?.activities ?? [];
|
|
1615
|
+
const allConnections = graph?.connections ?? [];
|
|
1616
|
+
// Fallback to previous linear logic when definition graph is unavailable.
|
|
1617
|
+
if (!graph || allActivities.length === 0) {
|
|
1618
|
+
const completedCount = contexts.filter((ctx) => ctx.status === 'Completed').length;
|
|
1619
|
+
const hasRunning = contexts.some((ctx) => ctx.status === 'Running');
|
|
1620
|
+
const currentStepIndex = hasRunning ? completedCount + 1 : completedCount;
|
|
1621
|
+
const totalSteps = instance.workflowState?.totalSteps ?? (contexts.length > 0 ? contexts.length : 0);
|
|
1622
|
+
const progressPercentage = totalSteps > 0 ? Math.min(100, Math.round((currentStepIndex / totalSteps) * 100)) : 0;
|
|
1623
|
+
return { workflowTitle, statusDisplay, currentStepIndex, totalSteps, progressPercentage };
|
|
1624
|
+
}
|
|
1625
|
+
const completedSet = new Set(contexts
|
|
1626
|
+
.filter((ctx) => ctx.status === 'Completed')
|
|
1627
|
+
.map((ctx) => ctx.scheduledActivityNodeId)
|
|
1628
|
+
.filter((id) => !!id));
|
|
1629
|
+
const runningSet = new Set(contexts
|
|
1630
|
+
.filter((ctx) => ctx.status === 'Running')
|
|
1631
|
+
.map((ctx) => ctx.scheduledActivityNodeId)
|
|
1632
|
+
.filter((id) => !!id));
|
|
1633
|
+
const visitedSet = new Set([...completedSet, ...runningSet]);
|
|
1634
|
+
const currentStepId = Array.from(runningSet)[0] ?? undefined;
|
|
1635
|
+
// Extract chosen outcomes from workflow state (written as `${stepId}_outcome`).
|
|
1636
|
+
const outcomeByStep = new Map();
|
|
1637
|
+
const stateProperties = instance.workflowState?.properties ?? {};
|
|
1638
|
+
for (const [key, value] of Object.entries(stateProperties)) {
|
|
1639
|
+
if (!key.endsWith('_outcome') || typeof value !== 'string') {
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
const stepId = key.slice(0, -'_outcome'.length);
|
|
1643
|
+
if (stepId) {
|
|
1644
|
+
outcomeByStep.set(stepId, value);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
// Build incoming map to infer start step when startActivityId is not set.
|
|
1648
|
+
const incomingMap = new Map();
|
|
1649
|
+
for (const connection of allConnections) {
|
|
1650
|
+
const targetId = connection.target.activtyName;
|
|
1651
|
+
incomingMap.set(targetId, (incomingMap.get(targetId) ?? 0) + 1);
|
|
1652
|
+
}
|
|
1653
|
+
let startStepId = graph.startActivityId;
|
|
1654
|
+
if (!startStepId) {
|
|
1655
|
+
const root = allActivities.find((activity) => (incomingMap.get(activity.id) ?? 0) === 0);
|
|
1656
|
+
startStepId = root?.id ?? allActivities[0]?.id;
|
|
1657
|
+
}
|
|
1658
|
+
// Resolve the "active path" by following decided outcomes and preferring visited targets.
|
|
1659
|
+
const resolveNextStepId = (stepId) => {
|
|
1660
|
+
const outgoing = allConnections.filter((connection) => connection.source.activtyName === stepId);
|
|
1661
|
+
if (outgoing.length === 0) {
|
|
1662
|
+
return null;
|
|
1663
|
+
}
|
|
1664
|
+
const chosenOutcome = outcomeByStep.get(stepId);
|
|
1665
|
+
if (chosenOutcome) {
|
|
1666
|
+
const exact = outgoing.find((connection) => connection.source.port === chosenOutcome);
|
|
1667
|
+
if (exact) {
|
|
1668
|
+
return exact.target.activtyName;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
if (currentStepId) {
|
|
1672
|
+
const toCurrent = outgoing.find((connection) => connection.target.activtyName === currentStepId);
|
|
1673
|
+
if (toCurrent) {
|
|
1674
|
+
return toCurrent.target.activtyName;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
const toVisited = outgoing.find((connection) => visitedSet.has(connection.target.activtyName));
|
|
1678
|
+
if (toVisited) {
|
|
1679
|
+
return toVisited.target.activtyName;
|
|
1680
|
+
}
|
|
1681
|
+
const donePort = outgoing.find((connection) => connection.source.port === 'Done');
|
|
1682
|
+
if (donePort) {
|
|
1683
|
+
return donePort.target.activtyName;
|
|
1684
|
+
}
|
|
1685
|
+
return outgoing[0]?.target.activtyName ?? null;
|
|
1686
|
+
};
|
|
1687
|
+
const path = [];
|
|
1688
|
+
const seen = new Set();
|
|
1689
|
+
const maxDepth = Math.max(1, allActivities.length * 3);
|
|
1690
|
+
let cursor = startStepId;
|
|
1691
|
+
while (cursor && !seen.has(cursor) && path.length < maxDepth) {
|
|
1692
|
+
path.push(cursor);
|
|
1693
|
+
seen.add(cursor);
|
|
1694
|
+
if (currentStepId && cursor === currentStepId) {
|
|
1695
|
+
break;
|
|
1696
|
+
}
|
|
1697
|
+
cursor = resolveNextStepId(cursor);
|
|
1698
|
+
}
|
|
1699
|
+
if (currentStepId && !path.includes(currentStepId)) {
|
|
1700
|
+
path.push(currentStepId);
|
|
1701
|
+
}
|
|
1702
|
+
const totalSteps = path.length > 0 ? path.length : allActivities.length;
|
|
1703
|
+
const completedCount = path.filter((stepId) => completedSet.has(stepId)).length;
|
|
1704
|
+
const hasRunning = !!currentStepId && path.includes(currentStepId);
|
|
1705
|
+
const currentStepIndex = Math.min(totalSteps, hasRunning ? completedCount + 1 : completedCount);
|
|
1706
|
+
const progressPercentage = instance.status === 'Finished'
|
|
1707
|
+
? 100
|
|
1708
|
+
: totalSteps > 0
|
|
1709
|
+
? Math.min(100, Math.round((currentStepIndex / totalSteps) * 100))
|
|
1710
|
+
: 0;
|
|
1711
|
+
return { workflowTitle, statusDisplay, currentStepIndex, totalSteps, progressPercentage };
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Compute runtime details for task board style overview fields.
|
|
1715
|
+
*/
|
|
1716
|
+
computeRuntimeDetails(instance, definition, activeBookmarks) {
|
|
1717
|
+
const currentStepId = this.resolveCurrentStepId(instance, activeBookmarks);
|
|
1718
|
+
if (!definition?.graph || !currentStepId) {
|
|
1719
|
+
return {
|
|
1720
|
+
currentActivityTitle: 'N/A',
|
|
1721
|
+
nextActivityTitle: instance.status === 'Finished' ? 'Completed' : 'N/A',
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
const graph = definition.graph;
|
|
1725
|
+
const currentActivity = this.findActivityInGraph(graph, currentStepId);
|
|
1726
|
+
const primaryBookmark = activeBookmarks.find((bookmark) => bookmark.activityId === currentStepId) ?? activeBookmarks[0] ?? null;
|
|
1727
|
+
const bookmarkPayload = this.parseBookmarkPayloadJson(primaryBookmark?.payloadJson);
|
|
1728
|
+
const currentActivityTitle = this.resolveActivityTitle(currentActivity, bookmarkPayload, currentStepId);
|
|
1729
|
+
const nextActivityTitle = this.resolveNextActivityTitle(graph, currentStepId, instance, definition);
|
|
1730
|
+
return {
|
|
1731
|
+
currentActivityTitle,
|
|
1732
|
+
nextActivityTitle,
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Human-task / cartable bookmark types that surface on the task board.
|
|
1737
|
+
*/
|
|
1738
|
+
isTaskBoardHumanBookmarkPayload(payload) {
|
|
1739
|
+
if (!payload) {
|
|
1740
|
+
return false;
|
|
1741
|
+
}
|
|
1742
|
+
const t = payload['activityType'];
|
|
1743
|
+
return t === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask || t === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable;
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Primary user id for assignee column: suspended task-board bookmark (assigned, then candidate users),
|
|
1747
|
+
* otherwise last completed step with a persisted user id.
|
|
1748
|
+
*/
|
|
1749
|
+
resolvePrimaryAssignUserId(instance, activeBookmarks) {
|
|
1750
|
+
const suspended = instance.subStatus === 'Suspended';
|
|
1751
|
+
if (suspended && activeBookmarks.length > 0) {
|
|
1752
|
+
const sorted = [...activeBookmarks].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
1753
|
+
for (const bm of sorted) {
|
|
1754
|
+
const payload = this.parseBookmarkPayloadJson(bm.payloadJson);
|
|
1755
|
+
if (!this.isTaskBoardHumanBookmarkPayload(payload)) {
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
const assigned = this.normalizeUserIds(payload?.['assignedUserIds']) ?? [];
|
|
1759
|
+
if (assigned.length > 0) {
|
|
1760
|
+
return assigned[0];
|
|
1761
|
+
}
|
|
1762
|
+
const candidates = this.normalizeUserIds(payload?.['candidateUserIds']) ?? [];
|
|
1763
|
+
if (candidates.length > 0) {
|
|
1764
|
+
return candidates[0];
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
return this.resolveLastPerformerUserId(instance);
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Last completed activity's `completedByUserId` from workflow state properties.
|
|
1772
|
+
*/
|
|
1773
|
+
resolveLastPerformerUserId(instance) {
|
|
1774
|
+
const props = (instance.workflowState?.properties ?? {});
|
|
1775
|
+
const contexts = [...(instance.workflowState?.activityExecutionContexts ?? [])];
|
|
1776
|
+
contexts.sort((a, b) => new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime());
|
|
1777
|
+
let lastId;
|
|
1778
|
+
for (const ctx of contexts) {
|
|
1779
|
+
const completed = ctx.status === 'Completed' || (ctx.completedAt != null && String(ctx.completedAt).trim().length > 0);
|
|
1780
|
+
if (!completed) {
|
|
1781
|
+
continue;
|
|
1782
|
+
}
|
|
1783
|
+
const nodeId = ctx.scheduledActivityNodeId;
|
|
1784
|
+
const uid = props[`${nodeId}_completedByUserId`];
|
|
1785
|
+
if (uid != null && String(uid).trim() !== '') {
|
|
1786
|
+
lastId = String(uid).trim();
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return lastId;
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Lookup user row for WorkflowInstance list/detail lookup column.
|
|
1793
|
+
*/
|
|
1794
|
+
async resolveAssignUserLookupFields(userId) {
|
|
1795
|
+
if (!userId) {
|
|
1796
|
+
return {};
|
|
1797
|
+
}
|
|
1798
|
+
const entityName = 'SecurityManagement.User';
|
|
1799
|
+
try {
|
|
1800
|
+
const row = await this.entityStorageService.getOne(entityName, userId);
|
|
1801
|
+
const displayName = row && typeof row === 'object' && row !== null && 'displayName' in row
|
|
1802
|
+
? String(row.displayName ?? '').trim()
|
|
1803
|
+
: '';
|
|
1804
|
+
const label = displayName.length > 0 ? displayName : userId;
|
|
1805
|
+
return {
|
|
1806
|
+
assignUser: { id: userId },
|
|
1807
|
+
assignUserRef: { id: userId, displayName: label },
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
catch {
|
|
1811
|
+
return {
|
|
1812
|
+
assignUser: { id: userId },
|
|
1813
|
+
assignUserRef: { id: userId, displayName: userId },
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Resolve current step from active bookmark first, then running context.
|
|
1819
|
+
*/
|
|
1820
|
+
resolveCurrentStepId(instance, activeBookmarks) {
|
|
1821
|
+
if (activeBookmarks.length > 0) {
|
|
1822
|
+
const latestBookmark = [...activeBookmarks].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0];
|
|
1823
|
+
return latestBookmark.activityId ?? null;
|
|
1824
|
+
}
|
|
1825
|
+
const contexts = instance.workflowState?.activityExecutionContexts ?? [];
|
|
1826
|
+
const runningContext = contexts.find((ctx) => ctx.status === 'Running' && !ctx.completedAt);
|
|
1827
|
+
return runningContext?.scheduledActivityNodeId ?? null;
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Resolve friendly activity title using payload/title inputs and fallback label.
|
|
1831
|
+
*/
|
|
1832
|
+
resolveActivityTitle(activity, bookmarkPayload, fallbackId) {
|
|
1833
|
+
const payloadTitle = bookmarkPayload?.['title'];
|
|
1834
|
+
if (typeof payloadTitle === 'string' && payloadTitle.trim().length > 0) {
|
|
1835
|
+
return payloadTitle.trim();
|
|
1836
|
+
}
|
|
1837
|
+
const inputTitle = activity?.inputs?.['title'];
|
|
1838
|
+
if (typeof inputTitle === 'string' && inputTitle.trim().length > 0) {
|
|
1839
|
+
return inputTitle.trim();
|
|
1840
|
+
}
|
|
1841
|
+
return this.formatReadableLabel(activity?.id ?? fallbackId);
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Resolve next activity title from workflow graph and known outcome.
|
|
1845
|
+
*/
|
|
1846
|
+
resolveNextActivityTitle(graph, currentStepId, instance, definition) {
|
|
1847
|
+
const outgoing = (graph.connections ?? []).filter((connection) => connection.source.activtyName === currentStepId);
|
|
1848
|
+
if (outgoing.length === 0) {
|
|
1849
|
+
return instance.status === 'Finished' ? 'Completed' : 'N/A';
|
|
1850
|
+
}
|
|
1851
|
+
const currentOutcomeKey = `${currentStepId}_outcome`;
|
|
1852
|
+
const currentOutcome = instance.workflowState?.properties?.[currentOutcomeKey];
|
|
1853
|
+
if (typeof currentOutcome === 'string' && currentOutcome.length > 0) {
|
|
1854
|
+
const matched = outgoing.find((connection) => connection.source.port === currentOutcome);
|
|
1855
|
+
if (matched) {
|
|
1856
|
+
const target = this.findActivityInGraph(graph, matched.target.activtyName);
|
|
1857
|
+
return this.resolveActivityTitle(target, null, matched.target.activtyName);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
if (outgoing.length === 1) {
|
|
1861
|
+
const target = this.findActivityInGraph(graph, outgoing[0].target.activtyName);
|
|
1862
|
+
return this.resolveActivityTitle(target, null, outgoing[0].target.activtyName);
|
|
1863
|
+
}
|
|
1864
|
+
const nextOptions = outgoing.map((connection) => {
|
|
1865
|
+
const target = this.findActivityInGraph(graph, connection.target.activtyName);
|
|
1866
|
+
const targetTitle = this.resolveActivityTitle(target, null, connection.target.activtyName);
|
|
1867
|
+
const port = connection.source.port || 'Done';
|
|
1868
|
+
return `${port}: ${targetTitle}`;
|
|
1869
|
+
});
|
|
1870
|
+
return nextOptions.join(' | ');
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Parse bookmark payload safely.
|
|
1874
|
+
*/
|
|
1875
|
+
parseBookmarkPayloadJson(payloadJson) {
|
|
1876
|
+
if (!payloadJson) {
|
|
1877
|
+
return null;
|
|
1878
|
+
}
|
|
1879
|
+
try {
|
|
1880
|
+
const parsed = JSON.parse(payloadJson);
|
|
1881
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
1882
|
+
}
|
|
1883
|
+
catch {
|
|
1884
|
+
return null;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Convert technical identifier to readable title-case label.
|
|
1889
|
+
*/
|
|
1890
|
+
formatReadableLabel(value) {
|
|
1891
|
+
return value
|
|
1892
|
+
.replace(/^workflow-activity:/, '')
|
|
1893
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
1894
|
+
.replace(/[-_]/g, ' ')
|
|
1895
|
+
.split(' ')
|
|
1896
|
+
.filter((token) => token.length > 0)
|
|
1897
|
+
.map((token) => token.charAt(0).toUpperCase() + token.slice(1))
|
|
1898
|
+
.join(' ');
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Build a user-friendly task board status from workflow status and sub-status.
|
|
1902
|
+
* Returns plain English labels instead of translation keys.
|
|
1903
|
+
*/
|
|
1904
|
+
getWorkflowStatusDisplay(status, subStatus) {
|
|
1905
|
+
const normalizedStatus = (status ?? '').toString().trim().toLowerCase();
|
|
1906
|
+
const normalizedSubStatus = (subStatus ?? '').toString().trim().toLowerCase();
|
|
1907
|
+
const stateKey = `${normalizedStatus}:${normalizedSubStatus}`;
|
|
1908
|
+
const combinedLabels = {
|
|
1909
|
+
'running:executing': 'In Progress',
|
|
1910
|
+
'running:suspended': 'Pending User Action',
|
|
1911
|
+
'running:idle': 'Waiting',
|
|
1912
|
+
running: 'In Progress',
|
|
1913
|
+
'finished:finished': 'Completed',
|
|
1914
|
+
'finished:faulted': 'Failed',
|
|
1915
|
+
'finished:cancelled': 'Cancelled',
|
|
1916
|
+
finished: 'Completed',
|
|
1917
|
+
};
|
|
1918
|
+
const exactMatch = combinedLabels[stateKey];
|
|
1919
|
+
if (exactMatch) {
|
|
1920
|
+
return exactMatch;
|
|
1921
|
+
}
|
|
1922
|
+
const statusOnlyMatch = combinedLabels[normalizedStatus];
|
|
1923
|
+
if (statusOnlyMatch) {
|
|
1924
|
+
return statusOnlyMatch;
|
|
1925
|
+
}
|
|
1926
|
+
if (normalizedSubStatus.includes('suspend')) {
|
|
1927
|
+
return 'Pending User Action';
|
|
1928
|
+
}
|
|
1929
|
+
if (normalizedSubStatus.includes('fault') || normalizedSubStatus.includes('error')) {
|
|
1930
|
+
return 'Failed';
|
|
1931
|
+
}
|
|
1932
|
+
if (normalizedSubStatus.includes('cancel')) {
|
|
1933
|
+
return 'Cancelled';
|
|
1934
|
+
}
|
|
1935
|
+
if (normalizedSubStatus.includes('finish') || normalizedSubStatus.includes('complete')) {
|
|
1936
|
+
return 'Completed';
|
|
1937
|
+
}
|
|
1938
|
+
if (normalizedSubStatus.includes('execut')) {
|
|
1939
|
+
return 'In Progress';
|
|
1940
|
+
}
|
|
1941
|
+
const fallbackTokens = [status, subStatus]
|
|
1942
|
+
.filter((value) => typeof value === 'string' && value.trim().length > 0)
|
|
1943
|
+
.map((value) => value.replace(/([a-z])([A-Z])/g, '$1 $2').trim());
|
|
1944
|
+
return fallbackTokens.length > 0 ? fallbackTokens.join(' - ') : 'Unknown';
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Save workflow instance to entity service for entity-based access.
|
|
1948
|
+
*/
|
|
1949
|
+
async saveInstanceToEntityService(instance) {
|
|
1950
|
+
try {
|
|
1951
|
+
// Get active bookmarks for this instance to include in entity model
|
|
1952
|
+
const activeBookmarks = await this.getActiveBookmarks(instance.id);
|
|
1953
|
+
// Add bookmarks to instance for entity storage access
|
|
1954
|
+
const instanceWithBookmarks = {
|
|
1955
|
+
...instance,
|
|
1956
|
+
bookmarks: activeBookmarks.map((b) => ({
|
|
1957
|
+
id: b.id,
|
|
1958
|
+
activityId: b.activityId,
|
|
1959
|
+
activityInstanceId: b.activityId, // For compatibility
|
|
1960
|
+
type: b.type,
|
|
1961
|
+
payload: b.payloadJson ? JSON.parse(b.payloadJson) : null,
|
|
1962
|
+
payloadJson: b.payloadJson,
|
|
1963
|
+
consumed: b.isConsumed,
|
|
1964
|
+
isConsumed: b.isConsumed,
|
|
1965
|
+
createdAt: b.createdAt,
|
|
1966
|
+
})),
|
|
1967
|
+
};
|
|
1968
|
+
const definition = await this.getDefinition(instance.definitionId);
|
|
1969
|
+
const overview = this.computeOverviewDisplayFields(instance, definition);
|
|
1970
|
+
const runtimeDetails = this.computeRuntimeDetails(instance, definition, activeBookmarks);
|
|
1971
|
+
const workflowVariablesTable = this.computeWorkflowVariablesTable(instance, definition);
|
|
1972
|
+
const assignUserFields = await this.resolveAssignUserLookupFields(this.resolvePrimaryAssignUserId(instance, activeBookmarks));
|
|
1973
|
+
const entityModel = {
|
|
1974
|
+
id: instance.id,
|
|
1975
|
+
definitionId: instance.definitionId,
|
|
1976
|
+
definitionVersionId: instance.definitionVersionId,
|
|
1977
|
+
version: instance.version,
|
|
1978
|
+
status: instance.status,
|
|
1979
|
+
subStatus: instance.subStatus,
|
|
1980
|
+
correlationId: instance.correlationId || undefined,
|
|
1981
|
+
name: instance.name || undefined,
|
|
1982
|
+
incidentCount: instance.incidentCount,
|
|
1983
|
+
parentWorkflowInstanceId: instance.parentWorkflowInstanceId || undefined,
|
|
1984
|
+
createdAt: instance.createdAt,
|
|
1985
|
+
updatedAt: instance.updatedAt,
|
|
1986
|
+
finishedAt: instance.finishedAt || undefined,
|
|
1987
|
+
workflowInstance: instanceWithBookmarks, // Include bookmarks
|
|
1988
|
+
workflowTitle: overview.workflowTitle,
|
|
1989
|
+
statusDisplay: overview.statusDisplay,
|
|
1990
|
+
startedAt: instance.createdAt,
|
|
1991
|
+
...assignUserFields,
|
|
1992
|
+
currentActivityTitle: runtimeDetails.currentActivityTitle,
|
|
1993
|
+
nextActivityTitle: runtimeDetails.nextActivityTitle,
|
|
1994
|
+
currentStepIndex: overview.currentStepIndex,
|
|
1995
|
+
totalSteps: overview.totalSteps,
|
|
1996
|
+
progressPercentage: overview.progressPercentage,
|
|
1997
|
+
workflowVariablesTable,
|
|
1998
|
+
};
|
|
1999
|
+
const entityName = 'WorkflowManagement.WorkflowInstance';
|
|
2000
|
+
// Check if instance already exists
|
|
2001
|
+
try {
|
|
2002
|
+
const existing = await this.entityStorageService.getOne(entityName, entityModel.id);
|
|
2003
|
+
if (existing) {
|
|
2004
|
+
// Already exists - don't insert again, will be updated later
|
|
2005
|
+
console.log('[AXCWorkflowExecutionService] ℹ️ Instance already exists in entity service, will be updated later', {
|
|
2006
|
+
instanceId: instance.id,
|
|
2007
|
+
});
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
catch (getError) {
|
|
2012
|
+
// Instance doesn't exist - proceed with insert
|
|
2013
|
+
}
|
|
2014
|
+
// Insert new instance
|
|
2015
|
+
await this.entityStorageService.insertOne(entityName, entityModel);
|
|
2016
|
+
console.log('[AXCWorkflowExecutionService] ✅ Inserted new instance in entity service', {
|
|
2017
|
+
instanceId: instance.id,
|
|
2018
|
+
status: instance.status,
|
|
2019
|
+
subStatus: instance.subStatus,
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
catch (error) {
|
|
2023
|
+
console.warn('[AXCWorkflowExecutionService] Failed to save instance to entity service:', error);
|
|
2024
|
+
// Don't throw - IndexedDB is primary storage
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Update workflow instance in entity service.
|
|
2029
|
+
* Uses upsert pattern: try update first, if fails then insert.
|
|
2030
|
+
*/
|
|
2031
|
+
async updateInstanceInEntityService(instance) {
|
|
2032
|
+
try {
|
|
2033
|
+
// Get active bookmarks for this instance to include in entity model
|
|
2034
|
+
const activeBookmarks = await this.getActiveBookmarks(instance.id);
|
|
2035
|
+
// Add bookmarks to instance for entity storage access
|
|
2036
|
+
const instanceWithBookmarks = {
|
|
2037
|
+
...instance,
|
|
2038
|
+
bookmarks: activeBookmarks.map((b) => ({
|
|
2039
|
+
id: b.id,
|
|
2040
|
+
activityId: b.activityId,
|
|
2041
|
+
activityInstanceId: b.activityId, // For compatibility
|
|
2042
|
+
type: b.type,
|
|
2043
|
+
payload: b.payloadJson ? JSON.parse(b.payloadJson) : null,
|
|
2044
|
+
payloadJson: b.payloadJson,
|
|
2045
|
+
consumed: b.isConsumed,
|
|
2046
|
+
isConsumed: b.isConsumed,
|
|
2047
|
+
createdAt: b.createdAt,
|
|
2048
|
+
})),
|
|
2049
|
+
};
|
|
2050
|
+
const definition = await this.getDefinition(instance.definitionId);
|
|
2051
|
+
const overview = this.computeOverviewDisplayFields(instance, definition);
|
|
2052
|
+
const runtimeDetails = this.computeRuntimeDetails(instance, definition, activeBookmarks);
|
|
2053
|
+
const workflowVariablesTable = this.computeWorkflowVariablesTable(instance, definition);
|
|
2054
|
+
const assignUserFields = await this.resolveAssignUserLookupFields(this.resolvePrimaryAssignUserId(instance, activeBookmarks));
|
|
2055
|
+
const entityModel = {
|
|
2056
|
+
id: instance.id,
|
|
2057
|
+
definitionId: instance.definitionId,
|
|
2058
|
+
definitionVersionId: instance.definitionVersionId,
|
|
2059
|
+
version: instance.version,
|
|
2060
|
+
status: instance.status,
|
|
2061
|
+
subStatus: instance.subStatus,
|
|
2062
|
+
correlationId: instance.correlationId || undefined,
|
|
2063
|
+
name: instance.name || undefined,
|
|
2064
|
+
incidentCount: instance.incidentCount,
|
|
2065
|
+
parentWorkflowInstanceId: instance.parentWorkflowInstanceId || undefined,
|
|
2066
|
+
createdAt: instance.createdAt,
|
|
2067
|
+
updatedAt: instance.updatedAt,
|
|
2068
|
+
finishedAt: instance.finishedAt || undefined,
|
|
2069
|
+
workflowInstance: instanceWithBookmarks, // Include bookmarks
|
|
2070
|
+
workflowTitle: overview.workflowTitle,
|
|
2071
|
+
statusDisplay: overview.statusDisplay,
|
|
2072
|
+
startedAt: instance.createdAt,
|
|
2073
|
+
...assignUserFields,
|
|
2074
|
+
currentActivityTitle: runtimeDetails.currentActivityTitle,
|
|
2075
|
+
nextActivityTitle: runtimeDetails.nextActivityTitle,
|
|
2076
|
+
currentStepIndex: overview.currentStepIndex,
|
|
2077
|
+
totalSteps: overview.totalSteps,
|
|
2078
|
+
progressPercentage: overview.progressPercentage,
|
|
2079
|
+
workflowVariablesTable,
|
|
2080
|
+
};
|
|
2081
|
+
const entityName = 'WorkflowManagement.WorkflowInstance';
|
|
2082
|
+
// Try update first (upsert pattern)
|
|
2083
|
+
try {
|
|
2084
|
+
await this.entityStorageService.updateOne(entityName, entityModel.id, entityModel);
|
|
2085
|
+
console.log('[AXCWorkflowExecutionService] ✅ Updated instance in entity service', {
|
|
2086
|
+
instanceId: instance.id,
|
|
2087
|
+
status: instance.status,
|
|
2088
|
+
subStatus: instance.subStatus,
|
|
2089
|
+
});
|
|
2090
|
+
return; // Successfully updated, exit
|
|
2091
|
+
}
|
|
2092
|
+
catch (updateError) {
|
|
2093
|
+
// Update failed - instance doesn't exist, try insert
|
|
2094
|
+
console.log('[AXCWorkflowExecutionService] ℹ️ Update failed, trying insert', {
|
|
2095
|
+
instanceId: instance.id,
|
|
2096
|
+
error: updateError.message,
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
// Try insert
|
|
2100
|
+
try {
|
|
2101
|
+
await this.entityStorageService.insertOne(entityName, entityModel);
|
|
2102
|
+
console.log('[AXCWorkflowExecutionService] ✅ Inserted instance in entity service', {
|
|
2103
|
+
instanceId: instance.id,
|
|
2104
|
+
status: instance.status,
|
|
2105
|
+
subStatus: instance.subStatus,
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
catch (insertError) {
|
|
2109
|
+
// Insert might fail if instance was created between update and insert
|
|
2110
|
+
// Try update again as fallback
|
|
2111
|
+
try {
|
|
2112
|
+
await this.entityStorageService.updateOne(entityName, entityModel.id, entityModel);
|
|
2113
|
+
console.log('[AXCWorkflowExecutionService] ✅ Updated instance in entity service (after insert failed)', {
|
|
2114
|
+
instanceId: instance.id,
|
|
2115
|
+
status: instance.status,
|
|
2116
|
+
subStatus: instance.subStatus,
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
catch (finalError) {
|
|
2120
|
+
console.warn('[AXCWorkflowExecutionService] Failed to upsert instance in entity service:', finalError);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
catch (error) {
|
|
2125
|
+
console.warn('[AXCWorkflowExecutionService] Failed to update instance in entity service:', error);
|
|
2126
|
+
// Don't throw - IndexedDB is primary storage
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
//#endregion
|
|
2130
|
+
//#region ---- Backend Activity Executors ----
|
|
2131
|
+
/**
|
|
2132
|
+
* Evaluates activity inputs against current workflow scope (same as backend execution).
|
|
2133
|
+
*/
|
|
2134
|
+
async evaluateActivityInputs(activity, state, lastOutput) {
|
|
2135
|
+
const scope = this.expressionScopeService.buildScopeFromState(state, this.ensureOutputsForScope(lastOutput));
|
|
2136
|
+
return (await this.expressionEvaluator.evaluate(activity.inputs || {}, scope));
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* After entity-create: link instance entity ref and persist {@link AXPRecordWorkflowInfo} on the row.
|
|
2140
|
+
*/
|
|
2141
|
+
async linkEntityRecordAfterCreate(instanceId, activity, state, lastOutput, entityId) {
|
|
2142
|
+
const properties = await this.evaluateActivityInputs(activity, state, lastOutput);
|
|
2143
|
+
const entityType = typeof properties['entity'] === 'string' ? properties['entity'].trim() : '';
|
|
2144
|
+
const recordId = entityId?.trim();
|
|
2145
|
+
if (!entityType || !recordId) {
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
const instance = await this.store.getInstance(instanceId);
|
|
2149
|
+
const definitionId = instance?.definitionId ?? instance?.workflowState?.definitionId ?? state.workflowId ?? null;
|
|
2150
|
+
const correlationId = instance?.correlationId ?? instance?.workflowState?.correlationId ?? null;
|
|
2151
|
+
await (0, entity_workflow_info_sync_1.syncEntityRecordWorkflowInfo)(this.entityStorageService, this.store, {
|
|
2152
|
+
entityType,
|
|
2153
|
+
entityId: recordId,
|
|
2154
|
+
instanceId,
|
|
2155
|
+
definitionId: definitionId != null ? String(definitionId) : null,
|
|
2156
|
+
correlationId: correlationId != null ? String(correlationId) : null,
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Execute backend activities locally via {@link WorkflowActivityRegistryService}.
|
|
2161
|
+
*
|
|
2162
|
+
* Uses WorkflowExpressionScopeService + WorkflowExpressionEvaluatorService for expression
|
|
2163
|
+
* evaluation (inputs, variables, outputs, context.eval) so backend activities get the
|
|
2164
|
+
* same expression handling as the platform ActivityExecutor.
|
|
2165
|
+
*/
|
|
2166
|
+
async executeBackendActivityLocally(activity, state, lastOutput) {
|
|
2167
|
+
const rawInputs = activity.inputs || {};
|
|
2168
|
+
// Evaluate expressions using same scope as platform (inputs, variables, outputs = lastActivityOutput)
|
|
2169
|
+
const scope = this.expressionScopeService.buildScopeFromState(state, this.ensureOutputsForScope(lastOutput));
|
|
2170
|
+
const properties = (await this.expressionEvaluator.evaluate(rawInputs, scope));
|
|
2171
|
+
const activityType = activity.name;
|
|
2172
|
+
return this.activityRegistry.execute(activityType, properties, {
|
|
2173
|
+
entityStorage: this.entityStorageService,
|
|
2174
|
+
sessionService: this.sessionService,
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Build variable updates from activity output using activity's outputToVariables (BPMN-style: activity → variable).
|
|
2179
|
+
* For each entry in activity.outputToVariables, sets variables[variableName] = output[outputPath].
|
|
2180
|
+
*/
|
|
2181
|
+
getVariableUpdatesFromOutput(workflow, activityId, output) {
|
|
2182
|
+
const activity = this.findActivityInGraph(workflow.graph, activityId);
|
|
2183
|
+
const outputToVariables = activity?.outputToVariables;
|
|
2184
|
+
if (!outputToVariables || typeof outputToVariables !== 'object')
|
|
2185
|
+
return {};
|
|
2186
|
+
const updates = {};
|
|
2187
|
+
for (const [variableName, outputPath] of Object.entries(outputToVariables)) {
|
|
2188
|
+
const path = String(outputPath);
|
|
2189
|
+
const value = path.split('.').reduce((obj, key) => obj?.[key], output);
|
|
2190
|
+
if (value !== undefined)
|
|
2191
|
+
updates[variableName] = value;
|
|
2192
|
+
}
|
|
2193
|
+
return updates;
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Resolves human-task bookmark title from evaluated inputs, then context, then activity definition.
|
|
2197
|
+
*/
|
|
2198
|
+
resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity) {
|
|
2199
|
+
const rawTitle = inputs['title'];
|
|
2200
|
+
if (typeof rawTitle === 'string' && rawTitle.trim().length > 0) {
|
|
2201
|
+
return rawTitle.trim();
|
|
2202
|
+
}
|
|
2203
|
+
const ctx = inputs['context'];
|
|
2204
|
+
if (ctx != null && typeof ctx === 'object') {
|
|
2205
|
+
const composed = this.composeHumanTaskTitleFromContext(ctx);
|
|
2206
|
+
if (composed) {
|
|
2207
|
+
return composed;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
const activityTitle = activityDef?.title;
|
|
2211
|
+
const resolvedActivityTitle = activityTitle != null ? (0, multilanguage_util_1.resolveMultiLanguageString)(activityTitle) : '';
|
|
2212
|
+
return resolvedActivityTitle || currentActivity.name || 'Workflow Task';
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Builds title from `context._taskTitleParts` when activity title input did not resolve.
|
|
2216
|
+
*/
|
|
2217
|
+
composeHumanTaskTitleFromContext(context) {
|
|
2218
|
+
const parts = context[AXP_WORKFLOW_TASK_CONTEXT_TITLE_PARTS_KEY];
|
|
2219
|
+
if (!Array.isArray(parts)) {
|
|
2220
|
+
return '';
|
|
2221
|
+
}
|
|
2222
|
+
return parts
|
|
2223
|
+
.map((part) => this.humanTaskContextLabel(part))
|
|
2224
|
+
.filter((segment) => segment.length > 0)
|
|
2225
|
+
.join(' — ');
|
|
2226
|
+
}
|
|
2227
|
+
humanTaskContextLabel(value) {
|
|
2228
|
+
if (value == null) {
|
|
2229
|
+
return '';
|
|
2230
|
+
}
|
|
2231
|
+
if (typeof value === 'string') {
|
|
2232
|
+
return value.trim();
|
|
2233
|
+
}
|
|
2234
|
+
if (typeof value === 'object') {
|
|
2235
|
+
return (0, multilanguage_util_1.resolveMultiLanguageString)(value)?.trim() ?? '';
|
|
2236
|
+
}
|
|
2237
|
+
return String(value).trim();
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Normalize user/role id field to array for bookmark payload (task board expects array).
|
|
2241
|
+
* Expression may evaluate to single string (e.g. {{ variables.managerId }}) or already array.
|
|
2242
|
+
*/
|
|
2243
|
+
normalizeUserIds(value) {
|
|
2244
|
+
if (value == null)
|
|
2245
|
+
return null;
|
|
2246
|
+
const raw = Array.isArray(value) ? value : [value];
|
|
2247
|
+
const trimmed = raw.map((id) => String(id).trim()).filter((id) => id.length > 0);
|
|
2248
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Diagnostic logging for human-task bookmark assignment (task board assignee).
|
|
2252
|
+
* Filter the browser console by `[WFM-LifecycleTaskTrace]`.
|
|
2253
|
+
*/
|
|
2254
|
+
logHumanTaskBookmarkAssignmentTrace(info) {
|
|
2255
|
+
const v = info.variables ?? {};
|
|
2256
|
+
console.log('[WFM-LifecycleTaskTrace] human-task bookmark', {
|
|
2257
|
+
phase: info.phase,
|
|
2258
|
+
instanceId: info.instanceId,
|
|
2259
|
+
workflowDefinitionName: info.workflowDefinitionName,
|
|
2260
|
+
activityId: info.activityId,
|
|
2261
|
+
activityType: info.activityType,
|
|
2262
|
+
sessionUserId: this.getSessionUser()?.id ?? null,
|
|
2263
|
+
rawAssignedUserIds: info.rawAssignedUserIds,
|
|
2264
|
+
rawAssignedUserIdsType: typeof info.rawAssignedUserIds,
|
|
2265
|
+
normalizedAssignedUserIds: info.normalizedAssignedUserIds,
|
|
2266
|
+
lifecycleVariableSlice: {
|
|
2267
|
+
employeeId: v['employeeId'],
|
|
2268
|
+
assigneeUserId: v['assigneeUserId'],
|
|
2269
|
+
managerUserId: v['managerUserId'],
|
|
2270
|
+
initiatorUserId: v['initiatorUserId'],
|
|
2271
|
+
targetEmployeeStatusId: v['targetEmployeeStatusId'],
|
|
2272
|
+
questionnaireId: v['questionnaireId'],
|
|
2273
|
+
},
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
//#endregion
|
|
2277
|
+
//#region ---- Bookmark Management ----
|
|
2278
|
+
/**
|
|
2279
|
+
* Create a bookmark for a workflow instance.
|
|
2280
|
+
* Aligns with ERD: BOOKMARK table structure.
|
|
2281
|
+
*
|
|
2282
|
+
* @param instanceId - Workflow instance ID
|
|
2283
|
+
* @param activityId - Activity ID where bookmark is created
|
|
2284
|
+
* @param type - Bookmark type (e.g., 'user-task', 'event', 'timer')
|
|
2285
|
+
* @param payload - Bookmark payload data
|
|
2286
|
+
* @returns Bookmark ID
|
|
2287
|
+
*/
|
|
2288
|
+
async createBookmark(instanceId, activityId, type, payload) {
|
|
2289
|
+
const bookmarkId = (0, uuid_util_1.generateUuid)();
|
|
2290
|
+
const hash = (0, uuid_util_1.generateUuid)(); // Simple hash for now
|
|
2291
|
+
const now = new Date().toISOString();
|
|
2292
|
+
const bookmark = {
|
|
2293
|
+
id: bookmarkId,
|
|
2294
|
+
instanceId,
|
|
2295
|
+
activityId,
|
|
2296
|
+
type,
|
|
2297
|
+
hash,
|
|
2298
|
+
payloadJson: JSON.stringify(payload),
|
|
2299
|
+
isConsumed: false,
|
|
2300
|
+
createdAt: now,
|
|
2301
|
+
};
|
|
2302
|
+
await this.store.addBookmark(bookmark);
|
|
2303
|
+
console.log(`[BackendWorkflowEngineService] 📌 Created bookmark: ${bookmarkId} for activity ${activityId}`);
|
|
2304
|
+
return bookmarkId;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Consume (mark as consumed) a bookmark for a workflow instance.
|
|
2308
|
+
*
|
|
2309
|
+
* @param instanceId - Workflow instance ID
|
|
2310
|
+
* @param activityId - Activity ID of the bookmark to consume
|
|
2311
|
+
*/
|
|
2312
|
+
async consumeBookmark(instanceId, activityId) {
|
|
2313
|
+
const bookmarks = await this.store.getBookmarksByInstanceAndActivity(instanceId, activityId, true);
|
|
2314
|
+
for (const bookmark of bookmarks) {
|
|
2315
|
+
bookmark.isConsumed = true;
|
|
2316
|
+
await this.store.updateBookmark(bookmark);
|
|
2317
|
+
console.log(`[BackendWorkflowEngineService] ✅ Consumed bookmark: ${bookmark.id} for activity ${activityId}`);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Get active bookmarks for a workflow instance.
|
|
2322
|
+
*
|
|
2323
|
+
* @param instanceId - Workflow instance ID
|
|
2324
|
+
* @returns Array of active (non-consumed) bookmarks
|
|
2325
|
+
*/
|
|
2326
|
+
async getActiveBookmarks(instanceId) {
|
|
2327
|
+
return this.store.getActiveBookmarks(instanceId);
|
|
2328
|
+
}
|
|
2329
|
+
//#endregion
|
|
2330
|
+
//#region ---- Instance Management ----
|
|
2331
|
+
/**
|
|
2332
|
+
* Convert workflow instance to instance state.
|
|
2333
|
+
*
|
|
2334
|
+
* Aligns with ERD structure:
|
|
2335
|
+
* - WORKFLOW_INSTANCE: header fields (status, subStatus, timestamps)
|
|
2336
|
+
* - WORKFLOW_INSTANCE_STATE: heavy state (input, output, properties, stateJson)
|
|
2337
|
+
* - BOOKMARK: separate table (used to derive currentStepId)
|
|
2338
|
+
*/
|
|
2339
|
+
async instanceToInstanceState(instance) {
|
|
2340
|
+
const state = instance.workflowState;
|
|
2341
|
+
// Derive currentStepId from active bookmarks (preferred) or execution contexts
|
|
2342
|
+
let currentStepId;
|
|
2343
|
+
// Try to get from active bookmarks first
|
|
2344
|
+
const activeBookmarks = await this.getActiveBookmarks(instance.id);
|
|
2345
|
+
if (activeBookmarks.length > 0) {
|
|
2346
|
+
// Use the most recent bookmark's activityId
|
|
2347
|
+
const latestBookmark = activeBookmarks.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0];
|
|
2348
|
+
currentStepId = latestBookmark.activityId;
|
|
2349
|
+
}
|
|
2350
|
+
else {
|
|
2351
|
+
// Fallback to execution contexts
|
|
2352
|
+
const currentContext = state.activityExecutionContexts.find((ctx) => ctx.status === 'Running' && !ctx.completedAt);
|
|
2353
|
+
currentStepId = currentContext?.scheduledActivityNodeId;
|
|
2354
|
+
}
|
|
2355
|
+
// Map status from instance header fields (ERD: WORKFLOW_INSTANCE.status/subStatus)
|
|
2356
|
+
let status = 'running';
|
|
2357
|
+
if (instance.status === 'Finished') {
|
|
2358
|
+
if (instance.subStatus === 'Faulted') {
|
|
2359
|
+
status = 'error';
|
|
2360
|
+
}
|
|
2361
|
+
else {
|
|
2362
|
+
status = 'completed';
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
else if (instance.subStatus === 'Suspended') {
|
|
2366
|
+
status = 'suspended';
|
|
2367
|
+
}
|
|
2368
|
+
// Extract state from workflowState (ERD: WORKFLOW_INSTANCE_STATE). Only last activity output is loaded.
|
|
2369
|
+
const properties = (state.properties || {});
|
|
2370
|
+
const { __lastActivityOutput, ...variables } = properties;
|
|
2371
|
+
const lastActivityOutput = __lastActivityOutput;
|
|
2372
|
+
const input = state.input || {};
|
|
2373
|
+
const output = state.output || {};
|
|
2374
|
+
// Normalize dates (instance.updatedAt is always string in AXPWorkflowInstance)
|
|
2375
|
+
const lastUpdated = instance.updatedAt ? new Date(instance.updatedAt) : new Date();
|
|
2376
|
+
return {
|
|
2377
|
+
instanceId: instance.id,
|
|
2378
|
+
workflowId: instance.definitionId,
|
|
2379
|
+
status,
|
|
2380
|
+
currentStepId,
|
|
2381
|
+
variables,
|
|
2382
|
+
lastActivityOutput,
|
|
2383
|
+
input,
|
|
2384
|
+
output,
|
|
2385
|
+
lastUpdated,
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Update instance state in instance and save to IndexedDB.
|
|
2390
|
+
*
|
|
2391
|
+
* Aligns with ERD structure:
|
|
2392
|
+
* - WORKFLOW_INSTANCE: header fields (status, subStatus, timestamps, correlationId)
|
|
2393
|
+
* - WORKFLOW_INSTANCE_STATE: heavy state (input, output, properties, stateJson)
|
|
2394
|
+
*
|
|
2395
|
+
* Handles status transitions correctly:
|
|
2396
|
+
* - running → suspended (when frontend activity encountered)
|
|
2397
|
+
* - suspended → running → completed (when workflow finishes)
|
|
2398
|
+
* - any → error (on errors)
|
|
2399
|
+
*/
|
|
2400
|
+
async updateInstanceFromState(instanceId, instanceState) {
|
|
2401
|
+
const instance = await this.getInstance(instanceId);
|
|
2402
|
+
if (!instance) {
|
|
2403
|
+
throw new Error(`Workflow instance not found: ${instanceId}`);
|
|
2404
|
+
}
|
|
2405
|
+
// Update instance from state
|
|
2406
|
+
const now = new Date().toISOString();
|
|
2407
|
+
// Map state status to instance status (ERD: WORKFLOW_INSTANCE.status/subStatus)
|
|
2408
|
+
let status = 'Running';
|
|
2409
|
+
let subStatus = 'Executing';
|
|
2410
|
+
// Handle status transitions
|
|
2411
|
+
if (instanceState.status === 'completed') {
|
|
2412
|
+
console.log('[BackendWorkflowEngineService] ✅ Marking workflow as Finished');
|
|
2413
|
+
status = 'Finished';
|
|
2414
|
+
subStatus = 'Finished';
|
|
2415
|
+
instance.finishedAt = now;
|
|
2416
|
+
}
|
|
2417
|
+
else if (instanceState.status === 'error') {
|
|
2418
|
+
status = 'Finished';
|
|
2419
|
+
subStatus = 'Faulted';
|
|
2420
|
+
instance.finishedAt = now;
|
|
2421
|
+
}
|
|
2422
|
+
else if (instanceState.status === 'suspended') {
|
|
2423
|
+
subStatus = 'Suspended';
|
|
2424
|
+
}
|
|
2425
|
+
else if (instanceState.status === 'running') {
|
|
2426
|
+
subStatus = 'Executing';
|
|
2427
|
+
}
|
|
2428
|
+
// Update WORKFLOW_INSTANCE header fields (ERD)
|
|
2429
|
+
instance.status = status;
|
|
2430
|
+
instance.subStatus = subStatus;
|
|
2431
|
+
instance.updatedAt = now;
|
|
2432
|
+
// Update WORKFLOW_INSTANCE_STATE heavy state (ERD). Only last activity output is persisted; exposed as scope.outputs.
|
|
2433
|
+
instance.workflowState.status = status;
|
|
2434
|
+
instance.workflowState.subStatus = subStatus;
|
|
2435
|
+
instance.workflowState.input = instanceState.input || {};
|
|
2436
|
+
instance.workflowState.output = instanceState.output || {};
|
|
2437
|
+
instance.workflowState.properties = {
|
|
2438
|
+
...(instanceState.variables || {}),
|
|
2439
|
+
__lastActivityOutput: instanceState.lastActivityOutput ?? null,
|
|
2440
|
+
};
|
|
2441
|
+
instance.workflowState.updatedAt = now;
|
|
2442
|
+
if (instanceState.status === 'completed' || instanceState.status === 'error') {
|
|
2443
|
+
instance.workflowState.finishedAt = now;
|
|
2444
|
+
}
|
|
2445
|
+
// Update activity execution context if currentStepId changed
|
|
2446
|
+
if (instanceState.currentStepId) {
|
|
2447
|
+
// Mark previous contexts as completed
|
|
2448
|
+
instance.workflowState.activityExecutionContexts.forEach((ctx) => {
|
|
2449
|
+
if (ctx.status === 'Running' && ctx.scheduledActivityNodeId !== instanceState.currentStepId) {
|
|
2450
|
+
ctx.status = 'Completed';
|
|
2451
|
+
ctx.completedAt = now;
|
|
2452
|
+
ctx.isExecuting = false;
|
|
2453
|
+
}
|
|
2454
|
+
});
|
|
2455
|
+
// Find or create context for current step
|
|
2456
|
+
let existingContext = instance.workflowState.activityExecutionContexts.find((ctx) => ctx.scheduledActivityNodeId === instanceState.currentStepId && ctx.status === 'Running');
|
|
2457
|
+
if (!existingContext) {
|
|
2458
|
+
// Add new context
|
|
2459
|
+
existingContext = {
|
|
2460
|
+
id: `ctx-${instanceState.currentStepId}`,
|
|
2461
|
+
parentContextId: null,
|
|
2462
|
+
scheduledActivityNodeId: instanceState.currentStepId,
|
|
2463
|
+
ownerActivityNodeId: null,
|
|
2464
|
+
properties: {},
|
|
2465
|
+
activityState: null,
|
|
2466
|
+
dynamicVariables: [],
|
|
2467
|
+
status: 'Running',
|
|
2468
|
+
isExecuting: false,
|
|
2469
|
+
faultCount: 0,
|
|
2470
|
+
startedAt: now,
|
|
2471
|
+
completedAt: null,
|
|
2472
|
+
tag: null,
|
|
2473
|
+
};
|
|
2474
|
+
instance.workflowState.activityExecutionContexts.push(existingContext);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
else {
|
|
2478
|
+
// No current step - mark all contexts as completed
|
|
2479
|
+
instance.workflowState.activityExecutionContexts.forEach((ctx) => {
|
|
2480
|
+
if (ctx.status === 'Running') {
|
|
2481
|
+
ctx.status = 'Completed';
|
|
2482
|
+
ctx.completedAt = now;
|
|
2483
|
+
ctx.isExecuting = false;
|
|
2484
|
+
}
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
// Save to IndexedDB
|
|
2488
|
+
await this.updateInstance(instance);
|
|
2489
|
+
// 🎯 Also update in entity service
|
|
2490
|
+
await this.updateInstanceInEntityService(instance);
|
|
2491
|
+
return instance;
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Get workflow instance from IndexedDB.
|
|
2495
|
+
*/
|
|
2496
|
+
async getInstance(instanceId) {
|
|
2497
|
+
return this.store.getInstance(instanceId);
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Update workflow instance in store.
|
|
2501
|
+
*/
|
|
2502
|
+
async updateInstance(instance) {
|
|
2503
|
+
instance.updatedAt = new Date().toISOString();
|
|
2504
|
+
await this.store.updateInstance(instance);
|
|
2505
|
+
}
|
|
2506
|
+
/**
|
|
2507
|
+
* Complete a frontend activity and continue workflow execution.
|
|
2508
|
+
*
|
|
2509
|
+
* This method is called when a frontend activity (ui-activity) completes execution.
|
|
2510
|
+
* It applies the frontend output to workflow state, finds the next activity based on outcome,
|
|
2511
|
+
* and executes all backend activities until reaching the next frontend activity or completion.
|
|
2512
|
+
*
|
|
2513
|
+
* Flow:
|
|
2514
|
+
* 1. Apply frontend output to workflow state
|
|
2515
|
+
* 2. Find next activity based on outcome from connections
|
|
2516
|
+
* 3. Execute ALL backend activities until hitting frontend activity or completion
|
|
2517
|
+
* 4. Return either nextTask (with new secure token) or completed status
|
|
2518
|
+
*
|
|
2519
|
+
* @param request - Front activity completion request with instanceId, activityNode, output, and outcome
|
|
2520
|
+
* @returns Response with nextTask (if frontend activity) or completion status
|
|
2521
|
+
*/
|
|
2522
|
+
async frontActivtyComplete(request) {
|
|
2523
|
+
try {
|
|
2524
|
+
// Get current workflow instance state
|
|
2525
|
+
let state = await this.getState({ instanceId: request.instanceId });
|
|
2526
|
+
if (!state) {
|
|
2527
|
+
console.error('[BackendWorkflowEngineService] ❌ Workflow instance not found:', request.instanceId);
|
|
2528
|
+
throw new Error(`Workflow instance not found: ${request.instanceId}`);
|
|
2529
|
+
}
|
|
2530
|
+
// Check if workflow is suspended
|
|
2531
|
+
// If suspended, check if the activity being completed is a ui-activity (not human-task)
|
|
2532
|
+
// If it's a ui-activity, we can continue execution (workflow manager is handling it)
|
|
2533
|
+
// If it's a human-task, use resume() instead
|
|
2534
|
+
if (state.status === 'suspended') {
|
|
2535
|
+
// Find the activity being completed to check its taskType
|
|
2536
|
+
const workflow = await this.getDefinition(state.workflowId);
|
|
2537
|
+
if (!workflow) {
|
|
2538
|
+
console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
|
|
2539
|
+
throw new Error(`Workflow definition not found: ${state.workflowId}`);
|
|
2540
|
+
}
|
|
2541
|
+
const activity = this.findActivityInGraph(workflow.graph, request.activityNode);
|
|
2542
|
+
if (activity) {
|
|
2543
|
+
const activityType = activity.name;
|
|
2544
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
2545
|
+
const taskType = activityDef?.taskType || 'ui-activity';
|
|
2546
|
+
// If it's a ui-activity, allow continuation (workflow manager is handling it)
|
|
2547
|
+
if (taskType === 'ui-activity') {
|
|
2548
|
+
// Change status to running to allow continuation
|
|
2549
|
+
state = {
|
|
2550
|
+
...state,
|
|
2551
|
+
status: 'running',
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
else {
|
|
2555
|
+
// It's a human-task, must use resume()
|
|
2556
|
+
console.warn('[BackendWorkflowEngineService] ⚠️ Workflow is suspended. Use resume() method instead.');
|
|
2557
|
+
throw new Error('Workflow is suspended. Use resume() method for suspended workflows.');
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
else {
|
|
2561
|
+
// Activity not found, but allow continuation (might be edge case)
|
|
2562
|
+
state = {
|
|
2563
|
+
...state,
|
|
2564
|
+
status: 'running',
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
// Load workflow definition
|
|
2569
|
+
const workflow = await this.getDefinition(state.workflowId);
|
|
2570
|
+
if (!workflow) {
|
|
2571
|
+
console.error('[BackendWorkflowEngineService] ❌ Workflow definition not found:', state.workflowId);
|
|
2572
|
+
throw new Error(`Workflow definition not found: ${state.workflowId}`);
|
|
2573
|
+
}
|
|
2574
|
+
const connections = workflow.graph.connections || [];
|
|
2575
|
+
// Consume bookmark for the activity being completed so only the next step has an active bookmark
|
|
2576
|
+
await this.consumeBookmark(request.instanceId, request.activityNode);
|
|
2577
|
+
const output = (request.output || {});
|
|
2578
|
+
const completedActivity = this.findActivityInGraph(workflow.graph, request.activityNode);
|
|
2579
|
+
// Resolve allowed outcomes from workflow definition and normalize outcome
|
|
2580
|
+
const allowedOutcomes = await this.getActivityAllowedOutcomes(workflow, request.activityNode);
|
|
2581
|
+
const coercedOutcome = this.coerceCreateEntityFormOutcome(completedActivity, request.outcome ?? 'Done', output);
|
|
2582
|
+
const outcome = this.normalizeOutcome(coercedOutcome, allowedOutcomes, request.activityNode);
|
|
2583
|
+
// Apply frontend output: only last activity output is kept (overwritten each step)
|
|
2584
|
+
let lastActivityOutput = output;
|
|
2585
|
+
// Apply outputToVariables for the completed frontend activity (e.g. leaveRequestForm → leaveRequestId: 'id')
|
|
2586
|
+
const variableUpdatesFromOutput = this.getVariableUpdatesFromOutput(workflow, request.activityNode, output);
|
|
2587
|
+
const formResultPromotions = this.buildPersistedEntityFormVariablePromotions(output, completedActivity?.name, outcome);
|
|
2588
|
+
const sessionUserFrontComplete = this.getSessionUser();
|
|
2589
|
+
const frontCompleteAudit = {
|
|
2590
|
+
[`${request.activityNode}_outcome`]: outcome,
|
|
2591
|
+
[`${request.activityNode}_activityOutput`]: output,
|
|
2592
|
+
};
|
|
2593
|
+
if (sessionUserFrontComplete?.id) {
|
|
2594
|
+
frontCompleteAudit[`${request.activityNode}_completedByUserId`] = sessionUserFrontComplete.id;
|
|
2595
|
+
frontCompleteAudit[`${request.activityNode}_completedByLabel`] =
|
|
2596
|
+
sessionUserFrontComplete.title || sessionUserFrontComplete.name || sessionUserFrontComplete.id;
|
|
2597
|
+
}
|
|
2598
|
+
else {
|
|
2599
|
+
frontCompleteAudit[`${request.activityNode}_completedByLabel`] = 'System';
|
|
2600
|
+
}
|
|
2601
|
+
state = {
|
|
2602
|
+
...state,
|
|
2603
|
+
variables: {
|
|
2604
|
+
...state.variables,
|
|
2605
|
+
...output, // Merge frontend output into variables
|
|
2606
|
+
...formResultPromotions, // Promote capture-form fields (e.g. effectiveDate) onto variables
|
|
2607
|
+
...variableUpdatesFromOutput, // Map output paths to variable names (e.g. id → leaveRequestId)
|
|
2608
|
+
...frontCompleteAudit,
|
|
2609
|
+
},
|
|
2610
|
+
lastActivityOutput,
|
|
2611
|
+
lastUpdated: new Date(),
|
|
2612
|
+
};
|
|
2613
|
+
// Find next activity based on outcome from frontend (use normalized outcome)
|
|
2614
|
+
const nextConnection = this.findWorkflowConnection(connections, request.activityNode, outcome);
|
|
2615
|
+
if (!nextConnection) {
|
|
2616
|
+
// No next activity → workflow completed
|
|
2617
|
+
state = {
|
|
2618
|
+
...state,
|
|
2619
|
+
status: 'completed',
|
|
2620
|
+
currentStepId: undefined,
|
|
2621
|
+
output: state.variables,
|
|
2622
|
+
};
|
|
2623
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
2624
|
+
return {
|
|
2625
|
+
output: { outcome, userInput: request.output },
|
|
2626
|
+
outcomes: { [outcome]: true },
|
|
2627
|
+
nextTask: null,
|
|
2628
|
+
state,
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
// 🎯 KEY LOGIC: Execute all backend activities until we reach next frontend activity
|
|
2632
|
+
let currentActivity = this.findActivityInGraph(workflow.graph, nextConnection.target.activtyName);
|
|
2633
|
+
let executionCount = 0;
|
|
2634
|
+
const maxExecutions = 500;
|
|
2635
|
+
if (!currentActivity) {
|
|
2636
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConnection.target.activtyName}' not found in graph`);
|
|
2637
|
+
state = {
|
|
2638
|
+
...state,
|
|
2639
|
+
status: 'error',
|
|
2640
|
+
currentStepId: undefined,
|
|
2641
|
+
};
|
|
2642
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
2643
|
+
throw new Error(`Next activity '${nextConnection.target.activtyName}' not found in workflow graph`);
|
|
2644
|
+
}
|
|
2645
|
+
// Only lastActivityOutput is persisted; used as scope.outputs for expressions.
|
|
2646
|
+
while (currentActivity && executionCount < maxExecutions) {
|
|
2647
|
+
executionCount++;
|
|
2648
|
+
// Map activity name (registry key) to activity type
|
|
2649
|
+
const activityType = currentActivity.name;
|
|
2650
|
+
// Determine execution mode (async)
|
|
2651
|
+
const executionMode = await this.determineExecutionMode(activityType, currentActivity);
|
|
2652
|
+
// Get activity definition to retrieve taskType (taskType === 'human-task' → suspend for cartable)
|
|
2653
|
+
const activityDef = await this.activityDefinitionService.getActivityByName(activityType);
|
|
2654
|
+
let taskTypeComplete = currentActivity.metadata?.['taskType'] ?? activityDef?.taskType ?? 'ui-activity';
|
|
2655
|
+
// Defensive: human-task and cartable must suspend (same as run())
|
|
2656
|
+
if (this.isTaskBoardSuspendGraphActivity(activityType)) {
|
|
2657
|
+
if (activityDef?.taskType === 'human-task')
|
|
2658
|
+
taskTypeComplete = 'human-task';
|
|
2659
|
+
}
|
|
2660
|
+
const effectiveExecutionModeComplete = taskTypeComplete === 'human-task' && executionMode === 'backend'
|
|
2661
|
+
? 'frontend'
|
|
2662
|
+
: this.isTaskBoardSuspendGraphActivity(activityType) &&
|
|
2663
|
+
(activityDef?.taskType === 'human-task' || taskTypeComplete === 'human-task')
|
|
2664
|
+
? executionMode === 'backend'
|
|
2665
|
+
? 'frontend'
|
|
2666
|
+
: executionMode
|
|
2667
|
+
: executionMode;
|
|
2668
|
+
// If frontend activity → stop and return as nextTask
|
|
2669
|
+
if (effectiveExecutionModeComplete === 'frontend' || effectiveExecutionModeComplete === 'both') {
|
|
2670
|
+
const pendingTask = await this.createTaskFromActivity(currentActivity, state);
|
|
2671
|
+
const resolvedTaskTypeComplete = taskTypeComplete === 'human-task' || pendingTask.taskType === 'human-task'
|
|
2672
|
+
? 'human-task'
|
|
2673
|
+
: pendingTask.taskType;
|
|
2674
|
+
// Extract assignment and task metadata from activity inputs (for human-task activities)
|
|
2675
|
+
let bookmarkPayload = {
|
|
2676
|
+
activityType,
|
|
2677
|
+
activityName: currentActivity.name,
|
|
2678
|
+
taskToken: pendingTask.taskToken, // Store taskToken for resume
|
|
2679
|
+
taskType: resolvedTaskTypeComplete,
|
|
2680
|
+
};
|
|
2681
|
+
// For human-task activities, extract assignment and task metadata from inputs
|
|
2682
|
+
if (resolvedTaskTypeComplete === 'human-task' && pendingTask.input) {
|
|
2683
|
+
const inputs = pendingTask.input;
|
|
2684
|
+
// Assignment metadata (normalize to array; expression may return single id)
|
|
2685
|
+
bookmarkPayload['assignedUserIds'] = this.normalizeUserIds(inputs['assignedUserIds']);
|
|
2686
|
+
bookmarkPayload['candidateUserIds'] = this.normalizeUserIds(inputs['candidateUserIds']);
|
|
2687
|
+
bookmarkPayload['candidateRoleIds'] = this.normalizeUserIds(inputs['candidateRoleIds']);
|
|
2688
|
+
// Task metadata
|
|
2689
|
+
bookmarkPayload['title'] = this.resolveHumanTaskBookmarkTitle(inputs, activityDef, currentActivity);
|
|
2690
|
+
bookmarkPayload['description'] = inputs['description'] || null;
|
|
2691
|
+
bookmarkPayload['priority'] = inputs['priority'] || 'Normal';
|
|
2692
|
+
bookmarkPayload['dueDate'] = inputs['dueDate'] || null;
|
|
2693
|
+
// UI configuration
|
|
2694
|
+
bookmarkPayload['layout'] = inputs['layout'] || null;
|
|
2695
|
+
bookmarkPayload['actions'] = inputs['actions'] || null;
|
|
2696
|
+
bookmarkPayload['context'] = inputs['context'] || {};
|
|
2697
|
+
}
|
|
2698
|
+
if (activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
|
|
2699
|
+
bookmarkPayload['assignedUserIds'] = [];
|
|
2700
|
+
}
|
|
2701
|
+
if (resolvedTaskTypeComplete === 'human-task') {
|
|
2702
|
+
const htInputs = pendingTask.input;
|
|
2703
|
+
this.logHumanTaskBookmarkAssignmentTrace({
|
|
2704
|
+
phase: 'engine-frontActivityComplete',
|
|
2705
|
+
instanceId: request.instanceId,
|
|
2706
|
+
workflowDefinitionName: workflow.name,
|
|
2707
|
+
activityId: currentActivity.id,
|
|
2708
|
+
activityType,
|
|
2709
|
+
rawAssignedUserIds: htInputs?.['assignedUserIds'],
|
|
2710
|
+
normalizedAssignedUserIds: bookmarkPayload['assignedUserIds'],
|
|
2711
|
+
variables: state.variables,
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
// Create bookmark for frontend activity
|
|
2715
|
+
// Store all metadata in bookmark payload (replaces Work Item)
|
|
2716
|
+
const bookmarkId = await this.createBookmark(request.instanceId, currentActivity.id, 'user-task', bookmarkPayload);
|
|
2717
|
+
// Update state: task-board activities → suspended, else running (form/popup)
|
|
2718
|
+
const isHumanTaskComplete = this.isTaskBoardSuspendGraphActivity(activityType);
|
|
2719
|
+
state = {
|
|
2720
|
+
...state,
|
|
2721
|
+
lastActivityOutput,
|
|
2722
|
+
status: isHumanTaskComplete ? 'suspended' : 'running',
|
|
2723
|
+
currentStepId: currentActivity.id,
|
|
2724
|
+
};
|
|
2725
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
2726
|
+
return {
|
|
2727
|
+
output: { outcome, userInput: request.output },
|
|
2728
|
+
outcomes: { [outcome]: true },
|
|
2729
|
+
nextTask: pendingTask,
|
|
2730
|
+
state,
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2733
|
+
// Backend activity → execute it
|
|
2734
|
+
const executionResult = await this.executeBackendActivityLocally(currentActivity, state, lastActivityOutput);
|
|
2735
|
+
lastActivityOutput = executionResult.output;
|
|
2736
|
+
// set-variable activity writes variables directly in output; it has no outputToVariables
|
|
2737
|
+
const isSetVariableComplete = currentActivity.name === 'workflow-activity:set-variable';
|
|
2738
|
+
const variableUpdatesComplete = isSetVariableComplete
|
|
2739
|
+
? executionResult.output
|
|
2740
|
+
: this.getVariableUpdatesFromOutput(workflow, currentActivity.id, executionResult.output);
|
|
2741
|
+
const backendOutcome = Object.keys(executionResult.outcomes)[0] || 'Done';
|
|
2742
|
+
const sessionUserComplete = this.getSessionUser();
|
|
2743
|
+
const backendAuditComplete = {
|
|
2744
|
+
[`${currentActivity.id}_outcome`]: backendOutcome,
|
|
2745
|
+
[`${currentActivity.id}_activityOutput`]: executionResult.output,
|
|
2746
|
+
};
|
|
2747
|
+
if (sessionUserComplete?.id) {
|
|
2748
|
+
backendAuditComplete[`${currentActivity.id}_completedByUserId`] = sessionUserComplete.id;
|
|
2749
|
+
backendAuditComplete[`${currentActivity.id}_completedByLabel`] =
|
|
2750
|
+
sessionUserComplete.title || sessionUserComplete.name || sessionUserComplete.id;
|
|
2751
|
+
}
|
|
2752
|
+
else {
|
|
2753
|
+
backendAuditComplete[`${currentActivity.id}_completedByLabel`] = 'System';
|
|
2754
|
+
}
|
|
2755
|
+
state = {
|
|
2756
|
+
...state,
|
|
2757
|
+
variables: {
|
|
2758
|
+
...state.variables,
|
|
2759
|
+
...(typeof variableUpdatesComplete === 'object' && variableUpdatesComplete !== null
|
|
2760
|
+
? variableUpdatesComplete
|
|
2761
|
+
: {}),
|
|
2762
|
+
...backendAuditComplete,
|
|
2763
|
+
},
|
|
2764
|
+
lastActivityOutput,
|
|
2765
|
+
currentStepId: currentActivity.id,
|
|
2766
|
+
lastUpdated: new Date(),
|
|
2767
|
+
};
|
|
2768
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
2769
|
+
// Find next activity based on outcome (backend execution result)
|
|
2770
|
+
const nextConn = this.findWorkflowConnection(connections, currentActivity.id, backendOutcome);
|
|
2771
|
+
if (!nextConn) {
|
|
2772
|
+
// No next activity → workflow completed
|
|
2773
|
+
state = {
|
|
2774
|
+
...state,
|
|
2775
|
+
status: 'completed',
|
|
2776
|
+
currentStepId: undefined,
|
|
2777
|
+
output: state.variables,
|
|
2778
|
+
lastActivityOutput,
|
|
2779
|
+
};
|
|
2780
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
2781
|
+
return {
|
|
2782
|
+
output: state.variables,
|
|
2783
|
+
outcomes: { Done: true },
|
|
2784
|
+
nextTask: null,
|
|
2785
|
+
state,
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
// Find next activity
|
|
2789
|
+
const nextActivity = this.findActivityInGraph(workflow.graph, nextConn.target.activtyName);
|
|
2790
|
+
if (!nextActivity) {
|
|
2791
|
+
console.error(`[BackendWorkflowEngineService] ❌ Next activity '${nextConn.target.activtyName}' not found in graph`);
|
|
2792
|
+
state = {
|
|
2793
|
+
...state,
|
|
2794
|
+
status: 'error',
|
|
2795
|
+
currentStepId: undefined,
|
|
2796
|
+
};
|
|
2797
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
2798
|
+
throw new Error(`Next activity '${nextConn.target.activtyName}' not found in workflow graph`);
|
|
2799
|
+
}
|
|
2800
|
+
currentActivity = nextActivity;
|
|
2801
|
+
}
|
|
2802
|
+
// If we exit loop due to max executions, mark as error
|
|
2803
|
+
if (executionCount >= maxExecutions) {
|
|
2804
|
+
console.error(`[BackendWorkflowEngineService] ❌ Maximum execution count (${maxExecutions}) reached - possible infinite loop`);
|
|
2805
|
+
state = {
|
|
2806
|
+
...state,
|
|
2807
|
+
status: 'error',
|
|
2808
|
+
currentStepId: undefined,
|
|
2809
|
+
};
|
|
2810
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
2811
|
+
throw new Error(`Workflow execution exceeded maximum steps (${maxExecutions}) - possible infinite loop`);
|
|
2812
|
+
}
|
|
2813
|
+
// If we exit loop, workflow is complete
|
|
2814
|
+
state = {
|
|
2815
|
+
...state,
|
|
2816
|
+
status: 'completed',
|
|
2817
|
+
currentStepId: undefined,
|
|
2818
|
+
output: state.variables,
|
|
2819
|
+
lastActivityOutput,
|
|
2820
|
+
};
|
|
2821
|
+
await this.updateInstanceFromState(request.instanceId, state);
|
|
2822
|
+
return {
|
|
2823
|
+
output: state.variables,
|
|
2824
|
+
outcomes: { Done: true },
|
|
2825
|
+
nextTask: null,
|
|
2826
|
+
state,
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
catch (error) {
|
|
2830
|
+
console.error('[BackendWorkflowEngineService] ❌ Error completing front activity:', error);
|
|
2831
|
+
throw error; // Re-throw to let caller handle
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
//#endregion
|
|
2835
|
+
//#region ---- Stale suspension cleanup (app startup) ----
|
|
2836
|
+
/**
|
|
2837
|
+
* Cleans up stale UI activities (form/popup) when user refreshed without submitting.
|
|
2838
|
+
* - Suspended instances with non-human-task bookmark → resume with outcome 'cancel'.
|
|
2839
|
+
* - Running instances with non-human-task bookmark (e.g. rejectionReasonDialog) → frontActivtyComplete with outcome 'cancel'.
|
|
2840
|
+
* So e.g. leave request: manager clicks Reject, dialog opens, user refreshes → dialog is "cancelled" and workflow goes back to managerConfirm (task board).
|
|
2841
|
+
*/
|
|
2842
|
+
async resumeStaleNonHumanTaskSuspensions() {
|
|
2843
|
+
// 1) Suspended instances: non-human-task bookmark → resume with cancel
|
|
2844
|
+
const suspended = await this.store.getInstancesBySubStatus('Suspended');
|
|
2845
|
+
for (const instance of suspended) {
|
|
2846
|
+
try {
|
|
2847
|
+
const bookmarks = await this.getActiveBookmarks(instance.id);
|
|
2848
|
+
for (const b of bookmarks) {
|
|
2849
|
+
const payload = this.parseBookmarkPayload(b);
|
|
2850
|
+
if (payload?.activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask ||
|
|
2851
|
+
payload?.activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
|
|
2852
|
+
continue;
|
|
2853
|
+
}
|
|
2854
|
+
const taskToken = payload?.taskToken;
|
|
2855
|
+
if (!taskToken)
|
|
2856
|
+
continue;
|
|
2857
|
+
await this.resume({
|
|
2858
|
+
instanceId: instance.id,
|
|
2859
|
+
stepId: b.activityId,
|
|
2860
|
+
outcome: 'cancel',
|
|
2861
|
+
taskToken,
|
|
2862
|
+
userInput: {},
|
|
2863
|
+
});
|
|
2864
|
+
console.log(`[BackendWorkflowEngineService] 🧹 Resumed stale non-human-task suspension: ${instance.id} activity ${b.activityId} with outcome cancel`);
|
|
2865
|
+
break;
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
catch (err) {
|
|
2869
|
+
console.warn(`[BackendWorkflowEngineService] ⚠️ Failed to resume stale suspension for instance ${instance.id}:`, err);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
// 2) Running instances: non-human-task bookmark (e.g. show-layout-popup) → complete with cancel so flow returns to previous step (e.g. task board)
|
|
2873
|
+
const running = await this.store.getInstancesBySubStatus('Executing');
|
|
2874
|
+
for (const instance of running) {
|
|
2875
|
+
try {
|
|
2876
|
+
const bookmarks = await this.getActiveBookmarks(instance.id);
|
|
2877
|
+
for (const b of bookmarks) {
|
|
2878
|
+
const payload = this.parseBookmarkPayload(b);
|
|
2879
|
+
if (payload?.activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.humanTask ||
|
|
2880
|
+
payload?.activityType === workflow_management_const_1.WorkflowManagementRootConfig.activities.cartable) {
|
|
2881
|
+
continue;
|
|
2882
|
+
}
|
|
2883
|
+
await this.frontActivtyComplete({
|
|
2884
|
+
instanceId: instance.id,
|
|
2885
|
+
activityNode: b.activityId,
|
|
2886
|
+
output: {},
|
|
2887
|
+
outcome: 'cancel',
|
|
2888
|
+
});
|
|
2889
|
+
console.log(`[BackendWorkflowEngineService] 🧹 Completed stale non-human-task (running): ${instance.id} activity ${b.activityId} with outcome cancel`);
|
|
2890
|
+
break;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
catch (err) {
|
|
2894
|
+
console.warn(`[BackendWorkflowEngineService] ⚠️ Failed to complete stale running activity for instance ${instance.id}:`, err);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
parseBookmarkPayload(b) {
|
|
2899
|
+
try {
|
|
2900
|
+
return b.payloadJson ? JSON.parse(b.payloadJson) : null;
|
|
2901
|
+
}
|
|
2902
|
+
catch {
|
|
2903
|
+
return null;
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
};
|
|
2907
|
+
exports.BackendWorkflowEngineService = BackendWorkflowEngineService;
|
|
2908
|
+
/**
|
|
2909
|
+
* Default outcome used when the requested outcome is not in the allowed list (standard scenario).
|
|
2910
|
+
*/
|
|
2911
|
+
BackendWorkflowEngineService.DEFAULT_UNMATCHED_OUTCOME = 'Done';
|
|
2912
|
+
exports.BackendWorkflowEngineService = BackendWorkflowEngineService = BackendWorkflowEngineService_1 = tslib_1.__decorate([
|
|
2913
|
+
(0, common_1.Injectable)(),
|
|
2914
|
+
tslib_1.__metadata("design:paramtypes", [workflow_execution_store_service_1.WorkflowExecutionStoreService,
|
|
2915
|
+
core_1.EntityStorageMiddlewareService,
|
|
2916
|
+
workflow_activity_definition_service_1.WorkflowActivityDefinitionService,
|
|
2917
|
+
workflow_expression_scope_service_1.WorkflowExpressionScopeService,
|
|
2918
|
+
workflow_expression_evaluator_service_1.WorkflowExpressionEvaluatorService,
|
|
2919
|
+
workflow_request_session_adapter_1.WorkflowRequestSessionAdapter,
|
|
2920
|
+
workflow_activity_registry_1.WorkflowActivityRegistryService])
|
|
2921
|
+
], BackendWorkflowEngineService);
|
|
2922
|
+
//# sourceMappingURL=workflow-engine.service.js.map
|