@acorex/platform 21.0.0-next.5 → 21.0.0-next.8
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/auth/index.d.ts +228 -3
- package/fesm2022/acorex-platform-auth.mjs +162 -2
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/acorex-platform-common.mjs +1 -1
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +11 -2
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +7 -7
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +79 -34
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +108 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +224 -89
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs → acorex-platform-themes-default-entity-master-list-view.component-D3qZa5fM.mjs} +4 -4
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-D3qZa5fM.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +2 -2
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-column.component-DTnfRy5f.mjs → acorex-platform-themes-shared-theme-color-chooser-column.component-Dz0cylyQ.mjs} +8 -8
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-Dz0cylyQ.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared.mjs +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +1084 -456
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/layout/builder/index.d.ts +6 -0
- package/layout/components/index.d.ts +4 -3
- package/layout/entity/index.d.ts +9 -0
- package/layout/widget-core/index.d.ts +42 -1
- package/layout/widgets/index.d.ts +12 -7
- package/package.json +9 -9
- package/workflow/index.d.ts +798 -939
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-DTnfRy5f.mjs.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, Optional, Inject, NgModule
|
|
2
|
+
import { Injectable, inject, InjectionToken, Optional, Inject, NgModule } from '@angular/core';
|
|
3
3
|
import { Subject, filter } from 'rxjs';
|
|
4
4
|
import { cloneDeep, get, set } from 'lodash-es';
|
|
5
|
-
import { setSmart } from '@acorex/platform/core';
|
|
5
|
+
import { setSmart, AXPDataGenerator } from '@acorex/platform/core';
|
|
6
6
|
import { AXPCommandService } from '@acorex/platform/runtime';
|
|
7
7
|
|
|
8
8
|
class AXPWorkflowError extends Error {
|
|
@@ -411,263 +411,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
411
411
|
}]
|
|
412
412
|
}] });
|
|
413
413
|
|
|
414
|
-
class AXPWorkflowModule {
|
|
415
|
-
static forRoot(config) {
|
|
416
|
-
return {
|
|
417
|
-
ngModule: AXPWorkflowModule,
|
|
418
|
-
providers: [
|
|
419
|
-
{
|
|
420
|
-
provide: 'AXPWorkflowModuleFactory',
|
|
421
|
-
useFactory: (registry) => () => {
|
|
422
|
-
registry.registerAction('start-workflow', AXPStartWorkflowAction);
|
|
423
|
-
registry.registerAction('decide', AXPWorkflowDecideAction);
|
|
424
|
-
//
|
|
425
|
-
if (config?.functions) {
|
|
426
|
-
for (const [key, type] of Object.entries(config.functions)) {
|
|
427
|
-
registry.registerFunction(key, type);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
//
|
|
431
|
-
if (config?.actions) {
|
|
432
|
-
for (const [key, type] of Object.entries(config.actions)) {
|
|
433
|
-
registry.registerAction(key, type);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
//
|
|
437
|
-
if (config?.workflows) {
|
|
438
|
-
for (const [key, type] of Object.entries(config.workflows)) {
|
|
439
|
-
registry.registerWorkflow(key, type);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
},
|
|
443
|
-
deps: [AXPWorkflowRegistryService],
|
|
444
|
-
multi: true,
|
|
445
|
-
},
|
|
446
|
-
...Object.values(config?.actions ?? { AXPStartWorkflowAction }),
|
|
447
|
-
...Object.values(config?.functions ?? {}),
|
|
448
|
-
],
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
static forChild(config) {
|
|
452
|
-
return {
|
|
453
|
-
ngModule: AXPWorkflowModule,
|
|
454
|
-
providers: [
|
|
455
|
-
// Built-in activities are already registered in forRoot via @NgModule providers
|
|
456
|
-
// No need to register again in forChild
|
|
457
|
-
{
|
|
458
|
-
provide: 'AXPWorkflowModuleFactory',
|
|
459
|
-
useFactory: (registry) => () => {
|
|
460
|
-
registry.registerAction('start-workflow', AXPStartWorkflowAction);
|
|
461
|
-
registry.registerAction('decide', AXPWorkflowDecideAction);
|
|
462
|
-
//
|
|
463
|
-
if (config?.functions) {
|
|
464
|
-
for (const [key, type] of Object.entries(config.functions)) {
|
|
465
|
-
registry.registerFunction(key, type);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
//
|
|
469
|
-
if (config?.actions) {
|
|
470
|
-
for (const [key, type] of Object.entries(config.actions)) {
|
|
471
|
-
registry.registerAction(key, type);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
//
|
|
475
|
-
if (config?.workflows) {
|
|
476
|
-
for (const [key, type] of Object.entries(config.workflows)) {
|
|
477
|
-
registry.registerWorkflow(key, type);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
},
|
|
481
|
-
deps: [AXPWorkflowRegistryService],
|
|
482
|
-
multi: true,
|
|
483
|
-
},
|
|
484
|
-
...Object.values(config?.actions ?? {}),
|
|
485
|
-
...Object.values(config?.functions ?? {}),
|
|
486
|
-
],
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* @ignore
|
|
491
|
-
*/
|
|
492
|
-
constructor(instances) {
|
|
493
|
-
instances?.forEach((f) => {
|
|
494
|
-
f();
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule, deps: [{ token: 'AXPWorkflowModuleFactory', optional: true }], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
498
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule }); }
|
|
499
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule }); }
|
|
500
|
-
}
|
|
501
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule, decorators: [{
|
|
502
|
-
type: NgModule,
|
|
503
|
-
args: [{
|
|
504
|
-
imports: [],
|
|
505
|
-
exports: [],
|
|
506
|
-
declarations: [],
|
|
507
|
-
providers: [],
|
|
508
|
-
}]
|
|
509
|
-
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
510
|
-
type: Optional
|
|
511
|
-
}, {
|
|
512
|
-
type: Inject,
|
|
513
|
-
args: ['AXPWorkflowModuleFactory']
|
|
514
|
-
}] }] });
|
|
515
|
-
|
|
516
414
|
/**
|
|
517
415
|
* Injection token for workflow definition loaders.
|
|
518
416
|
* Multiple loaders can be provided (multi: true).
|
|
519
417
|
*/
|
|
520
418
|
const AXP_WORKFLOW_DEFINITION_LOADER = new InjectionToken('AXP_WORKFLOW_DEFINITION_LOADER');
|
|
521
|
-
/**
|
|
522
|
-
* Resolver service for workflow definitions.
|
|
523
|
-
* Aggregates all registered loaders and resolves workflow definitions.
|
|
524
|
-
*/
|
|
525
|
-
class AXPWorkflowDefinitionResolver {
|
|
526
|
-
constructor() {
|
|
527
|
-
this.loaders = inject(AXP_WORKFLOW_DEFINITION_LOADER, { optional: true });
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Get workflow definition by name (unique key).
|
|
531
|
-
* Tries all registered loaders until one returns a definition.
|
|
532
|
-
* @param name - The workflow name (unique key)
|
|
533
|
-
* @returns Workflow definition or null if not found
|
|
534
|
-
*/
|
|
535
|
-
async get(name) {
|
|
536
|
-
const loaderArray = Array.isArray(this.loaders) ? this.loaders : this.loaders ? [this.loaders] : [];
|
|
537
|
-
for (const loader of loaderArray) {
|
|
538
|
-
try {
|
|
539
|
-
const definition = await loader.get(name);
|
|
540
|
-
if (definition) {
|
|
541
|
-
return definition;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
catch (error) {
|
|
545
|
-
console.warn(`[AXPWorkflowDefinitionResolver] Loader failed for ${name}:`, error);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
return null;
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Get all available workflow names from all loaders.
|
|
552
|
-
* @returns Array of unique workflow names
|
|
553
|
-
*/
|
|
554
|
-
async getAllNames() {
|
|
555
|
-
const loaderArray = Array.isArray(this.loaders) ? this.loaders : this.loaders ? [this.loaders] : [];
|
|
556
|
-
const allNames = new Set();
|
|
557
|
-
for (const loader of loaderArray) {
|
|
558
|
-
if (loader.getAllNames) {
|
|
559
|
-
try {
|
|
560
|
-
const names = await loader.getAllNames();
|
|
561
|
-
names.forEach((name) => allNames.add(name));
|
|
562
|
-
}
|
|
563
|
-
catch (error) {
|
|
564
|
-
console.warn('[AXPWorkflowDefinitionResolver] Failed to get names from loader:', error);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return Array.from(allNames);
|
|
569
|
-
}
|
|
570
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionResolver, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
571
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionResolver, providedIn: 'root' }); }
|
|
572
|
-
}
|
|
573
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionResolver, decorators: [{
|
|
574
|
-
type: Injectable,
|
|
575
|
-
args: [{ providedIn: 'root' }]
|
|
576
|
-
}] });
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* Registry service for workflow definitions.
|
|
580
|
-
* Caches loaded definitions and provides change notifications.
|
|
581
|
-
*/
|
|
582
|
-
class AXPWorkflowDefinitionRegistryService {
|
|
583
|
-
constructor() {
|
|
584
|
-
this.resolver = inject(AXPWorkflowDefinitionResolver);
|
|
585
|
-
this.cache = new Map();
|
|
586
|
-
this.onChanged = new Subject();
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Observable for workflow definition changes.
|
|
590
|
-
*/
|
|
591
|
-
get onChanged$() {
|
|
592
|
-
return this.onChanged.asObservable();
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Get workflow definition by name (unique key).
|
|
596
|
-
* Uses cache if available, otherwise loads from resolver.
|
|
597
|
-
* @param name - The workflow name (unique key)
|
|
598
|
-
* @returns Workflow definition or null if not found
|
|
599
|
-
*/
|
|
600
|
-
async get(name) {
|
|
601
|
-
// Check cache first
|
|
602
|
-
if (this.cache.has(name)) {
|
|
603
|
-
return this.cache.get(name);
|
|
604
|
-
}
|
|
605
|
-
// Load from resolver
|
|
606
|
-
const definition = await this.resolver.get(name);
|
|
607
|
-
if (definition) {
|
|
608
|
-
this.cache.set(name, definition);
|
|
609
|
-
this.onChanged.next({ name, action: 'registered' });
|
|
610
|
-
}
|
|
611
|
-
return definition;
|
|
612
|
-
}
|
|
613
|
-
/**
|
|
614
|
-
* Register a workflow definition in the cache.
|
|
615
|
-
* @param definition - The workflow definition to register
|
|
616
|
-
*/
|
|
617
|
-
register(definition) {
|
|
618
|
-
this.cache.set(definition.name, definition);
|
|
619
|
-
this.onChanged.next({ name: definition.name, action: 'registered' });
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Update a workflow definition in the cache.
|
|
623
|
-
* @param definition - The updated workflow definition
|
|
624
|
-
*/
|
|
625
|
-
update(definition) {
|
|
626
|
-
if (this.cache.has(definition.name)) {
|
|
627
|
-
this.cache.set(definition.name, definition);
|
|
628
|
-
this.onChanged.next({ name: definition.name, action: 'updated' });
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Remove a workflow definition from the cache.
|
|
633
|
-
* @param name - The workflow name to remove
|
|
634
|
-
*/
|
|
635
|
-
remove(name) {
|
|
636
|
-
if (this.cache.has(name)) {
|
|
637
|
-
this.cache.delete(name);
|
|
638
|
-
this.onChanged.next({ name, action: 'removed' });
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Clear all cached workflow definitions.
|
|
643
|
-
*/
|
|
644
|
-
clear() {
|
|
645
|
-
this.cache.clear();
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
|
-
* Check if a workflow definition is cached.
|
|
649
|
-
* @param definitionId - The workflow definition ID
|
|
650
|
-
* @returns True if cached, false otherwise
|
|
651
|
-
*/
|
|
652
|
-
has(definitionId) {
|
|
653
|
-
return this.cache.has(definitionId);
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Get all cached workflow definition IDs.
|
|
657
|
-
* @returns Array of definition IDs (only those that have been loaded)
|
|
658
|
-
*/
|
|
659
|
-
getAllIds() {
|
|
660
|
-
return Array.from(this.cache.keys());
|
|
661
|
-
}
|
|
662
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
663
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionRegistryService, providedIn: 'root' }); }
|
|
664
|
-
}
|
|
665
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionRegistryService, decorators: [{
|
|
666
|
-
type: Injectable,
|
|
667
|
-
args: [{
|
|
668
|
-
providedIn: 'root',
|
|
669
|
-
}]
|
|
670
|
-
}] });
|
|
671
419
|
|
|
672
420
|
// ============================================
|
|
673
421
|
// WORKFLOW INSTANCE v3.0.0 TYPES
|
|
@@ -1105,133 +853,215 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
1105
853
|
}] });
|
|
1106
854
|
|
|
1107
855
|
/**
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
*
|
|
1116
|
-
* @example
|
|
1117
|
-
* ```typescript
|
|
1118
|
-
* // In connectivity/mock
|
|
1119
|
-
* @Injectable()
|
|
1120
|
-
* export class AXCWorkflowExecutionService implements AXPWorkflowExecutionService {
|
|
1121
|
-
* async startExecution(request: AXPStartWorkflowExecutionRequest): Promise<AXPStartWorkflowExecutionResponse> {
|
|
1122
|
-
* // Mock implementation
|
|
1123
|
-
* }
|
|
1124
|
-
* }
|
|
856
|
+
* Injection token for workflow engine.
|
|
857
|
+
* Default implementation is AXPWorkflowLocalEngine.
|
|
858
|
+
*/
|
|
859
|
+
const AXP_WORKFLOW_ENGINE = new InjectionToken('AXP_WORKFLOW_ENGINE');
|
|
860
|
+
|
|
861
|
+
//#endregion
|
|
862
|
+
/**
|
|
863
|
+
* Service responsible for executing frontend workflow tasks.
|
|
1125
864
|
*
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
* constructor(private http: HttpClient) {}
|
|
865
|
+
* This service handles the execution of frontend or hybrid tasks using
|
|
866
|
+
* the command infrastructure. It translates command results into
|
|
867
|
+
* workflow-compatible output and outcome format.
|
|
1130
868
|
*
|
|
1131
|
-
*
|
|
1132
|
-
* return firstValueFrom(
|
|
1133
|
-
* this.http.post<AXPStartWorkflowExecutionResponse>(
|
|
1134
|
-
* `${this.config.baseUrl}/api/workflows/${request.workflowId}/start`,
|
|
1135
|
-
* { input: request.input }
|
|
1136
|
-
* )
|
|
1137
|
-
* );
|
|
1138
|
-
* }
|
|
1139
|
-
* }
|
|
1140
|
-
* ```
|
|
869
|
+
* This service is replaceable per platform and contains all execution logic.
|
|
1141
870
|
*/
|
|
1142
|
-
class
|
|
1143
|
-
|
|
1144
|
-
|
|
871
|
+
class FrontendTaskExecutor {
|
|
872
|
+
constructor() {
|
|
873
|
+
//#region ---- Services & Dependencies ----
|
|
874
|
+
this.commandService = inject(AXPCommandService);
|
|
875
|
+
}
|
|
876
|
+
//#endregion
|
|
877
|
+
//#region ---- Public Methods ----
|
|
878
|
+
/**
|
|
879
|
+
* Execute a frontend workflow task.
|
|
880
|
+
*
|
|
881
|
+
* Only executes tasks with executionMode 'frontend' or 'both'.
|
|
882
|
+
* Backend tasks should not be passed to this executor.
|
|
883
|
+
*
|
|
884
|
+
* @param task - Task to execute
|
|
885
|
+
* @returns Execution result with output and outcome
|
|
886
|
+
*
|
|
887
|
+
* @throws Error if task is not a frontend task
|
|
888
|
+
*/
|
|
889
|
+
async execute(task) {
|
|
890
|
+
// Validate execution mode
|
|
891
|
+
if (task.executionMode !== 'frontend' && task.executionMode !== 'both') {
|
|
892
|
+
throw new Error(`Task '${task.activityId}' is not a frontend task. ` +
|
|
893
|
+
`Execution mode: ${task.executionMode}. Backend tasks are handled automatically.`);
|
|
894
|
+
}
|
|
895
|
+
try {
|
|
896
|
+
// Check if command exists
|
|
897
|
+
const commandExists = this.commandService.exists(task.activityType);
|
|
898
|
+
if (!commandExists) {
|
|
899
|
+
console.warn(`[FrontendTaskExecutor] ⚠️ Frontend activity '${task.activityType}' is not registered. ` +
|
|
900
|
+
`Skipping execution.`);
|
|
901
|
+
return {
|
|
902
|
+
output: null,
|
|
903
|
+
outcome: 'Done'
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
// Prepare command input
|
|
907
|
+
// Flatten properties if nested (workflow-studio format)
|
|
908
|
+
let commandInput = task.input || task.config || {};
|
|
909
|
+
if (commandInput['properties'] && typeof commandInput['properties'] === 'object') {
|
|
910
|
+
// Flatten: {properties: {text: "..."}} -> {text: "..."}
|
|
911
|
+
commandInput = { ...commandInput['properties'] };
|
|
912
|
+
}
|
|
913
|
+
// Execute activity via CommandBus
|
|
914
|
+
// Activities registered as AXPCommand return {output, outcomes}
|
|
915
|
+
const result = await this.commandService.execute(task.activityType, commandInput);
|
|
916
|
+
if (!result) {
|
|
917
|
+
return {
|
|
918
|
+
output: null,
|
|
919
|
+
outcome: 'Failed',
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
if (!result.success) {
|
|
923
|
+
return {
|
|
924
|
+
output: {
|
|
925
|
+
error: result.message?.text,
|
|
926
|
+
},
|
|
927
|
+
outcome: 'Failed',
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
const commandResult = result.data;
|
|
931
|
+
const outcomes = commandResult?.outcomes ?? {};
|
|
932
|
+
// Determine outcome from command results
|
|
933
|
+
// Default to 'Done' if no outcomes specified
|
|
934
|
+
let outcome = 'Done';
|
|
935
|
+
if (Object.keys(outcomes).length > 0) {
|
|
936
|
+
outcome = outcomes['Done'] ? 'Done' : Object.keys(outcomes)[0] || 'Done';
|
|
937
|
+
}
|
|
938
|
+
return {
|
|
939
|
+
output: commandResult?.output ?? null,
|
|
940
|
+
outcome,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
console.error(`[FrontendTaskExecutor] ❌ Error executing frontend activity '${task.activityType}':`, error);
|
|
945
|
+
return {
|
|
946
|
+
output: { error: error.message || 'Unknown error' },
|
|
947
|
+
outcome: 'Failed'
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: FrontendTaskExecutor, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
952
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: FrontendTaskExecutor, providedIn: 'root' }); }
|
|
1145
953
|
}
|
|
1146
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type:
|
|
1147
|
-
type: Injectable
|
|
954
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: FrontendTaskExecutor, decorators: [{
|
|
955
|
+
type: Injectable,
|
|
956
|
+
args: [{
|
|
957
|
+
providedIn: 'root'
|
|
958
|
+
}]
|
|
1148
959
|
}] });
|
|
1149
960
|
|
|
1150
961
|
//#endregion
|
|
1151
962
|
/**
|
|
1152
|
-
*
|
|
963
|
+
* Workflow Manager - Facade for workflow lifecycle orchestration.
|
|
1153
964
|
*
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
* - Backend activities: Execute via API calls to backend
|
|
1157
|
-
* - State caching: Caches workflow state in client for performance
|
|
965
|
+
* This service is the ONLY interface the frontend uses to interact with workflows.
|
|
966
|
+
* It follows Clean Architecture principles and does NOT contain execution or business logic.
|
|
1158
967
|
*
|
|
968
|
+
* Responsibilities:
|
|
969
|
+
* - Orchestrate workflow lifecycle (start, execute, complete, resume)
|
|
970
|
+
* - Delegate execution to FrontendTaskExecutor
|
|
971
|
+
* - Cache workflow state in memory
|
|
972
|
+
* - Expose a stable API for UI
|
|
973
|
+
*
|
|
974
|
+
* Rules:
|
|
975
|
+
* - No HTTP calls (delegates to AXPWorkflowEngine)
|
|
976
|
+
* - No CommandBus / Command execution (delegates to FrontendTaskExecutor)
|
|
977
|
+
* - No workflow branching logic (backend decides)
|
|
978
|
+
* - No business validation (backend validates)
|
|
979
|
+
* - No backend assumptions (uses abstract runtime service)
|
|
1159
980
|
*/
|
|
1160
|
-
class
|
|
981
|
+
class AXPWorkflowManager {
|
|
1161
982
|
constructor() {
|
|
1162
983
|
//#region ---- Services & Dependencies ----
|
|
1163
|
-
this.
|
|
1164
|
-
this.
|
|
984
|
+
this.workflowEngine = inject(AXP_WORKFLOW_ENGINE);
|
|
985
|
+
this.frontendTaskExecutor = inject(FrontendTaskExecutor);
|
|
1165
986
|
//#endregion
|
|
1166
|
-
//#region ---- State Cache
|
|
987
|
+
//#region ---- State Cache ----
|
|
1167
988
|
/**
|
|
1168
989
|
* Cache workflow states in memory for quick access.
|
|
1169
|
-
* Key:
|
|
1170
|
-
* Value:
|
|
990
|
+
* Key: instanceId
|
|
991
|
+
* Value: AXPWorkflowInstanceState
|
|
1171
992
|
*/
|
|
1172
993
|
this.stateCache = new Map();
|
|
994
|
+
/**
|
|
995
|
+
* Cache TTL in milliseconds (5 minutes).
|
|
996
|
+
*/
|
|
997
|
+
this.CACHE_TTL = 5 * 60 * 1000;
|
|
1173
998
|
}
|
|
1174
999
|
//#endregion
|
|
1175
1000
|
//#region ---- Public Methods ----
|
|
1176
1001
|
/**
|
|
1177
|
-
* Start
|
|
1002
|
+
* Start a new workflow instance.
|
|
1178
1003
|
*
|
|
1004
|
+
* Creates a new workflow instance in backend and returns instance ID.
|
|
1179
1005
|
* Backend decides what to do: returns pendingTask or indicates completion.
|
|
1180
|
-
* Frontend only calls API - no business logic here.
|
|
1181
1006
|
*
|
|
1182
|
-
* @param workflowId - Workflow ID
|
|
1183
|
-
* @param input - Initial input data
|
|
1184
|
-
* @returns
|
|
1007
|
+
* @param workflowId - Workflow ID to start
|
|
1008
|
+
* @param input - Initial input data (optional)
|
|
1009
|
+
* @returns Start result with instanceId, state, and nextTask
|
|
1185
1010
|
*
|
|
1186
1011
|
* @example
|
|
1187
1012
|
* ```typescript
|
|
1188
|
-
* const result = await
|
|
1013
|
+
* const result = await workflowManager.start('my-workflow', { userId: '123' });
|
|
1189
1014
|
*
|
|
1190
|
-
* if (result.
|
|
1191
|
-
* // Execute task if frontend
|
|
1192
|
-
* if (result.
|
|
1193
|
-
* await
|
|
1194
|
-
* await
|
|
1015
|
+
* if (result.success && result.nextTask) {
|
|
1016
|
+
* // Execute task if frontend
|
|
1017
|
+
* if (result.nextTask.executionMode === 'frontend') {
|
|
1018
|
+
* const execResult = await workflowManager.execute(result.nextTask);
|
|
1019
|
+
* await workflowManager.complete(result.instanceId!, result.nextTask, execResult.outcome, execResult.output);
|
|
1195
1020
|
* }
|
|
1196
1021
|
* }
|
|
1197
1022
|
* ```
|
|
1198
1023
|
*/
|
|
1199
|
-
async
|
|
1024
|
+
async start(workflowId, input = {}) {
|
|
1200
1025
|
try {
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1026
|
+
const response = await this.workflowEngine.start({
|
|
1027
|
+
workflowId,
|
|
1028
|
+
input
|
|
1029
|
+
});
|
|
1030
|
+
// Cache state (normalize Date)
|
|
1031
|
+
const startNormalizedState = { ...response.state };
|
|
1032
|
+
if (startNormalizedState.lastUpdated && !(startNormalizedState.lastUpdated instanceof Date)) {
|
|
1033
|
+
startNormalizedState.lastUpdated = new Date(startNormalizedState.lastUpdated);
|
|
1034
|
+
}
|
|
1035
|
+
this.stateCache.set(response.instanceId, startNormalizedState);
|
|
1036
|
+
return {
|
|
1203
1037
|
success: true,
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1038
|
+
instanceId: response.instanceId,
|
|
1039
|
+
state: startNormalizedState,
|
|
1040
|
+
nextTask: response.pendingTask || null,
|
|
1041
|
+
output: startNormalizedState.output
|
|
1208
1042
|
};
|
|
1209
|
-
return result;
|
|
1210
1043
|
}
|
|
1211
1044
|
catch (error) {
|
|
1212
|
-
console.error('[
|
|
1045
|
+
console.error('[AXPWorkflowManager] ❌ Error starting workflow:', error);
|
|
1213
1046
|
return {
|
|
1214
1047
|
success: false,
|
|
1215
|
-
error: error.message || 'Failed to start workflow'
|
|
1216
|
-
nextTask: null
|
|
1048
|
+
error: error.message || 'Failed to start workflow'
|
|
1217
1049
|
};
|
|
1218
1050
|
}
|
|
1219
1051
|
}
|
|
1220
1052
|
/**
|
|
1221
|
-
* Execute a frontend task
|
|
1053
|
+
* Execute a frontend task.
|
|
1222
1054
|
*
|
|
1223
|
-
*
|
|
1224
|
-
*
|
|
1055
|
+
* Delegates to FrontendTaskExecutor for actual execution.
|
|
1056
|
+
* Only executes tasks with executionMode 'frontend' or 'both'.
|
|
1225
1057
|
*
|
|
1226
1058
|
* @param task - Task to execute
|
|
1227
1059
|
* @returns Execution result with output and outcome
|
|
1060
|
+
*
|
|
1061
|
+
* @throws Error if task is not a frontend task
|
|
1228
1062
|
*/
|
|
1229
|
-
async
|
|
1230
|
-
|
|
1231
|
-
if (task.executionMode !== 'frontend' && task.executionMode !== 'both') {
|
|
1232
|
-
throw new Error(`Task '${task.activityId}' is not a frontend task. Backend handles it automatically.`);
|
|
1233
|
-
}
|
|
1234
|
-
return await this.executeFrontendActivity(task);
|
|
1063
|
+
async execute(task) {
|
|
1064
|
+
return this.frontendTaskExecutor.execute(task);
|
|
1235
1065
|
}
|
|
1236
1066
|
/**
|
|
1237
1067
|
* Complete a task and get next task from backend.
|
|
@@ -1239,72 +1069,71 @@ class WorkflowCoordinator {
|
|
|
1239
1069
|
* Sends task result to backend API.
|
|
1240
1070
|
* Backend decides: next task, fail, or complete workflow.
|
|
1241
1071
|
*
|
|
1242
|
-
* @param
|
|
1072
|
+
* @param instanceId - Workflow instance ID
|
|
1243
1073
|
* @param task - Completed task
|
|
1244
1074
|
* @param outcome - Task outcome (e.g., 'Done', 'Confirmed', 'Cancelled')
|
|
1245
|
-
* @param output - Task output/result
|
|
1246
|
-
* @returns
|
|
1075
|
+
* @param output - Task output/result (optional)
|
|
1076
|
+
* @returns Complete result with next task (if any)
|
|
1247
1077
|
*/
|
|
1248
|
-
async
|
|
1078
|
+
async complete(instanceId, task, outcome, output) {
|
|
1249
1079
|
try {
|
|
1250
1080
|
// Send result to backend - backend decides next step
|
|
1251
|
-
const response = await this.
|
|
1252
|
-
|
|
1081
|
+
const response = await this.workflowEngine.resume({
|
|
1082
|
+
instanceId,
|
|
1253
1083
|
stepId: task.activityId,
|
|
1254
1084
|
taskToken: task.taskToken,
|
|
1255
1085
|
outcome,
|
|
1256
1086
|
userInput: output
|
|
1257
1087
|
});
|
|
1258
1088
|
// Update cache
|
|
1089
|
+
let completeNormalizedState = response.state;
|
|
1259
1090
|
if (response.state) {
|
|
1260
|
-
|
|
1091
|
+
// Normalize lastUpdated to Date (for cache safety)
|
|
1092
|
+
completeNormalizedState = { ...response.state };
|
|
1093
|
+
if (completeNormalizedState.lastUpdated && !(completeNormalizedState.lastUpdated instanceof Date)) {
|
|
1094
|
+
completeNormalizedState.lastUpdated = new Date(completeNormalizedState.lastUpdated);
|
|
1095
|
+
}
|
|
1096
|
+
this.stateCache.set(instanceId, completeNormalizedState);
|
|
1261
1097
|
}
|
|
1262
1098
|
return {
|
|
1263
1099
|
success: true,
|
|
1264
|
-
|
|
1100
|
+
instanceId,
|
|
1101
|
+
state: completeNormalizedState,
|
|
1265
1102
|
nextTask: response.nextTask || null,
|
|
1266
|
-
|
|
1267
|
-
state: response.state
|
|
1103
|
+
output: response.output
|
|
1268
1104
|
};
|
|
1269
1105
|
}
|
|
1270
1106
|
catch (error) {
|
|
1107
|
+
console.error('[AXPWorkflowManager] ❌ Error completing task:', error);
|
|
1271
1108
|
return {
|
|
1272
1109
|
success: false,
|
|
1273
|
-
|
|
1274
|
-
|
|
1110
|
+
instanceId,
|
|
1111
|
+
error: error.message || 'Failed to complete task'
|
|
1275
1112
|
};
|
|
1276
1113
|
}
|
|
1277
1114
|
}
|
|
1278
|
-
/**
|
|
1279
|
-
* Execute workflow by ID (backward compatibility).
|
|
1280
|
-
*
|
|
1281
|
-
* @deprecated Use startWorkflow + executeTask + completeTask pattern instead.
|
|
1282
|
-
* This method is kept for backward compatibility but will be removed.
|
|
1283
|
-
*/
|
|
1284
|
-
async executeWorkflowById(workflowId, input = {}) {
|
|
1285
|
-
// Just start workflow - caller should handle task execution
|
|
1286
|
-
return await this.startWorkflow(workflowId, input);
|
|
1287
|
-
}
|
|
1288
1115
|
/**
|
|
1289
1116
|
* Resume a suspended workflow (e.g., after user interaction).
|
|
1290
1117
|
*
|
|
1291
1118
|
* Backend determines nextStep based on outcome and outcomeConnections.
|
|
1292
|
-
* Client only provides
|
|
1119
|
+
* Client only provides instanceId, stepId, outcome, and optional userInput.
|
|
1293
1120
|
*
|
|
1294
|
-
* @param
|
|
1121
|
+
* @param instanceId - Workflow instance ID
|
|
1295
1122
|
* @param stepId - Step ID that was waiting for user input
|
|
1296
1123
|
* @param outcome - User action outcome (e.g., 'Confirmed', 'Cancelled', 'Submitted')
|
|
1297
1124
|
* @param userInput - Optional user input data
|
|
1125
|
+
* @param taskToken - Secure task token (required for secure resumption)
|
|
1126
|
+
* @returns Resume result with next task (if any)
|
|
1298
1127
|
*/
|
|
1299
|
-
async
|
|
1128
|
+
async resume(instanceId, stepId, outcome, userInput, taskToken) {
|
|
1300
1129
|
try {
|
|
1301
1130
|
// Ensure taskToken is provided for secure resumption
|
|
1302
1131
|
if (!taskToken) {
|
|
1303
|
-
throw new Error('Missing taskToken for
|
|
1132
|
+
throw new Error('Missing taskToken for resume operation');
|
|
1304
1133
|
}
|
|
1305
1134
|
// Backend handles everything: checks outcomeConnections and determines nextStep
|
|
1306
|
-
const response = await this.
|
|
1307
|
-
|
|
1135
|
+
const response = await this.workflowEngine.resume({
|
|
1136
|
+
instanceId,
|
|
1308
1137
|
stepId,
|
|
1309
1138
|
taskToken,
|
|
1310
1139
|
outcome,
|
|
@@ -1312,152 +1141,951 @@ class WorkflowCoordinator {
|
|
|
1312
1141
|
});
|
|
1313
1142
|
// Update cache with state from backend
|
|
1314
1143
|
if (response.state) {
|
|
1315
|
-
|
|
1144
|
+
// Normalize lastUpdated to Date (for cache safety)
|
|
1145
|
+
const normalizedState = { ...response.state };
|
|
1146
|
+
if (normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
|
|
1147
|
+
normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
|
|
1148
|
+
}
|
|
1149
|
+
this.stateCache.set(instanceId, normalizedState);
|
|
1150
|
+
}
|
|
1151
|
+
// Normalize state Date for return
|
|
1152
|
+
const normalizedState = response.state ? { ...response.state } : undefined;
|
|
1153
|
+
if (normalizedState && normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
|
|
1154
|
+
normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
|
|
1316
1155
|
}
|
|
1317
1156
|
return {
|
|
1318
1157
|
success: true,
|
|
1319
|
-
|
|
1158
|
+
instanceId,
|
|
1159
|
+
state: normalizedState || response.state,
|
|
1320
1160
|
nextTask: response.nextTask || null, // Backend determines this from outcomeConnections
|
|
1321
|
-
|
|
1322
|
-
state: response.state
|
|
1161
|
+
output: response.output
|
|
1323
1162
|
};
|
|
1324
1163
|
}
|
|
1325
1164
|
catch (error) {
|
|
1165
|
+
console.error('[AXPWorkflowManager] ❌ Error resuming workflow:', error);
|
|
1326
1166
|
return {
|
|
1327
1167
|
success: false,
|
|
1328
|
-
|
|
1329
|
-
|
|
1168
|
+
instanceId,
|
|
1169
|
+
error: error.message || 'Failed to resume workflow'
|
|
1330
1170
|
};
|
|
1331
1171
|
}
|
|
1332
1172
|
}
|
|
1333
1173
|
/**
|
|
1334
|
-
* Get workflow
|
|
1174
|
+
* Get workflow instance state.
|
|
1175
|
+
*
|
|
1176
|
+
* Retrieves state from cache (if valid) or from backend.
|
|
1177
|
+
*
|
|
1178
|
+
* @param instanceId - Workflow instance ID
|
|
1179
|
+
* @returns Workflow instance state or null if not found
|
|
1335
1180
|
*/
|
|
1336
|
-
async
|
|
1181
|
+
async getState(instanceId) {
|
|
1337
1182
|
// Check cache first
|
|
1338
|
-
const cached = this.stateCache.get(
|
|
1183
|
+
const cached = this.stateCache.get(instanceId);
|
|
1339
1184
|
if (cached) {
|
|
1340
|
-
//
|
|
1341
|
-
const
|
|
1342
|
-
if (
|
|
1343
|
-
|
|
1185
|
+
// Normalize lastUpdated to Date (for cache safety)
|
|
1186
|
+
const normalizedCached = { ...cached };
|
|
1187
|
+
if (normalizedCached.lastUpdated && !(normalizedCached.lastUpdated instanceof Date)) {
|
|
1188
|
+
normalizedCached.lastUpdated = new Date(normalizedCached.lastUpdated);
|
|
1189
|
+
}
|
|
1190
|
+
// Validate cache age
|
|
1191
|
+
const cacheAge = Date.now() - normalizedCached.lastUpdated.getTime();
|
|
1192
|
+
if (cacheAge < this.CACHE_TTL) {
|
|
1193
|
+
return normalizedCached;
|
|
1344
1194
|
}
|
|
1345
1195
|
}
|
|
1346
1196
|
// Fetch from backend
|
|
1347
1197
|
try {
|
|
1348
|
-
const state = await this.
|
|
1349
|
-
|
|
1198
|
+
const state = await this.workflowEngine.getState({
|
|
1199
|
+
instanceId
|
|
1350
1200
|
});
|
|
1201
|
+
// Normalize lastUpdated to Date (for cache safety)
|
|
1202
|
+
const normalizedState = { ...state };
|
|
1203
|
+
if (normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
|
|
1204
|
+
normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
|
|
1205
|
+
}
|
|
1351
1206
|
// Update cache
|
|
1352
|
-
this.stateCache.set(
|
|
1353
|
-
return
|
|
1207
|
+
this.stateCache.set(instanceId, normalizedState);
|
|
1208
|
+
return normalizedState;
|
|
1354
1209
|
}
|
|
1355
|
-
catch {
|
|
1210
|
+
catch (error) {
|
|
1211
|
+
console.error('[AXPWorkflowManager] ❌ Error getting workflow state:', error);
|
|
1356
1212
|
return null;
|
|
1357
1213
|
}
|
|
1358
1214
|
}
|
|
1215
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1216
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowManager, providedIn: 'root' }); }
|
|
1217
|
+
}
|
|
1218
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowManager, decorators: [{
|
|
1219
|
+
type: Injectable,
|
|
1220
|
+
args: [{
|
|
1221
|
+
providedIn: 'root'
|
|
1222
|
+
}]
|
|
1223
|
+
}] });
|
|
1224
|
+
|
|
1225
|
+
//#endregion
|
|
1226
|
+
/**
|
|
1227
|
+
* Local engine implementation that manages workflow progression and state.
|
|
1228
|
+
*
|
|
1229
|
+
* This engine:
|
|
1230
|
+
* - Returns frontend/both activities as pendingTask (does NOT execute them)
|
|
1231
|
+
* - Skips backend activities (does not error, continues execution)
|
|
1232
|
+
* - Maintains workflow state in memory
|
|
1233
|
+
* - Does not require backend API calls
|
|
1234
|
+
*
|
|
1235
|
+
* Execution of frontend tasks is handled by AXPWorkflowManager via FrontendTaskExecutor.
|
|
1236
|
+
* This engine only manages workflow progression and state storage.
|
|
1237
|
+
*
|
|
1238
|
+
* This is the DEFAULT engine provider. Applications can override it with
|
|
1239
|
+
* an API-based engine implementation.
|
|
1240
|
+
*/
|
|
1241
|
+
class AXPWorkflowLocalEngine {
|
|
1242
|
+
//#region ---- Services & Dependencies ----
|
|
1243
|
+
constructor(definitionLoaders = null) {
|
|
1244
|
+
this.definitionLoaders = definitionLoaders;
|
|
1245
|
+
//#endregion
|
|
1246
|
+
//#region ---- Instance Storage ----
|
|
1247
|
+
/**
|
|
1248
|
+
* In-memory storage for workflow instances.
|
|
1249
|
+
* Key: instanceId
|
|
1250
|
+
* Value: LocalWorkflowState
|
|
1251
|
+
*/
|
|
1252
|
+
this.instances = new Map();
|
|
1253
|
+
/**
|
|
1254
|
+
* Task token storage for secure resume operations.
|
|
1255
|
+
* Key: taskToken
|
|
1256
|
+
* Value: { instanceId, activityId }
|
|
1257
|
+
*/
|
|
1258
|
+
this.taskTokens = new Map();
|
|
1259
|
+
}
|
|
1260
|
+
//#endregion
|
|
1261
|
+
//#region ---- Public Methods (AXPWorkflowEngine) ----
|
|
1262
|
+
/**
|
|
1263
|
+
* Start a new workflow instance.
|
|
1264
|
+
*
|
|
1265
|
+
* Creates an in-memory workflow instance and progresses it.
|
|
1266
|
+
* Frontend/both activities are returned as pendingTask for external execution.
|
|
1267
|
+
* Backend activities are skipped.
|
|
1268
|
+
*/
|
|
1269
|
+
async start(request) {
|
|
1270
|
+
// Generate instance ID
|
|
1271
|
+
const instanceId = AXPDataGenerator.uuid();
|
|
1272
|
+
const now = new Date();
|
|
1273
|
+
// Load workflow definition
|
|
1274
|
+
const definition = await this.getDefinition(request.workflowId);
|
|
1275
|
+
if (!definition) {
|
|
1276
|
+
throw new Error(`Workflow definition not found: ${request.workflowId}`);
|
|
1277
|
+
}
|
|
1278
|
+
// Initialize workflow state
|
|
1279
|
+
const state = {
|
|
1280
|
+
instanceId,
|
|
1281
|
+
workflowId: request.workflowId,
|
|
1282
|
+
status: 'running',
|
|
1283
|
+
variables: {},
|
|
1284
|
+
input: request.input || {},
|
|
1285
|
+
output: undefined,
|
|
1286
|
+
lastUpdated: now,
|
|
1287
|
+
};
|
|
1288
|
+
// Create local state
|
|
1289
|
+
const localState = {
|
|
1290
|
+
instanceId,
|
|
1291
|
+
workflowId: request.workflowId,
|
|
1292
|
+
definition,
|
|
1293
|
+
state,
|
|
1294
|
+
completedActivities: new Set(),
|
|
1295
|
+
activityResults: new Map(),
|
|
1296
|
+
};
|
|
1297
|
+
// Store instance
|
|
1298
|
+
this.instances.set(instanceId, localState);
|
|
1299
|
+
// Execute workflow steps
|
|
1300
|
+
const pendingTask = await this.executeWorkflowSteps(localState);
|
|
1301
|
+
// Update state
|
|
1302
|
+
localState.state.lastUpdated = new Date();
|
|
1303
|
+
return {
|
|
1304
|
+
instanceId,
|
|
1305
|
+
state: localState.state,
|
|
1306
|
+
pendingTask: pendingTask || null,
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Resume a suspended workflow instance.
|
|
1311
|
+
*
|
|
1312
|
+
* Validates task token, applies externally executed result,
|
|
1313
|
+
* and continues progressing workflow steps.
|
|
1314
|
+
*/
|
|
1315
|
+
async resume(request) {
|
|
1316
|
+
// Validate task token
|
|
1317
|
+
const tokenInfo = this.taskTokens.get(request.taskToken);
|
|
1318
|
+
if (!tokenInfo || tokenInfo.instanceId !== request.instanceId || tokenInfo.activityId !== request.stepId) {
|
|
1319
|
+
throw new Error('Invalid task token');
|
|
1320
|
+
}
|
|
1321
|
+
// Get instance
|
|
1322
|
+
const localState = this.instances.get(request.instanceId);
|
|
1323
|
+
if (!localState) {
|
|
1324
|
+
throw new Error(`Workflow instance not found: ${request.instanceId}`);
|
|
1325
|
+
}
|
|
1326
|
+
// Store activity result (from external execution)
|
|
1327
|
+
localState.activityResults.set(request.stepId, {
|
|
1328
|
+
output: request.userInput || {},
|
|
1329
|
+
outcome: request.outcome,
|
|
1330
|
+
});
|
|
1331
|
+
localState.completedActivities.add(request.stepId);
|
|
1332
|
+
// Merge output/userInput into state variables
|
|
1333
|
+
if (request.userInput) {
|
|
1334
|
+
localState.state.variables = {
|
|
1335
|
+
...localState.state.variables,
|
|
1336
|
+
...(request.userInput || {}),
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
// Mark activity as completed and continue progression
|
|
1340
|
+
// Continue progressing workflow steps (skipping backend activities)
|
|
1341
|
+
const nextTask = await this.executeWorkflowSteps(localState);
|
|
1342
|
+
// Update state
|
|
1343
|
+
localState.state.lastUpdated = new Date();
|
|
1344
|
+
// Determine final status
|
|
1345
|
+
if (!nextTask && localState.state.status === 'running') {
|
|
1346
|
+
localState.state.status = 'completed';
|
|
1347
|
+
localState.state.output = localState.state.variables;
|
|
1348
|
+
}
|
|
1349
|
+
return {
|
|
1350
|
+
output: request.userInput || {},
|
|
1351
|
+
outcomes: { [request.outcome]: true },
|
|
1352
|
+
state: localState.state,
|
|
1353
|
+
nextTask: nextTask || null,
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Get current workflow instance state.
|
|
1358
|
+
*/
|
|
1359
|
+
async getState(request) {
|
|
1360
|
+
const localState = this.instances.get(request.instanceId);
|
|
1361
|
+
if (!localState) {
|
|
1362
|
+
throw new Error(`Workflow instance not found: ${request.instanceId}`);
|
|
1363
|
+
}
|
|
1364
|
+
// Normalize lastUpdated to Date (for cache safety)
|
|
1365
|
+
const state = { ...localState.state };
|
|
1366
|
+
if (state.lastUpdated && !(state.lastUpdated instanceof Date)) {
|
|
1367
|
+
state.lastUpdated = new Date(state.lastUpdated);
|
|
1368
|
+
}
|
|
1369
|
+
return state;
|
|
1370
|
+
}
|
|
1359
1371
|
//#endregion
|
|
1360
1372
|
//#region ---- Private Methods ----
|
|
1361
1373
|
/**
|
|
1362
|
-
*
|
|
1374
|
+
* Get workflow definition from available loaders.
|
|
1375
|
+
*/
|
|
1376
|
+
async getDefinition(workflowId) {
|
|
1377
|
+
// Try all loaders in order
|
|
1378
|
+
const loaders = this.definitionLoaders || [];
|
|
1379
|
+
for (const loader of loaders) {
|
|
1380
|
+
const definition = await loader.get(workflowId);
|
|
1381
|
+
if (definition) {
|
|
1382
|
+
return definition;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Progress workflow steps starting from the current position.
|
|
1363
1389
|
*
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
1390
|
+
* For frontend/both activities: returns task immediately (suspends workflow).
|
|
1391
|
+
* For backend activities: skips and continues.
|
|
1366
1392
|
*
|
|
1367
|
-
*
|
|
1368
|
-
* @returns Execution result with output and outcome
|
|
1393
|
+
* Returns the next pending task (if frontend activity found) or null (if completed).
|
|
1369
1394
|
*/
|
|
1370
|
-
async
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1395
|
+
async executeWorkflowSteps(localState) {
|
|
1396
|
+
const graph = localState.definition.graph;
|
|
1397
|
+
const activities = graph.activities || [];
|
|
1398
|
+
const connections = graph.connections || [];
|
|
1399
|
+
// Build activity map
|
|
1400
|
+
const activityMap = new Map();
|
|
1401
|
+
activities.forEach(activity => {
|
|
1402
|
+
activityMap.set(activity.id, activity);
|
|
1403
|
+
});
|
|
1404
|
+
// Build connection graph
|
|
1405
|
+
const outgoingConnections = new Map();
|
|
1406
|
+
const incomingConnections = new Map();
|
|
1407
|
+
connections.forEach(conn => {
|
|
1408
|
+
const sourceId = conn.source.activtyName;
|
|
1409
|
+
const targetId = conn.target.activtyName;
|
|
1410
|
+
if (!outgoingConnections.has(sourceId)) {
|
|
1411
|
+
outgoingConnections.set(sourceId, []);
|
|
1380
1412
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
let commandInput = task.input || task.config || {};
|
|
1385
|
-
if (commandInput['properties'] && typeof commandInput['properties'] === 'object') {
|
|
1386
|
-
// Flatten: {properties: {text: "..."}} -> {text: "..."}
|
|
1387
|
-
commandInput = { ...commandInput['properties'] };
|
|
1413
|
+
outgoingConnections.get(sourceId).push(conn);
|
|
1414
|
+
if (!incomingConnections.has(targetId)) {
|
|
1415
|
+
incomingConnections.set(targetId, []);
|
|
1388
1416
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1417
|
+
incomingConnections.get(targetId).push(conn);
|
|
1418
|
+
});
|
|
1419
|
+
// Find starting activity (use startActivityId or no incoming connections, or first activity)
|
|
1420
|
+
let currentActivityId = localState.currentActivityId;
|
|
1421
|
+
if (!currentActivityId) {
|
|
1422
|
+
// Use startActivityId if available
|
|
1423
|
+
if (graph.startActivityId) {
|
|
1424
|
+
currentActivityId = graph.startActivityId;
|
|
1425
|
+
}
|
|
1426
|
+
else {
|
|
1427
|
+
// Find root activity (no incoming connections)
|
|
1428
|
+
for (const activity of activities) {
|
|
1429
|
+
if (!incomingConnections.has(activity.id)) {
|
|
1430
|
+
currentActivityId = activity.id;
|
|
1431
|
+
break;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
// If no root found, use first activity
|
|
1435
|
+
if (!currentActivityId && activities.length > 0) {
|
|
1436
|
+
currentActivityId = activities[0].id;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
// Execute workflow steps
|
|
1441
|
+
while (currentActivityId) {
|
|
1442
|
+
const activity = activityMap.get(currentActivityId);
|
|
1443
|
+
if (!activity) {
|
|
1444
|
+
break;
|
|
1445
|
+
}
|
|
1446
|
+
// Skip if already completed
|
|
1447
|
+
if (localState.completedActivities.has(currentActivityId)) {
|
|
1448
|
+
// Move to next activity
|
|
1449
|
+
const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
|
|
1450
|
+
currentActivityId = nextId ?? undefined;
|
|
1451
|
+
continue;
|
|
1452
|
+
}
|
|
1453
|
+
// Determine execution mode
|
|
1454
|
+
const executionMode = activity.executionMode || 'frontend';
|
|
1455
|
+
// Handle backend activities: skip
|
|
1456
|
+
if (executionMode === 'backend') {
|
|
1457
|
+
console.log(`[WorkflowLocalEngine] ⏭️ Skipping backend activity: ${activity.name} (${activity.id})`);
|
|
1458
|
+
localState.completedActivities.add(currentActivityId);
|
|
1459
|
+
localState.activityResults.set(currentActivityId, {
|
|
1392
1460
|
output: null,
|
|
1393
|
-
outcome: '
|
|
1394
|
-
};
|
|
1461
|
+
outcome: 'Done',
|
|
1462
|
+
});
|
|
1463
|
+
// Move to next activity
|
|
1464
|
+
const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
|
|
1465
|
+
currentActivityId = nextId ?? undefined;
|
|
1466
|
+
continue;
|
|
1395
1467
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1468
|
+
// Handle frontend/both activities: return as pendingTask (do NOT execute)
|
|
1469
|
+
if (executionMode === 'frontend' || executionMode === 'both') {
|
|
1470
|
+
// Create task for external execution
|
|
1471
|
+
const task = {
|
|
1472
|
+
taskToken: AXPDataGenerator.uuid(),
|
|
1473
|
+
activityId: activity.id,
|
|
1474
|
+
activityType: activity.name,
|
|
1475
|
+
activityName: activity.title || undefined,
|
|
1476
|
+
executionMode: executionMode,
|
|
1477
|
+
input: activity.inputs || {},
|
|
1478
|
+
config: activity.customProperties || {},
|
|
1402
1479
|
};
|
|
1480
|
+
// Store task token for secure resume
|
|
1481
|
+
this.taskTokens.set(task.taskToken, {
|
|
1482
|
+
instanceId: localState.instanceId,
|
|
1483
|
+
activityId: activity.id,
|
|
1484
|
+
});
|
|
1485
|
+
// Update state to indicate suspension
|
|
1486
|
+
localState.currentActivityId = currentActivityId;
|
|
1487
|
+
localState.state.status = 'suspended';
|
|
1488
|
+
localState.state.currentStepId = currentActivityId;
|
|
1489
|
+
// Return task immediately - AXPWorkflowManager will execute it
|
|
1490
|
+
return task;
|
|
1403
1491
|
}
|
|
1404
|
-
|
|
1405
|
-
const
|
|
1406
|
-
|
|
1407
|
-
if (Object.keys(outcomes).length > 0) {
|
|
1408
|
-
outcome = outcomes['Done'] ? 'Done' : Object.keys(outcomes)[0] || 'Done';
|
|
1409
|
-
}
|
|
1410
|
-
return {
|
|
1411
|
-
output: commandResult?.output ?? null,
|
|
1412
|
-
outcome,
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
catch (error) {
|
|
1416
|
-
console.error(`[WorkflowCoordinator] ❌ Error executing frontend activity '${task.activityType}':`, error);
|
|
1417
|
-
return {
|
|
1418
|
-
output: { error: error.message || 'Unknown error' },
|
|
1419
|
-
outcome: 'Failed'
|
|
1420
|
-
};
|
|
1492
|
+
// Move to next activity
|
|
1493
|
+
const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
|
|
1494
|
+
currentActivityId = nextId ?? undefined;
|
|
1421
1495
|
}
|
|
1496
|
+
// Workflow completed
|
|
1497
|
+
localState.state.status = 'completed';
|
|
1498
|
+
localState.state.output = localState.state.variables;
|
|
1499
|
+
localState.currentActivityId = undefined;
|
|
1500
|
+
return null;
|
|
1422
1501
|
}
|
|
1423
1502
|
/**
|
|
1424
|
-
*
|
|
1425
|
-
* Backend returns executionId, initial state, and first task to execute.
|
|
1503
|
+
* Get next activity ID based on connections and outcomes.
|
|
1426
1504
|
*/
|
|
1427
|
-
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
}
|
|
1432
|
-
//
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1505
|
+
getNextActivityId(currentActivityId, outgoingConnections, activityResults) {
|
|
1506
|
+
const connections = outgoingConnections.get(currentActivityId) || [];
|
|
1507
|
+
if (connections.length === 0) {
|
|
1508
|
+
return null; // No outgoing connections - workflow ends
|
|
1509
|
+
}
|
|
1510
|
+
// Get current activity result
|
|
1511
|
+
const result = activityResults.get(currentActivityId);
|
|
1512
|
+
const outcome = result?.outcome || 'Done';
|
|
1513
|
+
// Find connection matching outcome
|
|
1514
|
+
// Outcome matching is typically done via port name (e.g., "Done", "Failed")
|
|
1515
|
+
for (const conn of connections) {
|
|
1516
|
+
const sourcePort = conn.source.port || 'Done';
|
|
1517
|
+
if (sourcePort === outcome) {
|
|
1518
|
+
return conn.target.activtyName;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
// If no matching outcome, use first connection (default path)
|
|
1522
|
+
if (connections.length > 0) {
|
|
1523
|
+
return connections[0].target.activtyName;
|
|
1524
|
+
}
|
|
1525
|
+
return null;
|
|
1439
1526
|
}
|
|
1440
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type:
|
|
1441
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type:
|
|
1527
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowLocalEngine, deps: [{ token: AXP_WORKFLOW_DEFINITION_LOADER, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1528
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowLocalEngine }); }
|
|
1442
1529
|
}
|
|
1443
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type:
|
|
1444
|
-
type: Injectable
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1530
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowLocalEngine, decorators: [{
|
|
1531
|
+
type: Injectable
|
|
1532
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1533
|
+
type: Optional
|
|
1534
|
+
}, {
|
|
1535
|
+
type: Inject,
|
|
1536
|
+
args: [AXP_WORKFLOW_DEFINITION_LOADER]
|
|
1537
|
+
}] }] });
|
|
1449
1538
|
|
|
1450
|
-
|
|
1451
|
-
* Types and interfaces for Workflow Execution Service.
|
|
1452
|
-
*/
|
|
1539
|
+
// Workflow Runtime Services
|
|
1453
1540
|
|
|
1454
|
-
|
|
1541
|
+
const AXP_WORKFLOW_PROVIDER = new InjectionToken('AXP_WORKFLOW_PROVIDER', {
|
|
1542
|
+
factory: () => [],
|
|
1543
|
+
});
|
|
1544
|
+
const AXP_WORKFLOW_CATEGORY_PROVIDER = new InjectionToken('AXP_WORKFLOW_CATEGORY_PROVIDER', {
|
|
1545
|
+
factory: () => [],
|
|
1546
|
+
});
|
|
1455
1547
|
|
|
1456
|
-
|
|
1548
|
+
//#region ---- Imports ----
|
|
1549
|
+
//#endregion
|
|
1550
|
+
/**
|
|
1551
|
+
* Optimized Workflow Definition Service
|
|
1552
|
+
*
|
|
1553
|
+
* Manages workflow definitions (metadata) for UI and tooling.
|
|
1554
|
+
* Similar to AXPActivityDefinitionService - only handles metadata, not execution.
|
|
1555
|
+
*
|
|
1556
|
+
* Performance optimizations:
|
|
1557
|
+
* 1. Uses childrenCount to determine if category has children (no query needed)
|
|
1558
|
+
* 2. Uses itemsCount to determine if category has workflows (no query needed)
|
|
1559
|
+
* 3. Aggressive caching prevents duplicate API calls
|
|
1560
|
+
* 4. Single pending request per resource prevents race conditions
|
|
1561
|
+
* 5. Lazy loading - only loads data when needed
|
|
1562
|
+
*/
|
|
1563
|
+
class AXPWorkflowDefinitionService {
|
|
1564
|
+
constructor() {
|
|
1565
|
+
//#region ---- Providers & Caches ----
|
|
1566
|
+
this.categoryProviders = inject(AXP_WORKFLOW_CATEGORY_PROVIDER, { optional: true }) || [];
|
|
1567
|
+
this.workflowProviders = inject(AXP_WORKFLOW_PROVIDER, { optional: true }) || [];
|
|
1568
|
+
//#endregion
|
|
1569
|
+
//#region ---- Cache Storage ----
|
|
1570
|
+
/** Cache for categories by id - O(1) lookup */
|
|
1571
|
+
this.categoriesById = new Map();
|
|
1572
|
+
/** Cache for categories by parentId - O(1) lookup */
|
|
1573
|
+
this.categoriesByParentId = new Map();
|
|
1574
|
+
/** Cache for workflow definitions by categoryId - O(1) lookup */
|
|
1575
|
+
this.workflowsByCategory = new Map();
|
|
1576
|
+
/** Cache for individual workflow definitions by name - O(1) lookup */
|
|
1577
|
+
this.workflowsByName = new Map();
|
|
1578
|
+
/** Track which provider index owns each category (by category ID) */
|
|
1579
|
+
this.categoryOwnership = new Map(); // Maps categoryId → provider index
|
|
1580
|
+
/** Pending API requests to prevent duplicate calls */
|
|
1581
|
+
this.pendingCategoriesRequests = new Map();
|
|
1582
|
+
this.pendingWorkflowsRequests = new Map();
|
|
1583
|
+
this.pendingWorkflowRequests = new Map();
|
|
1584
|
+
}
|
|
1585
|
+
//#endregion
|
|
1586
|
+
//#region ---- Public API: Categories ----
|
|
1587
|
+
/**
|
|
1588
|
+
* Get categories by parentId with aggressive caching
|
|
1589
|
+
*
|
|
1590
|
+
* Optimization: Returns cached result immediately if available,
|
|
1591
|
+
* preventing unnecessary API calls during navigation
|
|
1592
|
+
*
|
|
1593
|
+
* @param parentId - Parent category ID (undefined = root categories)
|
|
1594
|
+
* @returns Array of categories with count metadata (childrenCount, itemsCount)
|
|
1595
|
+
*/
|
|
1596
|
+
async getCategories(parentId) {
|
|
1597
|
+
// ✅ Fast path: Return cached result
|
|
1598
|
+
if (this.categoriesByParentId.has(parentId)) {
|
|
1599
|
+
return this.categoriesByParentId.get(parentId);
|
|
1600
|
+
}
|
|
1601
|
+
// ✅ Prevent duplicate requests: Return pending promise
|
|
1602
|
+
if (this.pendingCategoriesRequests.has(parentId)) {
|
|
1603
|
+
return this.pendingCategoriesRequests.get(parentId);
|
|
1604
|
+
}
|
|
1605
|
+
// ✅ Create single request and cache it
|
|
1606
|
+
const requestPromise = this.loadCategoriesFromProviders(parentId);
|
|
1607
|
+
this.pendingCategoriesRequests.set(parentId, requestPromise);
|
|
1608
|
+
return requestPromise;
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Get single category by ID with O(1) lookup
|
|
1612
|
+
*
|
|
1613
|
+
* Optimization: Uses Map for instant retrieval, falls back to
|
|
1614
|
+
* searching cache, then providers if not found
|
|
1615
|
+
*/
|
|
1616
|
+
async getCategoryById(categoryId) {
|
|
1617
|
+
// ✅ Fast path: O(1) lookup in cache
|
|
1618
|
+
if (this.categoriesById.has(categoryId)) {
|
|
1619
|
+
return this.categoriesById.get(categoryId);
|
|
1620
|
+
}
|
|
1621
|
+
// ✅ Search in cached parent-child lists
|
|
1622
|
+
for (const categories of this.categoriesByParentId.values()) {
|
|
1623
|
+
const found = categories.find(cat => cat.id === categoryId);
|
|
1624
|
+
if (found) {
|
|
1625
|
+
this.categoriesById.set(categoryId, found);
|
|
1626
|
+
return found;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
// ✅ Load root categories if not loaded
|
|
1630
|
+
if (!this.categoriesByParentId.has(undefined)) {
|
|
1631
|
+
await this.getCategories();
|
|
1632
|
+
if (this.categoriesById.has(categoryId)) {
|
|
1633
|
+
return this.categoriesById.get(categoryId);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
// ✅ Breadth-first search through hierarchy
|
|
1637
|
+
return this.searchCategoryInHierarchy(categoryId);
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Get category path from root to specified category
|
|
1641
|
+
*
|
|
1642
|
+
* Optimization: Builds path using cached categories only
|
|
1643
|
+
*/
|
|
1644
|
+
async getCategoriesPathById(categoryId) {
|
|
1645
|
+
const path = [];
|
|
1646
|
+
let currentCategoryId = categoryId;
|
|
1647
|
+
while (currentCategoryId) {
|
|
1648
|
+
const category = await this.getCategoryById(currentCategoryId);
|
|
1649
|
+
if (!category) {
|
|
1650
|
+
throw new Error(`Category '${currentCategoryId}' not found`);
|
|
1651
|
+
}
|
|
1652
|
+
path.unshift(category);
|
|
1653
|
+
currentCategoryId = category.parentId;
|
|
1654
|
+
}
|
|
1655
|
+
return path;
|
|
1656
|
+
}
|
|
1657
|
+
//#endregion
|
|
1658
|
+
//#region ---- Public API: Workflow Definitions ----
|
|
1659
|
+
/**
|
|
1660
|
+
* Get workflow definitions for a category with smart caching
|
|
1661
|
+
*
|
|
1662
|
+
* Optimization: Checks itemsCount before querying
|
|
1663
|
+
* - If itemsCount = 0, returns empty array (no API call)
|
|
1664
|
+
* - If itemsCount > 0, loads and caches workflow definitions
|
|
1665
|
+
* - Returns cached result on subsequent calls
|
|
1666
|
+
*
|
|
1667
|
+
* @param categoryId - Category ID to get workflow definitions from
|
|
1668
|
+
* @returns Array of workflow definition metadata
|
|
1669
|
+
*/
|
|
1670
|
+
async getWorkflowsByCategoryId(categoryId) {
|
|
1671
|
+
// ✅ Fast path: Return cached result
|
|
1672
|
+
if (this.workflowsByCategory.has(categoryId)) {
|
|
1673
|
+
return this.workflowsByCategory.get(categoryId);
|
|
1674
|
+
}
|
|
1675
|
+
// ✅ Smart optimization: Check itemsCount before querying
|
|
1676
|
+
const category = await this.getCategoryById(categoryId);
|
|
1677
|
+
if (category && category.itemsCount !== undefined && category.itemsCount === 0) {
|
|
1678
|
+
// Category has no workflows - cache empty array and skip API call
|
|
1679
|
+
const emptyArray = [];
|
|
1680
|
+
this.workflowsByCategory.set(categoryId, emptyArray);
|
|
1681
|
+
return emptyArray;
|
|
1682
|
+
}
|
|
1683
|
+
// ✅ Prevent duplicate requests
|
|
1684
|
+
if (this.pendingWorkflowsRequests.has(categoryId)) {
|
|
1685
|
+
return this.pendingWorkflowsRequests.get(categoryId);
|
|
1686
|
+
}
|
|
1687
|
+
// ✅ Load from providers
|
|
1688
|
+
const requestPromise = this.loadWorkflowsFromProviders(categoryId);
|
|
1689
|
+
this.pendingWorkflowsRequests.set(categoryId, requestPromise);
|
|
1690
|
+
return requestPromise;
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Get single workflow definition metadata by name with O(1) lookup
|
|
1694
|
+
*
|
|
1695
|
+
* Optimization: Uses Map for instant retrieval
|
|
1696
|
+
*
|
|
1697
|
+
* @param name - Workflow name (unique identifier)
|
|
1698
|
+
* @returns Workflow definition metadata or undefined if not found
|
|
1699
|
+
*/
|
|
1700
|
+
async getWorkflowByName(name) {
|
|
1701
|
+
// ✅ Fast path: O(1) lookup in cache
|
|
1702
|
+
if (this.workflowsByName.has(name)) {
|
|
1703
|
+
return this.workflowsByName.get(name);
|
|
1704
|
+
}
|
|
1705
|
+
// ✅ Prevent duplicate requests
|
|
1706
|
+
if (this.pendingWorkflowRequests.has(name)) {
|
|
1707
|
+
return this.pendingWorkflowRequests.get(name);
|
|
1708
|
+
}
|
|
1709
|
+
// ✅ Load from providers
|
|
1710
|
+
const requestPromise = this.loadWorkflowFromProviders(name);
|
|
1711
|
+
this.pendingWorkflowRequests.set(name, requestPromise);
|
|
1712
|
+
return requestPromise;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Get category ID containing a specific workflow definition
|
|
1716
|
+
*
|
|
1717
|
+
* Optimization: Searches cache first, loads on-demand if needed
|
|
1718
|
+
*/
|
|
1719
|
+
async getCategoryIdByWorkflowName(workflowName) {
|
|
1720
|
+
// ✅ Search in cached workflow definitions
|
|
1721
|
+
for (const [categoryId, definitions] of this.workflowsByCategory.entries()) {
|
|
1722
|
+
if (definitions.some(def => def.name === workflowName)) {
|
|
1723
|
+
return categoryId;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
// ✅ Try loading the workflow definition to find its category
|
|
1727
|
+
const definition = await this.getWorkflowByName(workflowName);
|
|
1728
|
+
if (definition && definition.category) {
|
|
1729
|
+
// Try to find category by name/id
|
|
1730
|
+
const categories = await this.getCategories();
|
|
1731
|
+
const found = categories.find(cat => cat.id === definition.category || cat.title === definition.category);
|
|
1732
|
+
if (found) {
|
|
1733
|
+
return found.id;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
return undefined;
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Get category path for a workflow
|
|
1740
|
+
*/
|
|
1741
|
+
async getCategoriesPathByWorkflowName(workflowName) {
|
|
1742
|
+
const categoryId = await this.getCategoryIdByWorkflowName(workflowName);
|
|
1743
|
+
if (!categoryId) {
|
|
1744
|
+
throw new Error(`Workflow '${workflowName}' not found in any category`);
|
|
1745
|
+
}
|
|
1746
|
+
return this.getCategoriesPathById(categoryId);
|
|
1747
|
+
}
|
|
1748
|
+
//#endregion
|
|
1749
|
+
//#region ---- Private: Data Loading ----
|
|
1750
|
+
/**
|
|
1751
|
+
* Load categories from providers and cache results
|
|
1752
|
+
*
|
|
1753
|
+
* Optimization: Tracks provider ownership to avoid unnecessary API calls
|
|
1754
|
+
* - For root (parentId = undefined): Query ALL providers
|
|
1755
|
+
* - For children: Only query the provider that owns the parent
|
|
1756
|
+
*/
|
|
1757
|
+
async loadCategoriesFromProviders(parentId) {
|
|
1758
|
+
try {
|
|
1759
|
+
const resolvedProviders = await Promise.allSettled(this.categoryProviders);
|
|
1760
|
+
const categories = [];
|
|
1761
|
+
// Determine which provider(s) to query
|
|
1762
|
+
const providerIndicesToQuery = parentId
|
|
1763
|
+
? this.getProviderIndexForCategory(parentId)
|
|
1764
|
+
: null; // Root: query all providers
|
|
1765
|
+
for (let i = 0; i < resolvedProviders.length; i++) {
|
|
1766
|
+
const p = resolvedProviders[i];
|
|
1767
|
+
// Skip if we have a specific provider index and this isn't it
|
|
1768
|
+
if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
|
|
1772
|
+
try {
|
|
1773
|
+
const cats = await p.value.getList(parentId);
|
|
1774
|
+
if (Array.isArray(cats) && cats.length > 0) {
|
|
1775
|
+
categories.push(...cats);
|
|
1776
|
+
// ✅ Track ownership: This provider INDEX owns these categories
|
|
1777
|
+
cats.forEach(cat => this.categoryOwnership.set(cat.id, i));
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
catch {
|
|
1781
|
+
// Continue on error - try other providers
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
// ✅ Cache results for fast subsequent access
|
|
1786
|
+
this.categoriesByParentId.set(parentId, categories);
|
|
1787
|
+
categories.forEach(cat => this.categoriesById.set(cat.id, cat));
|
|
1788
|
+
return categories;
|
|
1789
|
+
}
|
|
1790
|
+
finally {
|
|
1791
|
+
this.pendingCategoriesRequests.delete(parentId);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Get the provider index that owns a specific category
|
|
1796
|
+
*
|
|
1797
|
+
* @returns Array with provider index, or null if ownership unknown (query all)
|
|
1798
|
+
*/
|
|
1799
|
+
getProviderIndexForCategory(categoryId) {
|
|
1800
|
+
const ownerIndex = this.categoryOwnership.get(categoryId);
|
|
1801
|
+
if (ownerIndex !== undefined) {
|
|
1802
|
+
return [ownerIndex];
|
|
1803
|
+
}
|
|
1804
|
+
// Ownership unknown - will query all providers (fallback)
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Load workflow definitions from providers and cache results
|
|
1809
|
+
*
|
|
1810
|
+
* Optimization: Only queries the provider that owns the category
|
|
1811
|
+
* Uses provider INDEX to match category provider with workflow provider
|
|
1812
|
+
*/
|
|
1813
|
+
async loadWorkflowsFromProviders(categoryId) {
|
|
1814
|
+
try {
|
|
1815
|
+
const resolvedProviders = await Promise.allSettled(this.workflowProviders);
|
|
1816
|
+
const definitions = [];
|
|
1817
|
+
// ✅ Smart routing: Get provider INDEX that owns this category
|
|
1818
|
+
const ownerIndex = this.categoryOwnership.get(categoryId);
|
|
1819
|
+
const providerIndicesToQuery = ownerIndex !== undefined ? [ownerIndex] : null;
|
|
1820
|
+
for (let i = 0; i < resolvedProviders.length; i++) {
|
|
1821
|
+
const p = resolvedProviders[i];
|
|
1822
|
+
// Skip if we have a specific provider index and this isn't it
|
|
1823
|
+
if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
|
|
1827
|
+
try {
|
|
1828
|
+
const defs = await p.value.getList(categoryId);
|
|
1829
|
+
if (Array.isArray(defs)) {
|
|
1830
|
+
definitions.push(...defs);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
catch {
|
|
1834
|
+
// Continue on error - try other providers
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
// ✅ Cache results for fast subsequent access
|
|
1839
|
+
this.workflowsByCategory.set(categoryId, definitions);
|
|
1840
|
+
definitions.forEach(def => {
|
|
1841
|
+
if (def.name) {
|
|
1842
|
+
this.workflowsByName.set(def.name, def);
|
|
1843
|
+
}
|
|
1844
|
+
});
|
|
1845
|
+
return definitions;
|
|
1846
|
+
}
|
|
1847
|
+
finally {
|
|
1848
|
+
this.pendingWorkflowsRequests.delete(categoryId);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Load single workflow definition from providers and cache result
|
|
1853
|
+
*/
|
|
1854
|
+
async loadWorkflowFromProviders(name) {
|
|
1855
|
+
try {
|
|
1856
|
+
const resolvedProviders = await Promise.allSettled(this.workflowProviders);
|
|
1857
|
+
// Try providers first
|
|
1858
|
+
for (const p of resolvedProviders) {
|
|
1859
|
+
if (p.status === 'fulfilled' && p.value && typeof p.value.getById === 'function') {
|
|
1860
|
+
try {
|
|
1861
|
+
const result = await p.value.getById(name);
|
|
1862
|
+
if (result) {
|
|
1863
|
+
this.workflowsByName.set(name, result);
|
|
1864
|
+
return result;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
catch {
|
|
1868
|
+
// Continue on error
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
// Fallback: Search in cached workflow definitions
|
|
1873
|
+
for (const definitions of this.workflowsByCategory.values()) {
|
|
1874
|
+
const found = definitions.find(def => def.name === name);
|
|
1875
|
+
if (found) {
|
|
1876
|
+
this.workflowsByName.set(name, found);
|
|
1877
|
+
return found;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return undefined;
|
|
1881
|
+
}
|
|
1882
|
+
finally {
|
|
1883
|
+
this.pendingWorkflowRequests.delete(name);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Breadth-first search through category hierarchy
|
|
1888
|
+
*/
|
|
1889
|
+
async searchCategoryInHierarchy(categoryId) {
|
|
1890
|
+
const searchQueue = [undefined];
|
|
1891
|
+
const searched = new Set();
|
|
1892
|
+
while (searchQueue.length > 0) {
|
|
1893
|
+
const parentId = searchQueue.shift();
|
|
1894
|
+
if (searched.has(parentId))
|
|
1895
|
+
continue;
|
|
1896
|
+
searched.add(parentId);
|
|
1897
|
+
const categories = await this.getCategories(parentId);
|
|
1898
|
+
const found = categories.find(cat => cat.id === categoryId);
|
|
1899
|
+
if (found) {
|
|
1900
|
+
return found;
|
|
1901
|
+
}
|
|
1902
|
+
// ✅ Optimization: Only search children if childrenCount > 0
|
|
1903
|
+
for (const category of categories) {
|
|
1904
|
+
if (category.childrenCount > 0 && !searched.has(category.id)) {
|
|
1905
|
+
searchQueue.push(category.id);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
return undefined;
|
|
1910
|
+
}
|
|
1911
|
+
//#endregion
|
|
1912
|
+
//#region ---- Cache Management ----
|
|
1913
|
+
/**
|
|
1914
|
+
* Check if category has children (uses cached count)
|
|
1915
|
+
*/
|
|
1916
|
+
categoryHasChildren(categoryId) {
|
|
1917
|
+
const category = this.categoriesById.get(categoryId);
|
|
1918
|
+
return category ? category.childrenCount > 0 : false;
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Check if category has workflows (uses cached count)
|
|
1922
|
+
*/
|
|
1923
|
+
categoryHasWorkflows(categoryId) {
|
|
1924
|
+
const category = this.categoriesById.get(categoryId);
|
|
1925
|
+
return category ? (category.itemsCount ?? 0) > 0 : false;
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Clear all caches
|
|
1929
|
+
*/
|
|
1930
|
+
clearAllCache() {
|
|
1931
|
+
this.categoriesById.clear();
|
|
1932
|
+
this.categoriesByParentId.clear();
|
|
1933
|
+
this.workflowsByCategory.clear();
|
|
1934
|
+
this.workflowsByName.clear();
|
|
1935
|
+
this.categoryOwnership.clear();
|
|
1936
|
+
this.pendingCategoriesRequests.clear();
|
|
1937
|
+
this.pendingWorkflowsRequests.clear();
|
|
1938
|
+
this.pendingWorkflowRequests.clear();
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Clear categories cache only
|
|
1942
|
+
*/
|
|
1943
|
+
clearCategoriesCache() {
|
|
1944
|
+
this.categoriesById.clear();
|
|
1945
|
+
this.categoriesByParentId.clear();
|
|
1946
|
+
this.categoryOwnership.clear();
|
|
1947
|
+
this.pendingCategoriesRequests.clear();
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Clear workflows cache only
|
|
1951
|
+
*/
|
|
1952
|
+
clearWorkflowsCache() {
|
|
1953
|
+
this.workflowsByCategory.clear();
|
|
1954
|
+
this.workflowsByName.clear();
|
|
1955
|
+
this.pendingWorkflowsRequests.clear();
|
|
1956
|
+
this.pendingWorkflowRequests.clear();
|
|
1957
|
+
}
|
|
1958
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1959
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionService, providedIn: 'root' }); }
|
|
1960
|
+
}
|
|
1961
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionService, decorators: [{
|
|
1962
|
+
type: Injectable,
|
|
1963
|
+
args: [{
|
|
1964
|
+
providedIn: 'root',
|
|
1965
|
+
}]
|
|
1966
|
+
}] });
|
|
1967
|
+
|
|
1968
|
+
// Workflow Definition Types (Storage/Database)
|
|
1969
|
+
|
|
1970
|
+
class AXPWorkflowModule {
|
|
1971
|
+
static forRoot(config) {
|
|
1972
|
+
return {
|
|
1973
|
+
ngModule: AXPWorkflowModule,
|
|
1974
|
+
providers: [
|
|
1975
|
+
{
|
|
1976
|
+
provide: 'AXPWorkflowModuleFactory',
|
|
1977
|
+
useFactory: (registry) => () => {
|
|
1978
|
+
registry.registerAction('start-workflow', AXPStartWorkflowAction);
|
|
1979
|
+
registry.registerAction('decide', AXPWorkflowDecideAction);
|
|
1980
|
+
//
|
|
1981
|
+
if (config?.functions) {
|
|
1982
|
+
for (const [key, type] of Object.entries(config.functions)) {
|
|
1983
|
+
registry.registerFunction(key, type);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
//
|
|
1987
|
+
if (config?.actions) {
|
|
1988
|
+
for (const [key, type] of Object.entries(config.actions)) {
|
|
1989
|
+
registry.registerAction(key, type);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
//
|
|
1993
|
+
if (config?.workflows) {
|
|
1994
|
+
for (const [key, type] of Object.entries(config.workflows)) {
|
|
1995
|
+
registry.registerWorkflow(key, type);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
},
|
|
1999
|
+
deps: [AXPWorkflowRegistryService],
|
|
2000
|
+
multi: true,
|
|
2001
|
+
},
|
|
2002
|
+
...Object.values(config?.actions ?? { AXPStartWorkflowAction }),
|
|
2003
|
+
...Object.values(config?.functions ?? {}),
|
|
2004
|
+
],
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
static forChild(config) {
|
|
2008
|
+
return {
|
|
2009
|
+
ngModule: AXPWorkflowModule,
|
|
2010
|
+
providers: [
|
|
2011
|
+
// Built-in activities are already registered in forRoot via @NgModule providers
|
|
2012
|
+
// No need to register again in forChild
|
|
2013
|
+
{
|
|
2014
|
+
provide: 'AXPWorkflowModuleFactory',
|
|
2015
|
+
useFactory: (registry) => () => {
|
|
2016
|
+
registry.registerAction('start-workflow', AXPStartWorkflowAction);
|
|
2017
|
+
registry.registerAction('decide', AXPWorkflowDecideAction);
|
|
2018
|
+
//
|
|
2019
|
+
if (config?.functions) {
|
|
2020
|
+
for (const [key, type] of Object.entries(config.functions)) {
|
|
2021
|
+
registry.registerFunction(key, type);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
//
|
|
2025
|
+
if (config?.actions) {
|
|
2026
|
+
for (const [key, type] of Object.entries(config.actions)) {
|
|
2027
|
+
registry.registerAction(key, type);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
//
|
|
2031
|
+
if (config?.workflows) {
|
|
2032
|
+
for (const [key, type] of Object.entries(config.workflows)) {
|
|
2033
|
+
registry.registerWorkflow(key, type);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
},
|
|
2037
|
+
deps: [AXPWorkflowRegistryService],
|
|
2038
|
+
multi: true,
|
|
2039
|
+
},
|
|
2040
|
+
...Object.values(config?.actions ?? {}),
|
|
2041
|
+
...Object.values(config?.functions ?? {}),
|
|
2042
|
+
],
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* @ignore
|
|
2047
|
+
*/
|
|
2048
|
+
constructor(instances) {
|
|
2049
|
+
instances?.forEach((f) => {
|
|
2050
|
+
f();
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule, deps: [{ token: 'AXPWorkflowModuleFactory', optional: true }], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2054
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule }); }
|
|
2055
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule, providers: [
|
|
2056
|
+
AXPWorkflowLocalEngine,
|
|
2057
|
+
{
|
|
2058
|
+
provide: AXP_WORKFLOW_ENGINE,
|
|
2059
|
+
useExisting: AXPWorkflowLocalEngine,
|
|
2060
|
+
},
|
|
2061
|
+
AXPWorkflowManager,
|
|
2062
|
+
] }); }
|
|
2063
|
+
}
|
|
2064
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule, decorators: [{
|
|
2065
|
+
type: NgModule,
|
|
2066
|
+
args: [{
|
|
2067
|
+
imports: [],
|
|
2068
|
+
exports: [],
|
|
2069
|
+
declarations: [],
|
|
2070
|
+
providers: [
|
|
2071
|
+
AXPWorkflowLocalEngine,
|
|
2072
|
+
{
|
|
2073
|
+
provide: AXP_WORKFLOW_ENGINE,
|
|
2074
|
+
useExisting: AXPWorkflowLocalEngine,
|
|
2075
|
+
},
|
|
2076
|
+
AXPWorkflowManager,
|
|
2077
|
+
],
|
|
2078
|
+
}]
|
|
2079
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
2080
|
+
type: Optional
|
|
2081
|
+
}, {
|
|
2082
|
+
type: Inject,
|
|
2083
|
+
args: ['AXPWorkflowModuleFactory']
|
|
2084
|
+
}] }] });
|
|
1457
2085
|
|
|
1458
2086
|
/**
|
|
1459
2087
|
* Generated bundle index. Do not edit.
|
|
1460
2088
|
*/
|
|
1461
2089
|
|
|
1462
|
-
export { AXPActivityDefinitionService, AXPWorkflowAction, AXPWorkflowContext,
|
|
2090
|
+
export { AXPActivityDefinitionService, AXPWorkflowAction, AXPWorkflowContext, AXPWorkflowDefinitionService, AXPWorkflowError, AXPWorkflowEventService, AXPWorkflowFunction, AXPWorkflowLocalEngine, AXPWorkflowManager, AXPWorkflowModule, AXPWorkflowRegistryService, AXPWorkflowService, AXP_ACTIVITY_CATEGORY_PROVIDER, AXP_ACTIVITY_PROVIDER, AXP_WORKFLOW_CATEGORY_PROVIDER, AXP_WORKFLOW_DEFINITION_LOADER, AXP_WORKFLOW_ENGINE, AXP_WORKFLOW_PROVIDER, FrontendTaskExecutor, createWorkFlowEvent, ofType };
|
|
1463
2091
|
//# sourceMappingURL=acorex-platform-workflow.mjs.map
|