@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.
Files changed (32) hide show
  1. package/auth/index.d.ts +228 -3
  2. package/fesm2022/acorex-platform-auth.mjs +162 -2
  3. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  4. package/fesm2022/acorex-platform-common.mjs +1 -1
  5. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  6. package/fesm2022/acorex-platform-layout-builder.mjs +11 -2
  7. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-layout-components.mjs +7 -7
  9. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  10. package/fesm2022/acorex-platform-layout-entity.mjs +79 -34
  11. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-layout-widget-core.mjs +108 -1
  13. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-widgets.mjs +224 -89
  15. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  16. 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
  17. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-D3qZa5fM.mjs.map +1 -0
  18. package/fesm2022/acorex-platform-themes-default.mjs +2 -2
  19. 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
  20. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-Dz0cylyQ.mjs.map +1 -0
  21. package/fesm2022/acorex-platform-themes-shared.mjs +1 -1
  22. package/fesm2022/acorex-platform-workflow.mjs +1084 -456
  23. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  24. package/layout/builder/index.d.ts +6 -0
  25. package/layout/components/index.d.ts +4 -3
  26. package/layout/entity/index.d.ts +9 -0
  27. package/layout/widget-core/index.d.ts +42 -1
  28. package/layout/widgets/index.d.ts +12 -7
  29. package/package.json +9 -9
  30. package/workflow/index.d.ts +798 -939
  31. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs.map +0 -1
  32. 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, InjectionToken } from '@angular/core';
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
- * Abstract service for workflow execution operations.
1109
- *
1110
- * This service handles communication with backend for workflow execution.
1111
- *
1112
- * Implementation should be provided in connectivity layer:
1113
- * - Mock implementation: @acorex/connectivity/mock
1114
- * - API implementation: @acorex/connectivity/api
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
- * // In connectivity/api
1127
- * @Injectable()
1128
- * export class AXCWorkflowExecutionService implements AXPWorkflowExecutionService {
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
- * async startExecution(request: AXPStartWorkflowExecutionRequest): Promise<AXPStartWorkflowExecutionResponse> {
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 AXPWorkflowExecutionService {
1143
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowExecutionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1144
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowExecutionService }); }
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: AXPWorkflowExecutionService, decorators: [{
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
- * Production Workflow Coordinator.
963
+ * Workflow Manager - Facade for workflow lifecycle orchestration.
1153
964
  *
1154
- * Separates frontend/backend execution:
1155
- * - Frontend activities: Execute with AXPCommand in browser
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 WorkflowCoordinator {
981
+ class AXPWorkflowManager {
1161
982
  constructor() {
1162
983
  //#region ---- Services & Dependencies ----
1163
- this.workflowExecutionService = inject(AXPWorkflowExecutionService);
1164
- this.commandService = inject(AXPCommandService);
984
+ this.workflowEngine = inject(AXP_WORKFLOW_ENGINE);
985
+ this.frontendTaskExecutor = inject(FrontendTaskExecutor);
1165
986
  //#endregion
1166
- //#region ---- State Cache (Client-side for Performance) ----
987
+ //#region ---- State Cache ----
1167
988
  /**
1168
989
  * Cache workflow states in memory for quick access.
1169
- * Key: executionId
1170
- * Value: AXPWorkflowExecutionState
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 workflow execution in backend.
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 Execution result with pendingTask (if any)
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 coordinator.startWorkflow('my-workflow', { userId: '123' });
1013
+ * const result = await workflowManager.start('my-workflow', { userId: '123' });
1189
1014
  *
1190
- * if (result.pendingTask) {
1191
- * // Execute task if frontend, or wait for backend to complete
1192
- * if (result.pendingTask.executionMode === 'frontend') {
1193
- * await coordinator.executeTask(result.pendingTask);
1194
- * await coordinator.completeTask(result.executionId, result.pendingTask, outcome, output);
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 startWorkflow(workflowId, input = {}) {
1024
+ async start(workflowId, input = {}) {
1200
1025
  try {
1201
- const execution = await this.startWorkflowExecution(workflowId, input);
1202
- const result = {
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
- output: execution.state.output,
1205
- nextTask: execution.pendingTask || null,
1206
- executionId: execution.executionId,
1207
- state: execution.state
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('[WorkflowCoordinator] ❌ Error in startWorkflow', 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 using AXPCommand.
1053
+ * Execute a frontend task.
1222
1054
  *
1223
- * Only executes if task.executionMode is 'frontend' or 'both'.
1224
- * Backend tasks are handled automatically by backend.
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 executeTask(task) {
1230
- // Only execute frontend tasks
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 executionId - Execution ID
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 Next task from backend (if any)
1075
+ * @param output - Task output/result (optional)
1076
+ * @returns Complete result with next task (if any)
1247
1077
  */
1248
- async completeTask(executionId, task, outcome, output) {
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.workflowExecutionService.resumeExecution({
1252
- executionId,
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
- this.stateCache.set(executionId, response.state);
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
- output: response.output,
1100
+ instanceId,
1101
+ state: completeNormalizedState,
1265
1102
  nextTask: response.nextTask || null,
1266
- executionId,
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
- error: error.message || 'Failed to complete task',
1274
- nextTask: null
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 executionId, stepId, outcome, and optional userInput.
1119
+ * Client only provides instanceId, stepId, outcome, and optional userInput.
1293
1120
  *
1294
- * @param executionId - Workflow execution ID
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 resumeWorkflow(executionId, stepId, outcome, userInput, taskToken) {
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 resumeWorkflow');
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.workflowExecutionService.resumeExecution({
1307
- executionId,
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
- this.stateCache.set(executionId, response.state);
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
- output: response.output,
1158
+ instanceId,
1159
+ state: normalizedState || response.state,
1320
1160
  nextTask: response.nextTask || null, // Backend determines this from outcomeConnections
1321
- executionId,
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
- error: error.message || 'Failed to resume workflow',
1329
- nextTask: null
1168
+ instanceId,
1169
+ error: error.message || 'Failed to resume workflow'
1330
1170
  };
1331
1171
  }
1332
1172
  }
1333
1173
  /**
1334
- * Get workflow execution state (from cache or backend).
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 getWorkflowState(executionId) {
1181
+ async getState(instanceId) {
1337
1182
  // Check cache first
1338
- const cached = this.stateCache.get(executionId);
1183
+ const cached = this.stateCache.get(instanceId);
1339
1184
  if (cached) {
1340
- // Cache is valid for 5 minutes
1341
- const cacheAge = Date.now() - cached.lastUpdated.getTime();
1342
- if (cacheAge < 5 * 60 * 1000) {
1343
- return cached;
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.workflowExecutionService.getExecutionState({
1349
- executionId
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(executionId, state);
1353
- return state;
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
- * Execute a frontend activity using CommandBus.
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
- * Frontend activities are executed in the browser using AXPCommandService.
1365
- * Activities can also be executed in both frontend and backend (hybrid mode).
1390
+ * For frontend/both activities: returns task immediately (suspends workflow).
1391
+ * For backend activities: skips and continues.
1366
1392
  *
1367
- * @param task - Frontend task to execute
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 executeFrontendActivity(task) {
1371
- try {
1372
- // Check if command exists
1373
- const commandExists = this.commandService.exists(task.activityType);
1374
- if (!commandExists) {
1375
- console.warn(`[WorkflowCoordinator] ⚠️ Frontend activity '${task.activityType}' is not registered. Skipping execution.`);
1376
- return {
1377
- output: null,
1378
- outcome: 'Done'
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
- // Execute activity via CommandBus
1382
- // Activities registered as AXPCommand return {output, outcomes}
1383
- // 🎯 Flatten properties if nested (workflow-studio format)
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
- const result = await this.commandService.execute(task.activityType, commandInput);
1390
- if (!result) {
1391
- return {
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: 'Failed',
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
- if (!result.success) {
1397
- return {
1398
- output: {
1399
- error: result.message?.text,
1400
- },
1401
- outcome: 'Failed',
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
- const commandResult = result.data;
1405
- const outcomes = commandResult?.outcomes ?? {};
1406
- let outcome = 'Done';
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
- * Start workflow execution in backend.
1425
- * Backend returns executionId, initial state, and first task to execute.
1503
+ * Get next activity ID based on connections and outcomes.
1426
1504
  */
1427
- async startWorkflowExecution(workflowId, input) {
1428
- const response = await this.workflowExecutionService.startExecution({
1429
- workflowId,
1430
- input
1431
- });
1432
- // Cache state
1433
- this.stateCache.set(response.executionId, response.state);
1434
- return {
1435
- executionId: response.executionId,
1436
- state: response.state,
1437
- pendingTask: response.pendingTask
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: WorkflowCoordinator, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1441
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: WorkflowCoordinator, providedIn: 'root' }); }
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: WorkflowCoordinator, decorators: [{
1444
- type: Injectable,
1445
- args: [{
1446
- providedIn: 'root'
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
- // Production Workflow Coordinator (Frontend/Backend Separation)
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
- // Workflow Definition Types (Storage/Database)
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, AXPWorkflowDefinitionRegistryService, AXPWorkflowDefinitionResolver, AXPWorkflowError, AXPWorkflowEventService, AXPWorkflowExecutionService, AXPWorkflowFunction, AXPWorkflowModule, AXPWorkflowRegistryService, AXPWorkflowService, AXP_ACTIVITY_CATEGORY_PROVIDER, AXP_ACTIVITY_PROVIDER, AXP_WORKFLOW_DEFINITION_LOADER, WorkflowCoordinator, createWorkFlowEvent, ofType };
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