@acorex/platform 21.0.0-next.3 → 21.0.0-next.7

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 (30) hide show
  1. package/auth/index.d.ts +6 -6
  2. package/common/index.d.ts +10 -5
  3. package/core/index.d.ts +55 -315
  4. package/fesm2022/acorex-platform-auth.mjs +18 -22
  5. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  6. package/fesm2022/acorex-platform-common.mjs +62 -38
  7. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-core.mjs +150 -246
  9. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  10. package/fesm2022/acorex-platform-layout-builder.mjs +11 -2
  11. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-layout-components.mjs +3 -3
  13. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-entity.mjs +67 -83
  15. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-widget-core.mjs +110 -33
  17. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  18. package/fesm2022/acorex-platform-layout-widgets.mjs +19 -11
  19. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  20. package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs → acorex-platform-themes-default-entity-master-list-view.component-DfJEx_bs.mjs} +2 -2
  21. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DfJEx_bs.mjs.map +1 -0
  22. package/fesm2022/acorex-platform-themes-default.mjs +2 -2
  23. package/fesm2022/acorex-platform-workflow.mjs +1268 -1699
  24. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  25. package/layout/builder/index.d.ts +6 -0
  26. package/layout/entity/index.d.ts +2 -9
  27. package/layout/widget-core/index.d.ts +42 -4
  28. package/package.json +5 -5
  29. package/workflow/index.d.ts +650 -1074
  30. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs.map +0 -1
@@ -1,14 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, InjectionToken, Optional, SkipSelf, Inject, NgModule } 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';
6
- import { AXPCommandService, AXPCommandRegistry, AXPQueryService, provideCommandSetups, AXP_COMMAND_SETUP } from '@acorex/platform/runtime';
7
- import { AXDialogService } from '@acorex/components/dialog';
8
- import { AXTranslationService } from '@acorex/core/translation';
9
- import { AXPLayoutBuilderService } from '@acorex/platform/layout/builder';
10
- import { AXToastService } from '@acorex/components/toast';
11
- import { Router } from '@angular/router';
5
+ import { setSmart, AXPDataGenerator } from '@acorex/platform/core';
6
+ import { AXPCommandService } from '@acorex/platform/runtime';
12
7
 
13
8
  class AXPWorkflowError extends Error {
14
9
  constructor(message, inner = null) {
@@ -421,541 +416,652 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
421
416
  * Multiple loaders can be provided (multi: true).
422
417
  */
423
418
  const AXP_WORKFLOW_DEFINITION_LOADER = new InjectionToken('AXP_WORKFLOW_DEFINITION_LOADER');
419
+
420
+ // ============================================
421
+ // WORKFLOW INSTANCE v3.0.0 TYPES
422
+ // Based on Elsa Workflow Instance schema: https://elsaworkflows.io/schemas/workflow-instance/v3.0.0/schema.json
423
+ // Compatible with Elsa backend while using ACoreX naming conventions
424
+ // ============================================
425
+
426
+ const AXP_ACTIVITY_PROVIDER = new InjectionToken('AXP_ACTIVITY_PROVIDER', {
427
+ factory: () => [],
428
+ });
429
+ const AXP_ACTIVITY_CATEGORY_PROVIDER = new InjectionToken('AXP_ACTIVITY_CATEGORY_PROVIDER', {
430
+ factory: () => [],
431
+ });
432
+
433
+ //#region ---- Imports ----
434
+ //#endregion
424
435
  /**
425
- * Resolver service for workflow definitions.
426
- * Aggregates all registered loaders and resolves workflow definitions.
436
+ * Optimized Activity Definition Service
437
+ *
438
+ * Manages activity definitions (metadata) for UI and tooling.
439
+ * Similar to AXPReportDefinitionService - only handles metadata, not execution.
440
+ *
441
+ * Performance optimizations:
442
+ * 1. Uses childrenCount to determine if category has children (no query needed)
443
+ * 2. Uses itemsCount to determine if category has activities (no query needed)
444
+ * 3. Aggressive caching prevents duplicate API calls
445
+ * 4. Single pending request per resource prevents race conditions
446
+ * 5. Lazy loading - only loads data when needed
427
447
  */
428
- class AXPWorkflowDefinitionResolver {
448
+ class AXPActivityDefinitionService {
429
449
  constructor() {
430
- this.loaders = inject(AXP_WORKFLOW_DEFINITION_LOADER, { optional: true });
450
+ //#region ---- Providers & Caches ----
451
+ this.categoryProviders = inject(AXP_ACTIVITY_CATEGORY_PROVIDER, { optional: true }) || [];
452
+ this.activityProviders = inject(AXP_ACTIVITY_PROVIDER, { optional: true }) || [];
453
+ //#endregion
454
+ //#region ---- Cache Storage ----
455
+ /** Cache for categories by id - O(1) lookup */
456
+ this.categoriesById = new Map();
457
+ /** Cache for categories by parentId - O(1) lookup */
458
+ this.categoriesByParentId = new Map();
459
+ /** Cache for activity definitions by categoryId - O(1) lookup */
460
+ this.activitiesByCategory = new Map();
461
+ /** Cache for individual activity definitions by name - O(1) lookup */
462
+ this.activitiesByName = new Map();
463
+ /** Track which provider index owns each category (by category ID) */
464
+ this.categoryOwnership = new Map(); // Maps categoryId → provider index
465
+ /** Pending API requests to prevent duplicate calls */
466
+ this.pendingCategoriesRequests = new Map();
467
+ this.pendingActivitiesRequests = new Map();
468
+ this.pendingActivityRequests = new Map();
469
+ }
470
+ //#endregion
471
+ //#region ---- Initialization ----
472
+ //#endregion
473
+ //#region ---- Public API: Categories ----
474
+ /**
475
+ * Get categories by parentId with aggressive caching
476
+ *
477
+ * Optimization: Returns cached result immediately if available,
478
+ * preventing unnecessary API calls during navigation
479
+ *
480
+ * @param parentId - Parent category ID (undefined = root categories)
481
+ * @returns Array of categories with count metadata (childrenCount, itemsCount)
482
+ */
483
+ async getCategories(parentId) {
484
+ // ✅ Fast path: Return cached result
485
+ if (this.categoriesByParentId.has(parentId)) {
486
+ return this.categoriesByParentId.get(parentId);
487
+ }
488
+ // ✅ Prevent duplicate requests: Return pending promise
489
+ if (this.pendingCategoriesRequests.has(parentId)) {
490
+ return this.pendingCategoriesRequests.get(parentId);
491
+ }
492
+ // ✅ Create single request and cache it
493
+ const requestPromise = this.loadCategoriesFromProviders(parentId);
494
+ this.pendingCategoriesRequests.set(parentId, requestPromise);
495
+ return requestPromise;
431
496
  }
432
497
  /**
433
- * Get workflow definition by name (unique key).
434
- * Tries all registered loaders until one returns a definition.
435
- * @param name - The workflow name (unique key)
436
- * @returns Workflow definition or null if not found
498
+ * Get single category by ID with O(1) lookup
499
+ *
500
+ * Optimization: Uses Map for instant retrieval, falls back to
501
+ * searching cache, then providers if not found
437
502
  */
438
- async get(name) {
439
- const loaderArray = Array.isArray(this.loaders) ? this.loaders : this.loaders ? [this.loaders] : [];
440
- for (const loader of loaderArray) {
441
- try {
442
- const definition = await loader.get(name);
443
- if (definition) {
444
- return definition;
445
- }
503
+ async getCategoryById(categoryId) {
504
+ // Fast path: O(1) lookup in cache
505
+ if (this.categoriesById.has(categoryId)) {
506
+ return this.categoriesById.get(categoryId);
507
+ }
508
+ // Search in cached parent-child lists
509
+ for (const categories of this.categoriesByParentId.values()) {
510
+ const found = categories.find(cat => cat.id === categoryId);
511
+ if (found) {
512
+ this.categoriesById.set(categoryId, found);
513
+ return found;
446
514
  }
447
- catch (error) {
448
- console.warn(`[AXPWorkflowDefinitionResolver] Loader failed for ${name}:`, error);
515
+ }
516
+ // Load root categories if not loaded
517
+ if (!this.categoriesByParentId.has(undefined)) {
518
+ await this.getCategories();
519
+ if (this.categoriesById.has(categoryId)) {
520
+ return this.categoriesById.get(categoryId);
449
521
  }
450
522
  }
451
- return null;
523
+ // ✅ Breadth-first search through hierarchy
524
+ return this.searchCategoryInHierarchy(categoryId);
452
525
  }
453
526
  /**
454
- * Get all available workflow names from all loaders.
455
- * @returns Array of unique workflow names
527
+ * Get category path from root to specified category
528
+ *
529
+ * Optimization: Builds path using cached categories only
456
530
  */
457
- async getAllNames() {
458
- const loaderArray = Array.isArray(this.loaders) ? this.loaders : this.loaders ? [this.loaders] : [];
459
- const allNames = new Set();
460
- for (const loader of loaderArray) {
461
- if (loader.getAllNames) {
462
- try {
463
- const names = await loader.getAllNames();
464
- names.forEach((name) => allNames.add(name));
465
- }
466
- catch (error) {
467
- console.warn('[AXPWorkflowDefinitionResolver] Failed to get names from loader:', error);
468
- }
531
+ async getCategoriesPathById(categoryId) {
532
+ const path = [];
533
+ let currentCategoryId = categoryId;
534
+ while (currentCategoryId) {
535
+ const category = await this.getCategoryById(currentCategoryId);
536
+ if (!category) {
537
+ throw new Error(`Category '${currentCategoryId}' not found`);
469
538
  }
539
+ path.unshift(category);
540
+ currentCategoryId = category.parentId;
470
541
  }
471
- return Array.from(allNames);
472
- }
473
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionResolver, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
474
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionResolver, providedIn: 'root' }); }
475
- }
476
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionResolver, decorators: [{
477
- type: Injectable,
478
- args: [{ providedIn: 'root' }]
479
- }] });
480
-
481
- /**
482
- * Registry service for workflow definitions.
483
- * Caches loaded definitions and provides change notifications.
484
- */
485
- class AXPWorkflowDefinitionRegistryService {
486
- constructor() {
487
- this.resolver = inject(AXPWorkflowDefinitionResolver);
488
- this.cache = new Map();
489
- this.onChanged = new Subject();
542
+ return path;
490
543
  }
544
+ //#endregion
545
+ //#region ---- Public API: Activity Definitions ----
491
546
  /**
492
- * Observable for workflow definition changes.
547
+ * Get activity definitions for a category with smart caching
548
+ *
549
+ * Optimization: Checks itemsCount before querying
550
+ * - If itemsCount = 0, returns empty array (no API call)
551
+ * - If itemsCount > 0, loads and caches activity definitions
552
+ * - Returns cached result on subsequent calls
553
+ *
554
+ * @param categoryId - Category ID to get activity definitions from
555
+ * @returns Array of activity definitions
493
556
  */
494
- get onChanged$() {
495
- return this.onChanged.asObservable();
557
+ async getActivitiesByCategoryId(categoryId) {
558
+ // ✅ Fast path: Return cached result
559
+ if (this.activitiesByCategory.has(categoryId)) {
560
+ return this.activitiesByCategory.get(categoryId);
561
+ }
562
+ // ✅ Smart optimization: Check itemsCount before querying
563
+ const category = await this.getCategoryById(categoryId);
564
+ if (category && category.itemsCount !== undefined && category.itemsCount === 0) {
565
+ // Category has no activities - cache empty array and skip API call
566
+ const emptyArray = [];
567
+ this.activitiesByCategory.set(categoryId, emptyArray);
568
+ return emptyArray;
569
+ }
570
+ // ✅ Prevent duplicate requests
571
+ if (this.pendingActivitiesRequests.has(categoryId)) {
572
+ return this.pendingActivitiesRequests.get(categoryId);
573
+ }
574
+ // ✅ Load from providers
575
+ const requestPromise = this.loadActivitiesFromProviders(categoryId);
576
+ this.pendingActivitiesRequests.set(categoryId, requestPromise);
577
+ return requestPromise;
496
578
  }
497
579
  /**
498
- * Get workflow definition by name (unique key).
499
- * Uses cache if available, otherwise loads from resolver.
500
- * @param name - The workflow name (unique key)
501
- * @returns Workflow definition or null if not found
580
+ * Get single activity definition by name with O(1) lookup
581
+ *
582
+ * Optimization: Uses Map for instant retrieval
583
+ *
584
+ * @param name - Activity name (unique identifier and command key)
585
+ * @returns Activity definition or undefined if not found
502
586
  */
503
- async get(name) {
504
- // Check cache first
505
- if (this.cache.has(name)) {
506
- return this.cache.get(name);
587
+ async getActivityByName(name) {
588
+ // Fast path: O(1) lookup in cache
589
+ if (this.activitiesByName.has(name)) {
590
+ return this.activitiesByName.get(name);
507
591
  }
508
- // Load from resolver
509
- const definition = await this.resolver.get(name);
510
- if (definition) {
511
- this.cache.set(name, definition);
512
- this.onChanged.next({ name, action: 'registered' });
592
+ // Prevent duplicate requests
593
+ if (this.pendingActivityRequests.has(name)) {
594
+ return this.pendingActivityRequests.get(name);
513
595
  }
514
- return definition;
515
- }
516
- /**
517
- * Register a workflow definition in the cache.
518
- * @param definition - The workflow definition to register
519
- */
520
- register(definition) {
521
- this.cache.set(definition.name, definition);
522
- this.onChanged.next({ name: definition.name, action: 'registered' });
596
+ // ✅ Load from providers
597
+ const requestPromise = this.loadActivityFromProviders(name);
598
+ this.pendingActivityRequests.set(name, requestPromise);
599
+ return requestPromise;
523
600
  }
524
601
  /**
525
- * Update a workflow definition in the cache.
526
- * @param definition - The updated workflow definition
602
+ * Get category ID containing a specific activity definition
603
+ *
604
+ * Optimization: Searches cache first, loads on-demand if needed
527
605
  */
528
- update(definition) {
529
- if (this.cache.has(definition.name)) {
530
- this.cache.set(definition.name, definition);
531
- this.onChanged.next({ name: definition.name, action: 'updated' });
606
+ async getCategoryIdByActivityName(activityName) {
607
+ // Search in cached activity definitions
608
+ for (const [categoryId, definitions] of this.activitiesByCategory.entries()) {
609
+ if (definitions.some(def => def.name === activityName)) {
610
+ return categoryId;
611
+ }
612
+ }
613
+ // ✅ Try loading the activity definition to find its category
614
+ const definition = await this.getActivityByName(activityName);
615
+ if (definition && definition.category) {
616
+ // Try to find category by name/id
617
+ const categories = await this.getCategories();
618
+ const found = categories.find(cat => cat.id === definition.category || cat.title === definition.category);
619
+ if (found) {
620
+ return found.id;
621
+ }
532
622
  }
623
+ return undefined;
533
624
  }
534
625
  /**
535
- * Remove a workflow definition from the cache.
536
- * @param name - The workflow name to remove
626
+ * Get category path for an activity
537
627
  */
538
- remove(name) {
539
- if (this.cache.has(name)) {
540
- this.cache.delete(name);
541
- this.onChanged.next({ name, action: 'removed' });
628
+ async getCategoriesPathByActivityName(activityName) {
629
+ const categoryId = await this.getCategoryIdByActivityName(activityName);
630
+ if (!categoryId) {
631
+ throw new Error(`Activity '${activityName}' not found in any category`);
542
632
  }
633
+ return this.getCategoriesPathById(categoryId);
543
634
  }
635
+ //#endregion
636
+ //#region ---- Private: Data Loading ----
544
637
  /**
545
- * Clear all cached workflow definitions.
638
+ * Load categories from providers and cache results
639
+ *
640
+ * Optimization: Tracks provider ownership to avoid unnecessary API calls
641
+ * - For root (parentId = undefined): Query ALL providers
642
+ * - For children: Only query the provider that owns the parent
546
643
  */
547
- clear() {
548
- this.cache.clear();
644
+ async loadCategoriesFromProviders(parentId) {
645
+ try {
646
+ const resolvedProviders = await Promise.allSettled(this.categoryProviders);
647
+ const categories = [];
648
+ // Determine which provider(s) to query
649
+ const providerIndicesToQuery = parentId
650
+ ? this.getProviderIndexForCategory(parentId)
651
+ : null; // Root: query all providers
652
+ for (let i = 0; i < resolvedProviders.length; i++) {
653
+ const p = resolvedProviders[i];
654
+ // Skip if we have a specific provider index and this isn't it
655
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
656
+ continue;
657
+ }
658
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
659
+ try {
660
+ const cats = await p.value.getList(parentId);
661
+ if (Array.isArray(cats) && cats.length > 0) {
662
+ categories.push(...cats);
663
+ // ✅ Track ownership: This provider INDEX owns these categories
664
+ cats.forEach(cat => this.categoryOwnership.set(cat.id, i));
665
+ }
666
+ }
667
+ catch {
668
+ // Continue on error - try other providers
669
+ }
670
+ }
671
+ }
672
+ // ✅ Cache results for fast subsequent access
673
+ this.categoriesByParentId.set(parentId, categories);
674
+ categories.forEach(cat => this.categoriesById.set(cat.id, cat));
675
+ return categories;
676
+ }
677
+ finally {
678
+ this.pendingCategoriesRequests.delete(parentId);
679
+ }
549
680
  }
550
681
  /**
551
- * Check if a workflow definition is cached.
552
- * @param definitionId - The workflow definition ID
553
- * @returns True if cached, false otherwise
682
+ * Get the provider index that owns a specific category
683
+ *
684
+ * @returns Array with provider index, or null if ownership unknown (query all)
554
685
  */
555
- has(definitionId) {
556
- return this.cache.has(definitionId);
686
+ getProviderIndexForCategory(categoryId) {
687
+ const ownerIndex = this.categoryOwnership.get(categoryId);
688
+ if (ownerIndex !== undefined) {
689
+ return [ownerIndex];
690
+ }
691
+ // Ownership unknown - will query all providers (fallback)
692
+ return null;
557
693
  }
558
694
  /**
559
- * Get all cached workflow definition IDs.
560
- * @returns Array of definition IDs (only those that have been loaded)
695
+ * Load activity definitions from providers and cache results
696
+ *
697
+ * Optimization: Only queries the provider that owns the category
698
+ * Uses provider INDEX to match category provider with activity provider
561
699
  */
562
- getAllIds() {
563
- return Array.from(this.cache.keys());
564
- }
565
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
566
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionRegistryService, providedIn: 'root' }); }
567
- }
568
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionRegistryService, decorators: [{
569
- type: Injectable,
570
- args: [{
571
- providedIn: 'root',
572
- }]
573
- }] });
574
-
575
- // ============================================
576
- // WORKFLOW INSTANCE v3.0.0 TYPES
577
- // Based on Elsa Workflow Instance schema: https://elsaworkflows.io/schemas/workflow-instance/v3.0.0/schema.json
578
- // Compatible with Elsa backend while using ACoreX naming conventions
579
- // ============================================
580
-
581
- /**
582
- * Base abstract class for activities.
583
- * Extend this to create custom activities.
584
- */
585
- class Activity {
586
- constructor(type, name) {
587
- this.type = type;
588
- this.name = name;
700
+ async loadActivitiesFromProviders(categoryId) {
701
+ try {
702
+ const resolvedProviders = await Promise.allSettled(this.activityProviders);
703
+ const definitions = [];
704
+ // Smart routing: Get provider INDEX that owns this category
705
+ const ownerIndex = this.categoryOwnership.get(categoryId);
706
+ const providerIndicesToQuery = ownerIndex !== undefined ? [ownerIndex] : null;
707
+ for (let i = 0; i < resolvedProviders.length; i++) {
708
+ const p = resolvedProviders[i];
709
+ // Skip if we have a specific provider index and this isn't it
710
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
711
+ continue;
712
+ }
713
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
714
+ try {
715
+ const defs = await p.value.getList(categoryId);
716
+ if (Array.isArray(defs)) {
717
+ definitions.push(...defs);
718
+ }
719
+ }
720
+ catch {
721
+ // Continue on error - try other providers
722
+ }
723
+ }
724
+ }
725
+ // Cache results for fast subsequent access
726
+ this.activitiesByCategory.set(categoryId, definitions);
727
+ definitions.forEach(def => {
728
+ if (def.name) {
729
+ this.activitiesByName.set(def.name, def);
730
+ }
731
+ });
732
+ return definitions;
733
+ }
734
+ finally {
735
+ this.pendingActivitiesRequests.delete(categoryId);
736
+ }
589
737
  }
590
738
  /**
591
- * Helper method that returns Done outcome by default.
739
+ * Load single activity definition from providers and cache result
592
740
  */
593
- createResult(output, outcome = 'Done') {
594
- return {
595
- success: true,
596
- data: {
597
- output,
598
- outcomes: { [outcome]: true },
599
- },
600
- };
601
- }
602
- }
603
- /**
604
- * Activity registry for registering and creating activities.
605
- */
606
- class ActivityRegistry {
607
- constructor() {
608
- this.registry = new Map();
609
- this.descriptors = new Map();
741
+ async loadActivityFromProviders(name) {
742
+ try {
743
+ const resolvedProviders = await Promise.allSettled(this.activityProviders);
744
+ // Try providers first
745
+ for (const p of resolvedProviders) {
746
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getById === 'function') {
747
+ try {
748
+ const result = await p.value.getById(name);
749
+ if (result) {
750
+ this.activitiesByName.set(name, result);
751
+ return result;
752
+ }
753
+ }
754
+ catch {
755
+ // Continue on error
756
+ }
757
+ }
758
+ }
759
+ // Fallback: Search in cached activity definitions
760
+ for (const definitions of this.activitiesByCategory.values()) {
761
+ const found = definitions.find(def => def.name === name);
762
+ if (found) {
763
+ this.activitiesByName.set(name, found);
764
+ return found;
765
+ }
766
+ }
767
+ return undefined;
768
+ }
769
+ finally {
770
+ this.pendingActivityRequests.delete(name);
771
+ }
610
772
  }
611
773
  /**
612
- * Register an activity type.
774
+ * Breadth-first search through category hierarchy
613
775
  */
614
- register(type, factory, descriptor) {
615
- this.registry.set(type, factory);
616
- this.descriptors.set(type, descriptor);
776
+ async searchCategoryInHierarchy(categoryId) {
777
+ const searchQueue = [undefined];
778
+ const searched = new Set();
779
+ while (searchQueue.length > 0) {
780
+ const parentId = searchQueue.shift();
781
+ if (searched.has(parentId))
782
+ continue;
783
+ searched.add(parentId);
784
+ const categories = await this.getCategories(parentId);
785
+ const found = categories.find(cat => cat.id === categoryId);
786
+ if (found) {
787
+ return found;
788
+ }
789
+ // ✅ Optimization: Only search children if childrenCount > 0
790
+ for (const category of categories) {
791
+ if (category.childrenCount > 0 && !searched.has(category.id)) {
792
+ searchQueue.push(category.id);
793
+ }
794
+ }
795
+ }
796
+ return undefined;
617
797
  }
798
+ //#endregion
799
+ //#region ---- Cache Management ----
618
800
  /**
619
- * Create an activity instance.
801
+ * Check if category has children (uses cached count)
620
802
  */
621
- create(type) {
622
- const factory = this.registry.get(type);
623
- if (!factory) {
624
- throw new Error(`Unknown activity type: ${type}`);
625
- }
626
- return factory();
803
+ categoryHasChildren(categoryId) {
804
+ const category = this.categoriesById.get(categoryId);
805
+ return category ? category.childrenCount > 0 : false;
627
806
  }
628
807
  /**
629
- * Get activity descriptor.
808
+ * Check if category has activities (uses cached count)
630
809
  */
631
- getDescriptor(type) {
632
- return this.descriptors.get(type);
810
+ categoryHasActivities(categoryId) {
811
+ const category = this.categoriesById.get(categoryId);
812
+ return category ? (category.itemsCount ?? 0) > 0 : false;
633
813
  }
634
814
  /**
635
- * Get all registered types.
815
+ * Clear all caches
636
816
  */
637
- getTypes() {
638
- return Array.from(this.registry.keys());
817
+ clearAllCache() {
818
+ this.categoriesById.clear();
819
+ this.categoriesByParentId.clear();
820
+ this.activitiesByCategory.clear();
821
+ this.activitiesByName.clear();
822
+ this.categoryOwnership.clear();
823
+ this.pendingCategoriesRequests.clear();
824
+ this.pendingActivitiesRequests.clear();
825
+ this.pendingActivityRequests.clear();
639
826
  }
640
827
  /**
641
- * Get all descriptors.
828
+ * Clear categories cache only
642
829
  */
643
- getAllDescriptors() {
644
- return Array.from(this.descriptors.values());
830
+ clearCategoriesCache() {
831
+ this.categoriesById.clear();
832
+ this.categoriesByParentId.clear();
833
+ this.categoryOwnership.clear();
834
+ this.pendingCategoriesRequests.clear();
645
835
  }
646
836
  /**
647
- * Get descriptors by category.
837
+ * Clear activities cache only
648
838
  */
649
- getDescriptorsByCategory(category) {
650
- return this.getAllDescriptors().filter(d => d.category === category);
651
- }
839
+ clearActivitiesCache() {
840
+ this.activitiesByCategory.clear();
841
+ this.activitiesByName.clear();
842
+ this.pendingActivitiesRequests.clear();
843
+ this.pendingActivityRequests.clear();
844
+ }
845
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
846
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityDefinitionService, providedIn: 'root' }); }
652
847
  }
848
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityDefinitionService, decorators: [{
849
+ type: Injectable,
850
+ args: [{
851
+ providedIn: 'root',
852
+ }]
853
+ }] });
653
854
 
654
855
  /**
655
- * Injection token for activity providers.
856
+ * Injection token for workflow engine.
857
+ * Default implementation is AXPWorkflowLocalEngine.
656
858
  */
657
- const AXP_ACTIVITY_PROVIDER = new InjectionToken('AXP_ACTIVITY_PROVIDER');
859
+ const AXP_WORKFLOW_ENGINE = new InjectionToken('AXP_WORKFLOW_ENGINE');
860
+
861
+ //#endregion
658
862
  /**
659
- * Activity Provider Service.
660
- * Collects all activity providers and manages activity registration.
863
+ * Service responsible for executing frontend workflow tasks.
864
+ *
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.
868
+ *
869
+ * This service is replaceable per platform and contains all execution logic.
661
870
  */
662
- class AXPActivityProviderService {
871
+ class FrontendTaskExecutor {
663
872
  constructor() {
664
- this.activityDescriptors = new Map();
665
- this.categories = [];
666
- this.providers = [];
667
- this.categoryProviders = [];
668
- this.initialized = false;
873
+ //#region ---- Services & Dependencies ----
669
874
  this.commandService = inject(AXPCommandService);
670
- this.commandRegistry = inject(AXPCommandRegistry);
671
- }
672
- /**
673
- * Register an activity provider.
674
- */
675
- registerProvider(provider) {
676
- this.providers.push(provider);
677
- if (this.initialized) {
678
- this.initializeProvider(provider);
679
- }
680
- }
681
- /**
682
- * Register a category provider.
683
- */
684
- registerCategoryProvider(provider) {
685
- this.categoryProviders.push(provider);
686
- if (this.initialized) {
687
- this.initializeCategoryProvider(provider);
688
- }
689
875
  }
876
+ //#endregion
877
+ //#region ---- Public Methods ----
690
878
  /**
691
- * Initialize all providers.
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
692
888
  */
693
- async initialize() {
694
- if (this.initialized)
695
- return;
696
- // Initialize category providers first
697
- for (const provider of this.categoryProviders) {
698
- await this.initializeCategoryProvider(provider);
699
- }
700
- // Initialize activity providers
701
- for (const provider of this.providers) {
702
- await this.initializeProvider(provider);
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.`);
703
894
  }
704
- this.initialized = true;
705
- }
706
- /**
707
- * Get activity descriptor by key.
708
- */
709
- getDescriptor(key) {
710
- return this.activityDescriptors.get(key);
711
- }
712
- /**
713
- * Get all activity descriptors.
714
- */
715
- getAllDescriptors() {
716
- return Array.from(this.activityDescriptors.values());
717
- }
718
- /**
719
- * Get descriptors by category.
720
- */
721
- getDescriptorsByCategory(category) {
722
- return this.getAllDescriptors().filter(d => d.category === category);
723
- }
724
- /**
725
- * Get all categories.
726
- */
727
- getCategories() {
728
- return [...this.categories].sort((a, b) => (a.order || 999) - (b.order || 999));
729
- }
730
- /**
731
- * Create an activity instance by key using AXPCommandService.
732
- */
733
- async createActivity(key) {
734
895
  try {
735
896
  // Check if command exists
736
- if (!this.commandService.exists(key)) {
737
- return null;
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
+ };
738
929
  }
739
- // Get command loader from registry
740
- const loader = this.commandRegistry.getLoader(key);
741
- if (!loader) {
742
- return null;
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';
743
937
  }
744
- // Create command instance
745
- const command = await loader();
746
- return command;
938
+ return {
939
+ output: commandResult?.output ?? null,
940
+ outcome,
941
+ };
747
942
  }
748
943
  catch (error) {
749
- return null;
944
+ console.error(`[FrontendTaskExecutor] ❌ Error executing frontend activity '${task.activityType}':`, error);
945
+ return {
946
+ output: { error: error.message || 'Unknown error' },
947
+ outcome: 'Failed'
948
+ };
750
949
  }
751
950
  }
752
- // ============================================
753
- // PRIVATE METHODS
754
- // ============================================
755
- async initializeProvider(provider) {
756
- const context = {
757
- registerActivity: (config) => {
758
- this.activityDescriptors.set(config.key, config.descriptor);
759
- }
760
- };
761
- await provider.provide(context);
762
- }
763
- async initializeCategoryProvider(provider) {
764
- const context = {
765
- registerCategories: (categories) => {
766
- this.categories.push(...categories);
767
- }
768
- };
769
- await provider.provide(context);
770
- }
771
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
772
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityProviderService, providedIn: 'root' }); }
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' }); }
773
953
  }
774
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityProviderService, decorators: [{
954
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: FrontendTaskExecutor, decorators: [{
775
955
  type: Injectable,
776
956
  args: [{
777
957
  providedIn: 'root'
778
958
  }]
779
959
  }] });
780
960
 
781
- /**
782
- * Injection token for activity category providers.
783
- */
784
- const AXP_ACTIVITY_CATEGORY_PROVIDER = new InjectionToken('AXP_ACTIVITY_CATEGORY_PROVIDER');
785
- class AXPActivityCategoryProviderService {
786
- constructor(parent, providers = []) {
787
- this.parent = parent;
788
- this.providers = providers;
789
- this.categories = new Map();
790
- this.isInitialized = false;
791
- if (!parent) {
792
- this.initialize();
793
- }
794
- }
795
- async initialize() {
796
- if (this.isInitialized) {
797
- return;
798
- }
799
- for (const provider of this.providers) {
800
- await provider.provide({
801
- registerCategories: (categories) => {
802
- categories.forEach(cat => this.categories.set(cat.name, cat));
803
- }
804
- });
805
- }
806
- this.isInitialized = true;
807
- }
808
- getAllCategories() {
809
- return Array.from(this.categories.values()).sort((a, b) => (a.order || 0) - (b.order || 0));
810
- }
811
- getCategory(name) {
812
- return this.categories.get(name);
813
- }
814
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityCategoryProviderService, deps: [{ token: AXPActivityCategoryProviderService, optional: true, skipSelf: true }, { token: AXP_ACTIVITY_CATEGORY_PROVIDER, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
815
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityCategoryProviderService, providedIn: 'root' }); }
816
- }
817
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityCategoryProviderService, decorators: [{
818
- type: Injectable,
819
- args: [{ providedIn: 'root' }]
820
- }], ctorParameters: () => [{ type: AXPActivityCategoryProviderService, decorators: [{
821
- type: Optional
822
- }, {
823
- type: SkipSelf
824
- }] }, { type: undefined, decorators: [{
825
- type: Optional
826
- }, {
827
- type: Inject,
828
- args: [AXP_ACTIVITY_CATEGORY_PROVIDER]
829
- }] }] });
830
-
831
- /**
832
- * Abstract service for workflow execution operations.
833
- *
834
- * This service handles communication with backend for workflow execution.
835
- *
836
- * Implementation should be provided in connectivity layer:
837
- * - Mock implementation: @acorex/connectivity/mock
838
- * - API implementation: @acorex/connectivity/api
839
- *
840
- * @example
841
- * ```typescript
842
- * // In connectivity/mock
843
- * @Injectable()
844
- * export class AXCWorkflowExecutionService implements AXPWorkflowExecutionService {
845
- * async startExecution(request: AXPStartWorkflowExecutionRequest): Promise<AXPStartWorkflowExecutionResponse> {
846
- * // Mock implementation
847
- * }
848
- * }
849
- *
850
- * // In connectivity/api
851
- * @Injectable()
852
- * export class AXCWorkflowExecutionService implements AXPWorkflowExecutionService {
853
- * constructor(private http: HttpClient) {}
854
- *
855
- * async startExecution(request: AXPStartWorkflowExecutionRequest): Promise<AXPStartWorkflowExecutionResponse> {
856
- * return firstValueFrom(
857
- * this.http.post<AXPStartWorkflowExecutionResponse>(
858
- * `${this.config.baseUrl}/api/workflows/${request.workflowId}/start`,
859
- * { input: request.input }
860
- * )
861
- * );
862
- * }
863
- * }
864
- * ```
865
- */
866
- class AXPWorkflowExecutionService {
867
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowExecutionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
868
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowExecutionService }); }
869
- }
870
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowExecutionService, decorators: [{
871
- type: Injectable
872
- }] });
873
-
874
961
  //#endregion
875
962
  /**
876
- * Production Workflow Coordinator.
963
+ * Workflow Manager - Facade for workflow lifecycle orchestration.
964
+ *
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.
877
967
  *
878
- * Separates frontend/backend execution:
879
- * - Frontend activities: Execute with AXPCommand in browser
880
- * - Backend activities: Execute via API calls to backend
881
- * - State caching: Caches workflow state in client for performance
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
882
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)
883
980
  */
884
- class WorkflowCoordinator {
981
+ class AXPWorkflowManager {
885
982
  constructor() {
886
983
  //#region ---- Services & Dependencies ----
887
- this.workflowExecutionService = inject(AXPWorkflowExecutionService);
888
- this.commandService = inject(AXPCommandService);
984
+ this.workflowEngine = inject(AXP_WORKFLOW_ENGINE);
985
+ this.frontendTaskExecutor = inject(FrontendTaskExecutor);
889
986
  //#endregion
890
- //#region ---- State Cache (Client-side for Performance) ----
987
+ //#region ---- State Cache ----
891
988
  /**
892
989
  * Cache workflow states in memory for quick access.
893
- * Key: executionId
894
- * Value: AXPWorkflowExecutionState
990
+ * Key: instanceId
991
+ * Value: AXPWorkflowInstanceState
895
992
  */
896
993
  this.stateCache = new Map();
994
+ /**
995
+ * Cache TTL in milliseconds (5 minutes).
996
+ */
997
+ this.CACHE_TTL = 5 * 60 * 1000;
897
998
  }
898
999
  //#endregion
899
1000
  //#region ---- Public Methods ----
900
1001
  /**
901
- * Start workflow execution in backend.
1002
+ * Start a new workflow instance.
902
1003
  *
1004
+ * Creates a new workflow instance in backend and returns instance ID.
903
1005
  * Backend decides what to do: returns pendingTask or indicates completion.
904
- * Frontend only calls API - no business logic here.
905
1006
  *
906
- * @param workflowId - Workflow ID
907
- * @param input - Initial input data
908
- * @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
909
1010
  *
910
1011
  * @example
911
1012
  * ```typescript
912
- * const result = await coordinator.startWorkflow('my-workflow', { userId: '123' });
1013
+ * const result = await workflowManager.start('my-workflow', { userId: '123' });
913
1014
  *
914
- * if (result.pendingTask) {
915
- * // Execute task if frontend, or wait for backend to complete
916
- * if (result.pendingTask.executionMode === 'frontend') {
917
- * await coordinator.executeTask(result.pendingTask);
918
- * 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);
919
1020
  * }
920
1021
  * }
921
1022
  * ```
922
1023
  */
923
- async startWorkflow(workflowId, input = {}) {
1024
+ async start(workflowId, input = {}) {
924
1025
  try {
925
- const execution = await this.startWorkflowExecution(workflowId, input);
926
- 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 {
927
1037
  success: true,
928
- output: execution.state.output,
929
- nextTask: execution.pendingTask || null,
930
- executionId: execution.executionId,
931
- state: execution.state
1038
+ instanceId: response.instanceId,
1039
+ state: startNormalizedState,
1040
+ nextTask: response.pendingTask || null,
1041
+ output: startNormalizedState.output
932
1042
  };
933
- return result;
934
1043
  }
935
1044
  catch (error) {
936
- console.error('[WorkflowCoordinator] ❌ Error in startWorkflow', error);
1045
+ console.error('[AXPWorkflowManager] ❌ Error starting workflow:', error);
937
1046
  return {
938
1047
  success: false,
939
- error: error.message || 'Failed to start workflow',
940
- nextTask: null
1048
+ error: error.message || 'Failed to start workflow'
941
1049
  };
942
1050
  }
943
1051
  }
944
1052
  /**
945
- * Execute a frontend task using AXPCommand.
1053
+ * Execute a frontend task.
946
1054
  *
947
- * Only executes if task.executionMode is 'frontend' or 'both'.
948
- * Backend tasks are handled automatically by backend.
1055
+ * Delegates to FrontendTaskExecutor for actual execution.
1056
+ * Only executes tasks with executionMode 'frontend' or 'both'.
949
1057
  *
950
1058
  * @param task - Task to execute
951
1059
  * @returns Execution result with output and outcome
1060
+ *
1061
+ * @throws Error if task is not a frontend task
952
1062
  */
953
- async executeTask(task) {
954
- // Only execute frontend tasks
955
- if (task.executionMode !== 'frontend' && task.executionMode !== 'both') {
956
- throw new Error(`Task '${task.activityId}' is not a frontend task. Backend handles it automatically.`);
957
- }
958
- return await this.executeFrontendActivity(task);
1063
+ async execute(task) {
1064
+ return this.frontendTaskExecutor.execute(task);
959
1065
  }
960
1066
  /**
961
1067
  * Complete a task and get next task from backend.
@@ -963,72 +1069,71 @@ class WorkflowCoordinator {
963
1069
  * Sends task result to backend API.
964
1070
  * Backend decides: next task, fail, or complete workflow.
965
1071
  *
966
- * @param executionId - Execution ID
1072
+ * @param instanceId - Workflow instance ID
967
1073
  * @param task - Completed task
968
1074
  * @param outcome - Task outcome (e.g., 'Done', 'Confirmed', 'Cancelled')
969
- * @param output - Task output/result
970
- * @returns Next task from backend (if any)
1075
+ * @param output - Task output/result (optional)
1076
+ * @returns Complete result with next task (if any)
971
1077
  */
972
- async completeTask(executionId, task, outcome, output) {
1078
+ async complete(instanceId, task, outcome, output) {
973
1079
  try {
974
1080
  // Send result to backend - backend decides next step
975
- const response = await this.workflowExecutionService.resumeExecution({
976
- executionId,
1081
+ const response = await this.workflowEngine.resume({
1082
+ instanceId,
977
1083
  stepId: task.activityId,
978
1084
  taskToken: task.taskToken,
979
1085
  outcome,
980
1086
  userInput: output
981
1087
  });
982
1088
  // Update cache
1089
+ let completeNormalizedState = response.state;
983
1090
  if (response.state) {
984
- 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);
985
1097
  }
986
1098
  return {
987
1099
  success: true,
988
- output: response.output,
1100
+ instanceId,
1101
+ state: completeNormalizedState,
989
1102
  nextTask: response.nextTask || null,
990
- executionId,
991
- state: response.state
1103
+ output: response.output
992
1104
  };
993
1105
  }
994
1106
  catch (error) {
1107
+ console.error('[AXPWorkflowManager] ❌ Error completing task:', error);
995
1108
  return {
996
1109
  success: false,
997
- error: error.message || 'Failed to complete task',
998
- nextTask: null
1110
+ instanceId,
1111
+ error: error.message || 'Failed to complete task'
999
1112
  };
1000
1113
  }
1001
1114
  }
1002
1115
  /**
1003
- * Execute workflow by ID (backward compatibility).
1004
- *
1005
- * @deprecated Use startWorkflow + executeTask + completeTask pattern instead.
1006
- * This method is kept for backward compatibility but will be removed.
1007
- */
1008
- async executeWorkflowById(workflowId, input = {}) {
1009
- // Just start workflow - caller should handle task execution
1010
- return await this.startWorkflow(workflowId, input);
1011
- }
1012
- /**
1013
- * Resume a suspended workflow (e.g., after user interaction).
1116
+ * Resume a suspended workflow (e.g., after user interaction).
1014
1117
  *
1015
1118
  * Backend determines nextStep based on outcome and outcomeConnections.
1016
- * Client only provides executionId, stepId, outcome, and optional userInput.
1119
+ * Client only provides instanceId, stepId, outcome, and optional userInput.
1017
1120
  *
1018
- * @param executionId - Workflow execution ID
1121
+ * @param instanceId - Workflow instance ID
1019
1122
  * @param stepId - Step ID that was waiting for user input
1020
1123
  * @param outcome - User action outcome (e.g., 'Confirmed', 'Cancelled', 'Submitted')
1021
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)
1022
1127
  */
1023
- async resumeWorkflow(executionId, stepId, outcome, userInput, taskToken) {
1128
+ async resume(instanceId, stepId, outcome, userInput, taskToken) {
1024
1129
  try {
1025
1130
  // Ensure taskToken is provided for secure resumption
1026
1131
  if (!taskToken) {
1027
- throw new Error('Missing taskToken for resumeWorkflow');
1132
+ throw new Error('Missing taskToken for resume operation');
1028
1133
  }
1029
1134
  // Backend handles everything: checks outcomeConnections and determines nextStep
1030
- const response = await this.workflowExecutionService.resumeExecution({
1031
- executionId,
1135
+ const response = await this.workflowEngine.resume({
1136
+ instanceId,
1032
1137
  stepId,
1033
1138
  taskToken,
1034
1139
  outcome,
@@ -1036,1366 +1141,823 @@ class WorkflowCoordinator {
1036
1141
  });
1037
1142
  // Update cache with state from backend
1038
1143
  if (response.state) {
1039
- 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);
1040
1155
  }
1041
1156
  return {
1042
1157
  success: true,
1043
- output: response.output,
1158
+ instanceId,
1159
+ state: normalizedState || response.state,
1044
1160
  nextTask: response.nextTask || null, // Backend determines this from outcomeConnections
1045
- executionId,
1046
- state: response.state
1161
+ output: response.output
1047
1162
  };
1048
1163
  }
1049
1164
  catch (error) {
1165
+ console.error('[AXPWorkflowManager] ❌ Error resuming workflow:', error);
1050
1166
  return {
1051
1167
  success: false,
1052
- error: error.message || 'Failed to resume workflow',
1053
- nextTask: null
1168
+ instanceId,
1169
+ error: error.message || 'Failed to resume workflow'
1054
1170
  };
1055
1171
  }
1056
1172
  }
1057
1173
  /**
1058
- * 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
1059
1180
  */
1060
- async getWorkflowState(executionId) {
1181
+ async getState(instanceId) {
1061
1182
  // Check cache first
1062
- const cached = this.stateCache.get(executionId);
1183
+ const cached = this.stateCache.get(instanceId);
1063
1184
  if (cached) {
1064
- // Cache is valid for 5 minutes
1065
- const cacheAge = Date.now() - cached.lastUpdated.getTime();
1066
- if (cacheAge < 5 * 60 * 1000) {
1067
- 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;
1068
1194
  }
1069
1195
  }
1070
1196
  // Fetch from backend
1071
1197
  try {
1072
- const state = await this.workflowExecutionService.getExecutionState({
1073
- executionId
1198
+ const state = await this.workflowEngine.getState({
1199
+ instanceId
1074
1200
  });
1075
- // Update cache
1076
- this.stateCache.set(executionId, state);
1077
- return state;
1078
- }
1079
- catch {
1080
- return null;
1081
- }
1082
- }
1083
- //#endregion
1084
- //#region ---- Private Methods ----
1085
- /**
1086
- * Execute a frontend activity using CommandBus.
1087
- *
1088
- * Frontend activities are executed in the browser using AXPCommandService.
1089
- * Activities can also be executed in both frontend and backend (hybrid mode).
1090
- *
1091
- * @param task - Frontend task to execute
1092
- * @returns Execution result with output and outcome
1093
- */
1094
- async executeFrontendActivity(task) {
1095
- try {
1096
- // Check if command exists
1097
- const commandExists = this.commandService.exists(task.activityType);
1098
- if (!commandExists) {
1099
- console.warn(`[WorkflowCoordinator] ⚠️ Frontend activity '${task.activityType}' is not registered. Skipping execution.`);
1100
- return {
1101
- output: null,
1102
- outcome: 'Done'
1103
- };
1104
- }
1105
- // Execute activity via CommandBus
1106
- // Activities registered as AXPCommand return {output, outcomes}
1107
- // 🎯 Flatten properties if nested (workflow-studio format)
1108
- let commandInput = task.input || task.config || {};
1109
- if (commandInput['properties'] && typeof commandInput['properties'] === 'object') {
1110
- // Flatten: {properties: {text: "..."}} -> {text: "..."}
1111
- commandInput = { ...commandInput['properties'] };
1112
- }
1113
- const result = await this.commandService.execute(task.activityType, commandInput);
1114
- if (!result) {
1115
- return {
1116
- output: null,
1117
- outcome: 'Failed',
1118
- };
1119
- }
1120
- if (!result.success) {
1121
- return {
1122
- output: {
1123
- error: result.message?.text,
1124
- },
1125
- outcome: 'Failed',
1126
- };
1127
- }
1128
- const commandResult = result.data;
1129
- const outcomes = commandResult?.outcomes ?? {};
1130
- let outcome = 'Done';
1131
- if (Object.keys(outcomes).length > 0) {
1132
- outcome = outcomes['Done'] ? 'Done' : Object.keys(outcomes)[0] || 'Done';
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);
1133
1205
  }
1134
- return {
1135
- output: commandResult?.output ?? null,
1136
- outcome,
1137
- };
1206
+ // Update cache
1207
+ this.stateCache.set(instanceId, normalizedState);
1208
+ return normalizedState;
1138
1209
  }
1139
1210
  catch (error) {
1140
- console.error(`[WorkflowCoordinator] ❌ Error executing frontend activity '${task.activityType}':`, error);
1141
- return {
1142
- output: { error: error.message || 'Unknown error' },
1143
- outcome: 'Failed'
1144
- };
1211
+ console.error('[AXPWorkflowManager] ❌ Error getting workflow state:', error);
1212
+ return null;
1145
1213
  }
1146
1214
  }
1147
- /**
1148
- * Start workflow execution in backend.
1149
- * Backend returns executionId, initial state, and first task to execute.
1150
- */
1151
- async startWorkflowExecution(workflowId, input) {
1152
- const response = await this.workflowExecutionService.startExecution({
1153
- workflowId,
1154
- input
1155
- });
1156
- // Cache state
1157
- this.stateCache.set(response.executionId, response.state);
1158
- return {
1159
- executionId: response.executionId,
1160
- state: response.state,
1161
- pendingTask: response.pendingTask
1162
- };
1163
- }
1164
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: WorkflowCoordinator, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1165
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: WorkflowCoordinator, providedIn: 'root' }); }
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' }); }
1166
1217
  }
1167
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: WorkflowCoordinator, decorators: [{
1218
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowManager, decorators: [{
1168
1219
  type: Injectable,
1169
1220
  args: [{
1170
1221
  providedIn: 'root'
1171
1222
  }]
1172
1223
  }] });
1173
1224
 
1225
+ //#endregion
1174
1226
  /**
1175
- * Types and interfaces for Workflow Execution Service.
1176
- */
1177
-
1178
- // Production Workflow Coordinator (Frontend/Backend Separation)
1179
-
1180
- /**
1181
- * WriteLine Activity - Simple console logging activity.
1227
+ * Local engine implementation that manages workflow progression and state.
1182
1228
  *
1183
- * Usage:
1184
- * ```typescript
1185
- * const activity = new WriteLine();
1186
- * await activity.execute({ text: 'Hello World' });
1187
- * ```
1188
- */
1189
- class WriteLine extends Activity {
1190
- constructor() {
1191
- super('WriteLine');
1192
- }
1193
- async execute(input) {
1194
- const text = input.text || '';
1195
- if (text !== undefined && text !== null) {
1196
- console.log(`[WriteLine] ${text}`);
1197
- }
1198
- return this.createResult(undefined, 'Done');
1199
- }
1200
- }
1201
-
1202
- var writeLine_activity = /*#__PURE__*/Object.freeze({
1203
- __proto__: null,
1204
- WriteLine: WriteLine
1205
- });
1206
-
1207
- /**
1208
- * Sequence Activity - Executes activities in sequential order.
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.
1209
1237
  *
1210
- * Usage:
1211
- * ```typescript
1212
- * const sequence = new Sequence();
1213
- * sequence.activities = [activity1, activity2, activity3];
1214
- * await sequence.execute({});
1215
- * ```
1238
+ * This is the DEFAULT engine provider. Applications can override it with
1239
+ * an API-based engine implementation.
1216
1240
  */
1217
- class Sequence extends Activity {
1218
- constructor() {
1219
- super('Sequence');
1241
+ class AXPWorkflowLocalEngine {
1242
+ //#region ---- Services & Dependencies ----
1243
+ constructor(definitionLoaders = null) {
1244
+ this.definitionLoaders = definitionLoaders;
1245
+ //#endregion
1246
+ //#region ---- Instance Storage ----
1220
1247
  /**
1221
- * Activities to execute in sequence.
1248
+ * In-memory storage for workflow instances.
1249
+ * Key: instanceId
1250
+ * Value: LocalWorkflowState
1222
1251
  */
1223
- this.activities = [];
1224
- }
1225
- async execute(input) {
1226
- // Execute all activities in sequence
1227
- for (const activity of this.activities) {
1228
- const result = await activity.execute(input);
1229
- if (!result.success) {
1230
- return {
1231
- success: false,
1232
- message: result.message,
1233
- data: {
1234
- output: undefined,
1235
- outcomes: {
1236
- Failed: true,
1237
- },
1238
- },
1239
- };
1240
- }
1241
- }
1242
- return this.createResult(undefined, 'Done');
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();
1243
1259
  }
1244
- }
1245
-
1246
- var sequence_activity = /*#__PURE__*/Object.freeze({
1247
- __proto__: null,
1248
- Sequence: Sequence
1249
- });
1250
-
1251
- /**
1252
- * Show Confirm Dialog Activity - Displays confirmation dialog to user.
1253
- *
1254
- * Has two outcomes:
1255
- * - 'Confirmed': User clicked confirm/yes
1256
- * - 'Cancelled': User clicked cancel/no
1257
- *
1258
- * Usage:
1259
- * ```typescript
1260
- * const dialog = new ShowConfirmDialog();
1261
- * await dialog.execute({
1262
- * title: 'Confirm Delete',
1263
- * message: 'Are you sure?',
1264
- * color: 'danger'
1265
- * });
1266
- * ```
1267
- */
1268
- class ShowConfirmDialog extends Activity {
1269
1260
  //#endregion
1270
- constructor() {
1271
- super('ShowConfirmDialog');
1272
- //#region ---- Services & Dependencies ----
1273
- this.dialogService = inject(AXDialogService);
1274
- this.translationService = inject(AXTranslationService);
1275
- }
1276
- async execute(input) {
1277
- const { title = '', message = '', color = 'primary', defaultAction = 'cancel', align = 'horizontal', backdrop = false } = input;
1278
- // Translate title and message only if they start with '@' (translation key)
1279
- // Otherwise use the text as-is
1280
- const translatedTitle = title
1281
- ? (title.startsWith('@')
1282
- ? await this.translationService.translateAsync(title) || title
1283
- : title)
1284
- : '';
1285
- const translatedMessage = message
1286
- ? (message.startsWith('@')
1287
- ? await this.translationService.translateAsync(message) || message
1288
- : message)
1289
- : '';
1290
- try {
1291
- const confirmResult = await this.dialogService.confirm(translatedTitle, translatedMessage, color, align, backdrop, defaultAction);
1292
- const result = confirmResult.result;
1293
- const action = result ? 'confirm' : 'cancel';
1294
- return this.createResult({
1295
- result,
1296
- action
1297
- }, result ? 'Confirmed' : 'Cancelled');
1298
- }
1299
- catch (err) {
1300
- console.error('[ShowConfirmDialog] Error showing dialog:', err);
1301
- return {
1302
- success: false,
1303
- message: {
1304
- text: err instanceof Error ? err.message : 'Failed to show confirm dialog',
1305
- },
1306
- data: {
1307
- output: {
1308
- result: false,
1309
- action: 'error',
1310
- },
1311
- outcomes: {
1312
- Cancelled: true,
1313
- },
1314
- },
1315
- };
1316
- }
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
+ };
1317
1308
  }
1318
- }
1319
-
1320
- var showConfirmDialog_activity = /*#__PURE__*/Object.freeze({
1321
- __proto__: null,
1322
- ShowConfirmDialog: ShowConfirmDialog
1323
- });
1324
-
1325
- /**
1326
- * Show Alert Dialog Activity - Displays alert dialog to user.
1327
- *
1328
- * Has one outcome:
1329
- * - 'Done': User clicked OK
1330
- *
1331
- * Usage:
1332
- * ```typescript
1333
- * const dialog = new ShowAlertDialog();
1334
- * await dialog.execute({
1335
- * title: 'Alert',
1336
- * message: 'This is an alert',
1337
- * color: 'info'
1338
- * });
1339
- * ```
1340
- */
1341
- class ShowAlertDialog extends Activity {
1342
- //#endregion
1343
- constructor() {
1344
- super('ShowAlertDialog');
1345
- //#region ---- Services & Dependencies ----
1346
- this.dialogService = inject(AXDialogService);
1347
- this.translationService = inject(AXTranslationService);
1348
- }
1349
- async execute(input) {
1350
- const { title = '', message = '', color = 'primary' } = input;
1351
- // Translate title and message only if they start with '@' (translation key)
1352
- // Otherwise use the text as-is
1353
- const translatedTitle = title
1354
- ? (title.startsWith('@')
1355
- ? await this.translationService.translateAsync(title) || title
1356
- : title)
1357
- : '';
1358
- const translatedMessage = message
1359
- ? (message.startsWith('@')
1360
- ? await this.translationService.translateAsync(message) || message
1361
- : message)
1362
- : '';
1363
- try {
1364
- await this.dialogService.alert(translatedTitle, translatedMessage, color);
1365
- return this.createResult({
1366
- result: true,
1367
- action: 'ok'
1368
- }, 'Done');
1369
- }
1370
- catch (err) {
1371
- console.error('[ShowAlertDialog] Error showing dialog:', err);
1372
- return {
1373
- success: false,
1374
- message: {
1375
- text: err instanceof Error ? err.message : 'Failed to show alert dialog',
1376
- },
1377
- data: {
1378
- output: {
1379
- result: false,
1380
- action: 'error',
1381
- },
1382
- outcomes: {
1383
- Failed: true,
1384
- },
1385
- },
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 || {}),
1386
1337
  };
1387
1338
  }
1388
- }
1389
- }
1390
-
1391
- var showAlertDialog_activity = /*#__PURE__*/Object.freeze({
1392
- __proto__: null,
1393
- ShowAlertDialog: ShowAlertDialog
1394
- });
1395
-
1396
- /**
1397
- * Show Dialog Layout Builder Activity - Displays dialog using Layout Builder.
1398
- *
1399
- * This activity allows you to create custom dialogs using the Layout Builder API.
1400
- * It accepts JSON-serializable content (AXPWidgetNode) and actions configuration.
1401
- *
1402
- * The content can be created using Layout Builder and then converted to JSON:
1403
- * ```typescript
1404
- * const builder = layoutBuilderService.create();
1405
- * builder.flex(flex => {
1406
- * flex.setDirection('column')
1407
- * .formField('First Name', field => {
1408
- * field.path('firstName');
1409
- * field.textBox({ placeholder: 'Enter first name' });
1410
- * });
1411
- * });
1412
- * const contentNode = builder.build();
1413
- * ```
1414
- *
1415
- * Usage in Workflow:
1416
- * ```typescript
1417
- * const dialog = new ShowDialogLayoutBuilder();
1418
- * await dialog.execute({
1419
- * title: 'User Information',
1420
- * size: 'md',
1421
- * context: { firstName: '', lastName: '' },
1422
- * content: {
1423
- * type: 'flex-layout',
1424
- * mode: 'edit',
1425
- * options: {
1426
- * flexDirection: 'column',
1427
- * gap: '16px'
1428
- * },
1429
- * children: [
1430
- * {
1431
- * type: 'form-field',
1432
- * mode: 'edit',
1433
- * options: {
1434
- * label: 'First Name',
1435
- * showLabel: true
1436
- * },
1437
- * children: [{
1438
- * type: 'text-editor',
1439
- * path: 'firstName',
1440
- * options: {
1441
- * placeholder: 'Enter first name'
1442
- * }
1443
- * }]
1444
- * }
1445
- * ]
1446
- * },
1447
- * actions: {
1448
- * cancel: '@general:actions.cancel.title',
1449
- * submit: '@general:actions.submit.title',
1450
- * custom: [{
1451
- * title: 'Save Draft',
1452
- * icon: 'fa-save',
1453
- * color: 'secondary',
1454
- * command: { name: 'save-draft' }
1455
- * }]
1456
- * }
1457
- * });
1458
- * ```
1459
- *
1460
- * Usage in JSON Workflow Definition:
1461
- * ```json
1462
- * {
1463
- * "type": "ShowDialogLayoutBuilder",
1464
- * "properties": {
1465
- * "title": "User Information",
1466
- * "size": "md",
1467
- * "context": { "firstName": "", "lastName": "" },
1468
- * "content": {
1469
- * "type": "flex-layout",
1470
- * "mode": "edit",
1471
- * "options": {
1472
- * "flexDirection": "column",
1473
- * "gap": "16px"
1474
- * },
1475
- * "children": [
1476
- * {
1477
- * "type": "form-field",
1478
- * "mode": "edit",
1479
- * "options": {
1480
- * "label": "First Name",
1481
- * "showLabel": true
1482
- * },
1483
- * "children": [{
1484
- * "type": "text-editor",
1485
- * "path": "firstName",
1486
- * "options": {
1487
- * "placeholder": "Enter first name"
1488
- * }
1489
- * }]
1490
- * }
1491
- * ]
1492
- * },
1493
- * "actions": {
1494
- * "cancel": "@general:actions.cancel.title",
1495
- * "submit": "@general:actions.submit.title"
1496
- * }
1497
- * }
1498
- * }
1499
- * ```
1500
- */
1501
- class ShowDialogLayoutBuilder extends Activity {
1502
- //#endregion
1503
- constructor() {
1504
- super('ShowDialogLayoutBuilder');
1505
- //#region ---- Services & Dependencies ----
1506
- this.layoutBuilder = inject(AXPLayoutBuilderService);
1507
- }
1508
- async execute(input) {
1509
- const { title = '', size = 'md', context = {}, closeButton = false, message, content, actions } = input;
1510
- try {
1511
- // Create dialog using layout builder
1512
- const dialogRef = await this.layoutBuilder
1513
- .create()
1514
- .dialog(dialog => {
1515
- // Set basic dialog options
1516
- dialog
1517
- .setTitle(title)
1518
- .setSize(size)
1519
- .setCloseButton(closeButton)
1520
- .setContext(context);
1521
- // Set message if provided
1522
- if (message) {
1523
- dialog.setMessage(message);
1524
- }
1525
- // Set content if provided
1526
- if (content) {
1527
- // If content is already a flex-layout, use it directly
1528
- // Otherwise, wrap it in a flex container with column direction
1529
- if (content.type === 'flex-layout') {
1530
- // Set content layout directly (accessing internal state of DialogContainerBuilder)
1531
- dialog.contentLayout = content;
1532
- }
1533
- else {
1534
- // Wrap content in a flex container
1535
- const wrappedContent = {
1536
- type: 'flex-layout',
1537
- mode: 'edit',
1538
- options: {
1539
- flexDirection: 'column',
1540
- gap: '10px',
1541
- },
1542
- children: [content],
1543
- };
1544
- dialog.contentLayout = wrappedContent;
1545
- }
1546
- }
1547
- // Set actions if provided
1548
- if (actions) {
1549
- dialog.setActions(actionBuilder => {
1550
- if (actions.cancel) {
1551
- actionBuilder.cancel(actions.cancel);
1552
- }
1553
- if (actions.submit) {
1554
- actionBuilder.submit(actions.submit);
1555
- }
1556
- if (actions.custom) {
1557
- actions.custom.forEach(action => {
1558
- actionBuilder.custom(action);
1559
- });
1560
- }
1561
- });
1562
- }
1563
- })
1564
- .show();
1565
- // Get user action and context
1566
- const action = dialogRef.action() || 'cancel';
1567
- const dialogContext = dialogRef.context();
1568
- // Determine outcomes
1569
- const cancelled = action === 'cancel';
1570
- const confirmed = action === 'submit' || (!cancelled && action !== 'error');
1571
- // Close dialog
1572
- dialogRef.close();
1573
- // Return result with appropriate outcome
1574
- const outcome = cancelled ? 'Cancelled' : confirmed ? 'Confirmed' : 'Done';
1575
- return this.createResult({
1576
- context: dialogContext,
1577
- action,
1578
- cancelled,
1579
- confirmed
1580
- }, outcome);
1581
- }
1582
- catch (err) {
1583
- console.error('[ShowDialogLayoutBuilder] Error showing dialog:', err);
1584
- return {
1585
- success: false,
1586
- message: {
1587
- text: err instanceof Error ? err.message : 'Failed to show dialog',
1588
- },
1589
- data: {
1590
- output: {
1591
- context: {},
1592
- action: 'error',
1593
- cancelled: true,
1594
- confirmed: false,
1595
- },
1596
- outcomes: {
1597
- Error: true,
1598
- },
1599
- },
1600
- };
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;
1601
1348
  }
1349
+ return {
1350
+ output: request.userInput || {},
1351
+ outcomes: { [request.outcome]: true },
1352
+ state: localState.state,
1353
+ nextTask: nextTask || null,
1354
+ };
1602
1355
  }
1603
- }
1604
-
1605
- /**
1606
- * Show Toast Activity - Displays toast notification to user.
1607
- *
1608
- * Usage:
1609
- * ```typescript
1610
- * const toast = new ShowToast();
1611
- * await toast.execute({
1612
- * color: 'success',
1613
- * title: 'Success',
1614
- * message: 'Operation completed successfully!'
1615
- * });
1616
- * ```
1617
- */
1618
- class ShowToast extends Activity {
1619
- constructor() {
1620
- super('ShowToast');
1621
- this.toastService = inject(AXToastService);
1622
- this.translationService = inject(AXTranslationService);
1623
- }
1624
- async execute(input) {
1625
- const { color = 'primary', title = '', message = '', duration = 3000 } = input;
1626
- // Translate title and message only if they start with '@' (translation key)
1627
- // Otherwise use the text as-is
1628
- const translatedTitle = title
1629
- ? (title.startsWith('@')
1630
- ? await this.translationService.translateAsync(title) || title
1631
- : title)
1632
- : '';
1633
- const translatedMessage = message
1634
- ? (message.startsWith('@')
1635
- ? await this.translationService.translateAsync(message) || message
1636
- : message)
1637
- : '';
1638
- try {
1639
- await this.toastService.show({
1640
- color: color,
1641
- title: translatedTitle,
1642
- content: translatedMessage,
1643
- closeButton: true,
1644
- timeOut: duration,
1645
- timeOutProgress: true,
1646
- });
1647
- return this.createResult(undefined, 'Done');
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}`);
1648
1363
  }
1649
- catch (err) {
1650
- console.error('[ShowToast] Error showing toast:', err);
1651
- return {
1652
- success: false,
1653
- message: {
1654
- text: err instanceof Error ? err.message : 'Failed to show toast',
1655
- },
1656
- data: {
1657
- output: undefined,
1658
- outcomes: {
1659
- Failed: true,
1660
- },
1661
- },
1662
- };
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);
1663
1368
  }
1369
+ return state;
1664
1370
  }
1665
- }
1666
-
1667
- var showToast_activity = /*#__PURE__*/Object.freeze({
1668
- __proto__: null,
1669
- ShowToast: ShowToast
1670
- });
1671
-
1672
- /**
1673
- * Navigate Activity - Navigates to different pages/routes.
1674
- *
1675
- * Usage:
1676
- * ```typescript
1677
- * const navigate = new Navigate();
1678
- * await navigate.execute({
1679
- * mode: 'route',
1680
- * route: '/users',
1681
- * params: { id: '123' }
1682
- * });
1683
- * ```
1684
- */
1685
- class Navigate extends Activity {
1686
- constructor() {
1687
- super('Navigate');
1688
- this.router = inject(Router);
1371
+ //#endregion
1372
+ //#region ---- Private Methods ----
1373
+ /**
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;
1689
1386
  }
1690
- async execute(input) {
1691
- const { mode = 'route', route = '', params = {}, queryParams = {}, entity = '', entityId = '', url = '' } = input;
1692
- try {
1693
- switch (mode) {
1694
- case 'route':
1695
- await this.router.navigate([route], { queryParams });
1696
- break;
1697
- case 'entity-details':
1698
- const detailsRoute = `/entities/${entity}/details/${entityId}`;
1699
- await this.router.navigate([detailsRoute], { queryParams });
1700
- break;
1701
- case 'entity-list':
1702
- const listRoute = `/entities/${entity}`;
1703
- await this.router.navigate([listRoute], { queryParams });
1704
- break;
1705
- case 'external':
1706
- if (url) {
1707
- window.open(url, '_blank');
1708
- }
1387
+ /**
1388
+ * Progress workflow steps starting from the current position.
1389
+ *
1390
+ * For frontend/both activities: returns task immediately (suspends workflow).
1391
+ * For backend activities: skips and continues.
1392
+ *
1393
+ * Returns the next pending task (if frontend activity found) or null (if completed).
1394
+ */
1395
+ async executeWorkflowSteps(localState) {
1396
+ const flowchart = localState.definition.root;
1397
+ const activities = flowchart.activities || [];
1398
+ const connections = flowchart.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.activity;
1409
+ const targetId = conn.target.activity;
1410
+ if (!outgoingConnections.has(sourceId)) {
1411
+ outgoingConnections.set(sourceId, []);
1412
+ }
1413
+ outgoingConnections.get(sourceId).push(conn);
1414
+ if (!incomingConnections.has(targetId)) {
1415
+ incomingConnections.set(targetId, []);
1416
+ }
1417
+ incomingConnections.get(targetId).push(conn);
1418
+ });
1419
+ // Find starting activity (no incoming connections, or first activity)
1420
+ let currentActivityId = localState.currentActivityId;
1421
+ if (!currentActivityId) {
1422
+ // Find root activity (no incoming connections)
1423
+ for (const activity of activities) {
1424
+ if (!incomingConnections.has(activity.id)) {
1425
+ currentActivityId = activity.id;
1709
1426
  break;
1710
- default:
1711
- console.error(`[Navigate] Unknown navigation mode: ${mode}`);
1712
- return {
1713
- success: false,
1714
- message: {
1715
- text: `Unknown navigation mode: ${mode}`,
1716
- },
1717
- data: {
1718
- output: undefined,
1719
- outcomes: {
1720
- Failed: true,
1721
- },
1722
- },
1723
- };
1427
+ }
1428
+ }
1429
+ // If no root found, use first activity
1430
+ if (!currentActivityId && activities.length > 0) {
1431
+ currentActivityId = activities[0].id;
1724
1432
  }
1725
- return this.createResult(undefined, 'Done');
1726
- }
1727
- catch (err) {
1728
- console.error('[Navigate] Error navigating:', err);
1729
- return {
1730
- success: false,
1731
- message: {
1732
- text: err instanceof Error ? err.message : 'Failed to navigate',
1733
- },
1734
- data: {
1735
- output: undefined,
1736
- outcomes: {
1737
- Failed: true,
1738
- },
1739
- },
1740
- };
1741
1433
  }
1434
+ // Execute workflow steps
1435
+ while (currentActivityId) {
1436
+ const activity = activityMap.get(currentActivityId);
1437
+ if (!activity) {
1438
+ break;
1439
+ }
1440
+ // Skip if already completed
1441
+ if (localState.completedActivities.has(currentActivityId)) {
1442
+ // Move to next activity
1443
+ const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
1444
+ currentActivityId = nextId ?? undefined;
1445
+ continue;
1446
+ }
1447
+ // Determine execution mode
1448
+ const executionMode = activity.executionMode || 'frontend';
1449
+ // Handle backend activities: skip
1450
+ if (executionMode === 'backend') {
1451
+ console.log(`[WorkflowLocalEngine] ⏭️ Skipping backend activity: ${activity.type} (${activity.id})`);
1452
+ localState.completedActivities.add(currentActivityId);
1453
+ localState.activityResults.set(currentActivityId, {
1454
+ output: null,
1455
+ outcome: 'Done',
1456
+ });
1457
+ // Move to next activity
1458
+ const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
1459
+ currentActivityId = nextId ?? undefined;
1460
+ continue;
1461
+ }
1462
+ // Handle frontend/both activities: return as pendingTask (do NOT execute)
1463
+ if (executionMode === 'frontend' || executionMode === 'both') {
1464
+ // Create task for external execution
1465
+ const task = {
1466
+ taskToken: AXPDataGenerator.uuid(),
1467
+ activityId: activity.id,
1468
+ activityType: activity.type,
1469
+ activityName: activity.name ? activity.name : undefined,
1470
+ executionMode: executionMode,
1471
+ input: activity['properties'] || {},
1472
+ config: activity.customProperties || {},
1473
+ };
1474
+ // Store task token for secure resume
1475
+ this.taskTokens.set(task.taskToken, {
1476
+ instanceId: localState.instanceId,
1477
+ activityId: activity.id,
1478
+ });
1479
+ // Update state to indicate suspension
1480
+ localState.currentActivityId = currentActivityId;
1481
+ localState.state.status = 'suspended';
1482
+ localState.state.currentStepId = currentActivityId;
1483
+ // Return task immediately - AXPWorkflowManager will execute it
1484
+ return task;
1485
+ }
1486
+ // Move to next activity
1487
+ const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
1488
+ currentActivityId = nextId ?? undefined;
1489
+ }
1490
+ // Workflow completed
1491
+ localState.state.status = 'completed';
1492
+ localState.state.output = localState.state.variables;
1493
+ localState.currentActivityId = undefined;
1494
+ return null;
1742
1495
  }
1743
- }
1744
-
1745
- var navigate_activity = /*#__PURE__*/Object.freeze({
1746
- __proto__: null,
1747
- Navigate: Navigate
1748
- });
1749
-
1750
- /**
1751
- * Set Variable Activity - Sets a variable value in workflow context.
1752
- *
1753
- * Usage:
1754
- * ```typescript
1755
- * const setVar = new SetVariable();
1756
- * await setVar.execute({
1757
- * variableName: 'userId',
1758
- * value: '12345'
1759
- * });
1760
- * ```
1761
- */
1762
- class SetVariable extends Activity {
1763
- constructor() {
1764
- super('workflow-activity:set-variable', 'Set Variable');
1765
- }
1766
- async execute(input) {
1767
- const { variableName, value } = input;
1768
- try {
1769
- // In a real implementation, this would set the variable in workflow context
1770
- // For now, we'll just log it
1771
- console.log(`[SetVariable] Setting ${variableName} = ${JSON.stringify(value)}`);
1772
- return this.createResult(undefined, 'Done');
1496
+ /**
1497
+ * Get next activity ID based on connections and outcomes.
1498
+ */
1499
+ getNextActivityId(currentActivityId, outgoingConnections, activityResults) {
1500
+ const connections = outgoingConnections.get(currentActivityId) || [];
1501
+ if (connections.length === 0) {
1502
+ return null; // No outgoing connections - workflow ends
1503
+ }
1504
+ // Get current activity result
1505
+ const result = activityResults.get(currentActivityId);
1506
+ const outcome = result?.outcome || 'Done';
1507
+ // Find connection matching outcome
1508
+ // Outcome matching is typically done via port name (e.g., "Done", "Failed")
1509
+ for (const conn of connections) {
1510
+ const sourcePort = conn.source.port || 'Done';
1511
+ if (sourcePort === outcome) {
1512
+ return conn.target.activity;
1513
+ }
1773
1514
  }
1774
- catch (err) {
1775
- console.error('[SetVariable] Error setting variable:', err);
1776
- return {
1777
- success: false,
1778
- message: {
1779
- text: err instanceof Error ? err.message : 'Failed to set variable',
1780
- },
1781
- data: {
1782
- output: undefined,
1783
- outcomes: {
1784
- Failed: true,
1785
- },
1786
- },
1787
- };
1515
+ // If no matching outcome, use first connection (default path)
1516
+ if (connections.length > 0) {
1517
+ return connections[0].target.activity;
1788
1518
  }
1519
+ return null;
1789
1520
  }
1521
+ 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 }); }
1522
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowLocalEngine }); }
1790
1523
  }
1524
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowLocalEngine, decorators: [{
1525
+ type: Injectable
1526
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
1527
+ type: Optional
1528
+ }, {
1529
+ type: Inject,
1530
+ args: [AXP_WORKFLOW_DEFINITION_LOADER]
1531
+ }] }] });
1532
+
1533
+ // Workflow Runtime Services
1791
1534
 
1792
- var setVariable_activity = /*#__PURE__*/Object.freeze({
1793
- __proto__: null,
1794
- SetVariable: SetVariable
1535
+ const AXP_WORKFLOW_PROVIDER = new InjectionToken('AXP_WORKFLOW_PROVIDER', {
1536
+ factory: () => [],
1537
+ });
1538
+ const AXP_WORKFLOW_CATEGORY_PROVIDER = new InjectionToken('AXP_WORKFLOW_CATEGORY_PROVIDER', {
1539
+ factory: () => [],
1795
1540
  });
1796
1541
 
1542
+ //#region ---- Imports ----
1543
+ //#endregion
1797
1544
  /**
1798
- * Dispatch Event Activity - Dispatches an event to the system.
1545
+ * Optimized Workflow Definition Service
1799
1546
  *
1800
- * Usage:
1801
- * ```typescript
1802
- * const dispatch = new DispatchEvent();
1803
- * await dispatch.execute({
1804
- * eventName: 'user-created',
1805
- * eventData: { userId: '123', name: 'John' }
1806
- * });
1807
- * ```
1547
+ * Manages workflow definitions (metadata) for UI and tooling.
1548
+ * Similar to AXPActivityDefinitionService - only handles metadata, not execution.
1549
+ *
1550
+ * Performance optimizations:
1551
+ * 1. Uses childrenCount to determine if category has children (no query needed)
1552
+ * 2. Uses itemsCount to determine if category has workflows (no query needed)
1553
+ * 3. Aggressive caching prevents duplicate API calls
1554
+ * 4. Single pending request per resource prevents race conditions
1555
+ * 5. Lazy loading - only loads data when needed
1808
1556
  */
1809
- class DispatchEvent extends Activity {
1557
+ class AXPWorkflowDefinitionService {
1810
1558
  constructor() {
1811
- super('DispatchEvent');
1559
+ //#region ---- Providers & Caches ----
1560
+ this.categoryProviders = inject(AXP_WORKFLOW_CATEGORY_PROVIDER, { optional: true }) || [];
1561
+ this.workflowProviders = inject(AXP_WORKFLOW_PROVIDER, { optional: true }) || [];
1562
+ //#endregion
1563
+ //#region ---- Cache Storage ----
1564
+ /** Cache for categories by id - O(1) lookup */
1565
+ this.categoriesById = new Map();
1566
+ /** Cache for categories by parentId - O(1) lookup */
1567
+ this.categoriesByParentId = new Map();
1568
+ /** Cache for workflow definitions by categoryId - O(1) lookup */
1569
+ this.workflowsByCategory = new Map();
1570
+ /** Cache for individual workflow definitions by name - O(1) lookup */
1571
+ this.workflowsByName = new Map();
1572
+ /** Track which provider index owns each category (by category ID) */
1573
+ this.categoryOwnership = new Map(); // Maps categoryId → provider index
1574
+ /** Pending API requests to prevent duplicate calls */
1575
+ this.pendingCategoriesRequests = new Map();
1576
+ this.pendingWorkflowsRequests = new Map();
1577
+ this.pendingWorkflowRequests = new Map();
1812
1578
  }
1813
- async execute(input) {
1814
- const { eventName, eventData } = input;
1815
- try {
1816
- // In a real implementation, this would dispatch the event through an event bus
1817
- // For now, we'll just log it
1818
- console.log(`[DispatchEvent] Dispatching event: ${eventName}`, eventData);
1819
- return this.createResult(undefined, 'Done');
1579
+ //#endregion
1580
+ //#region ---- Public API: Categories ----
1581
+ /**
1582
+ * Get categories by parentId with aggressive caching
1583
+ *
1584
+ * Optimization: Returns cached result immediately if available,
1585
+ * preventing unnecessary API calls during navigation
1586
+ *
1587
+ * @param parentId - Parent category ID (undefined = root categories)
1588
+ * @returns Array of categories with count metadata (childrenCount, itemsCount)
1589
+ */
1590
+ async getCategories(parentId) {
1591
+ // ✅ Fast path: Return cached result
1592
+ if (this.categoriesByParentId.has(parentId)) {
1593
+ return this.categoriesByParentId.get(parentId);
1820
1594
  }
1821
- catch (err) {
1822
- console.error('[DispatchEvent] Error dispatching event:', err);
1823
- return {
1824
- success: false,
1825
- message: {
1826
- text: err instanceof Error ? err.message : 'Failed to dispatch event',
1827
- },
1828
- data: {
1829
- output: undefined,
1830
- outcomes: {
1831
- Failed: true,
1832
- },
1833
- },
1834
- };
1595
+ // Prevent duplicate requests: Return pending promise
1596
+ if (this.pendingCategoriesRequests.has(parentId)) {
1597
+ return this.pendingCategoriesRequests.get(parentId);
1835
1598
  }
1599
+ // ✅ Create single request and cache it
1600
+ const requestPromise = this.loadCategoriesFromProviders(parentId);
1601
+ this.pendingCategoriesRequests.set(parentId, requestPromise);
1602
+ return requestPromise;
1836
1603
  }
1837
- }
1838
-
1839
- var dispatchEvent_activity = /*#__PURE__*/Object.freeze({
1840
- __proto__: null,
1841
- DispatchEvent: DispatchEvent
1842
- });
1843
-
1844
- /**
1845
- * If Activity - Conditional execution based on a condition.
1846
- *
1847
- * Usage:
1848
- * ```typescript
1849
- * const ifActivity = new If();
1850
- * ifActivity.condition = '{{user.isAdmin}}';
1851
- * ifActivity.thenActivities = [activity1, activity2];
1852
- * ifActivity.elseActivities = [activity3];
1853
- * ```
1854
- */
1855
- class If extends Activity {
1856
- constructor() {
1857
- super('If');
1858
- }
1859
- async execute(input) {
1860
- const { condition, thenActivities = [], elseActivities = [] } = input;
1861
- try {
1862
- // Evaluate condition (simplified - in real implementation, use expression evaluator)
1863
- const conditionResult = this.evaluateCondition(condition);
1864
- let result;
1865
- let activities;
1866
- if (conditionResult) {
1867
- activities = thenActivities;
1868
- result = { branch: 'then' };
1604
+ /**
1605
+ * Get single category by ID with O(1) lookup
1606
+ *
1607
+ * Optimization: Uses Map for instant retrieval, falls back to
1608
+ * searching cache, then providers if not found
1609
+ */
1610
+ async getCategoryById(categoryId) {
1611
+ // ✅ Fast path: O(1) lookup in cache
1612
+ if (this.categoriesById.has(categoryId)) {
1613
+ return this.categoriesById.get(categoryId);
1614
+ }
1615
+ // Search in cached parent-child lists
1616
+ for (const categories of this.categoriesByParentId.values()) {
1617
+ const found = categories.find(cat => cat.id === categoryId);
1618
+ if (found) {
1619
+ this.categoriesById.set(categoryId, found);
1620
+ return found;
1869
1621
  }
1870
- else {
1871
- activities = elseActivities;
1872
- result = { branch: 'else' };
1622
+ }
1623
+ // Load root categories if not loaded
1624
+ if (!this.categoriesByParentId.has(undefined)) {
1625
+ await this.getCategories();
1626
+ if (this.categoriesById.has(categoryId)) {
1627
+ return this.categoriesById.get(categoryId);
1873
1628
  }
1874
- // Execute activities in the chosen branch
1875
- for (const activity of activities) {
1876
- const activityResult = await activity.execute(input);
1877
- if (!activityResult.success) {
1878
- return {
1879
- success: false,
1880
- message: activityResult.message,
1881
- data: {
1882
- output: { branch: conditionResult ? 'then' : 'else', failedActivity: activity },
1883
- outcomes: {
1884
- Failed: true,
1885
- },
1886
- },
1887
- };
1888
- }
1629
+ }
1630
+ // Breadth-first search through hierarchy
1631
+ return this.searchCategoryInHierarchy(categoryId);
1632
+ }
1633
+ /**
1634
+ * Get category path from root to specified category
1635
+ *
1636
+ * Optimization: Builds path using cached categories only
1637
+ */
1638
+ async getCategoriesPathById(categoryId) {
1639
+ const path = [];
1640
+ let currentCategoryId = categoryId;
1641
+ while (currentCategoryId) {
1642
+ const category = await this.getCategoryById(currentCategoryId);
1643
+ if (!category) {
1644
+ throw new Error(`Category '${currentCategoryId}' not found`);
1889
1645
  }
1890
- return this.createResult(result, conditionResult ? 'Then' : 'Else');
1646
+ path.unshift(category);
1647
+ currentCategoryId = category.parentId;
1891
1648
  }
1892
- catch (err) {
1893
- console.error('[If] Error evaluating condition:', err);
1894
- return {
1895
- success: false,
1896
- message: {
1897
- text: err instanceof Error ? err.message : 'Failed to evaluate condition',
1898
- },
1899
- data: {
1900
- output: { branch: 'error' },
1901
- outcomes: {
1902
- Failed: true,
1903
- },
1904
- },
1905
- };
1649
+ return path;
1650
+ }
1651
+ //#endregion
1652
+ //#region ---- Public API: Workflow Definitions ----
1653
+ /**
1654
+ * Get workflow definitions for a category with smart caching
1655
+ *
1656
+ * Optimization: Checks itemsCount before querying
1657
+ * - If itemsCount = 0, returns empty array (no API call)
1658
+ * - If itemsCount > 0, loads and caches workflow definitions
1659
+ * - Returns cached result on subsequent calls
1660
+ *
1661
+ * @param categoryId - Category ID to get workflow definitions from
1662
+ * @returns Array of workflow definition metadata
1663
+ */
1664
+ async getWorkflowsByCategoryId(categoryId) {
1665
+ // ✅ Fast path: Return cached result
1666
+ if (this.workflowsByCategory.has(categoryId)) {
1667
+ return this.workflowsByCategory.get(categoryId);
1668
+ }
1669
+ // ✅ Smart optimization: Check itemsCount before querying
1670
+ const category = await this.getCategoryById(categoryId);
1671
+ if (category && category.itemsCount !== undefined && category.itemsCount === 0) {
1672
+ // Category has no workflows - cache empty array and skip API call
1673
+ const emptyArray = [];
1674
+ this.workflowsByCategory.set(categoryId, emptyArray);
1675
+ return emptyArray;
1676
+ }
1677
+ // ✅ Prevent duplicate requests
1678
+ if (this.pendingWorkflowsRequests.has(categoryId)) {
1679
+ return this.pendingWorkflowsRequests.get(categoryId);
1680
+ }
1681
+ // ✅ Load from providers
1682
+ const requestPromise = this.loadWorkflowsFromProviders(categoryId);
1683
+ this.pendingWorkflowsRequests.set(categoryId, requestPromise);
1684
+ return requestPromise;
1685
+ }
1686
+ /**
1687
+ * Get single workflow definition metadata by name with O(1) lookup
1688
+ *
1689
+ * Optimization: Uses Map for instant retrieval
1690
+ *
1691
+ * @param name - Workflow name (unique identifier)
1692
+ * @returns Workflow definition metadata or undefined if not found
1693
+ */
1694
+ async getWorkflowByName(name) {
1695
+ // ✅ Fast path: O(1) lookup in cache
1696
+ if (this.workflowsByName.has(name)) {
1697
+ return this.workflowsByName.get(name);
1906
1698
  }
1699
+ // ✅ Prevent duplicate requests
1700
+ if (this.pendingWorkflowRequests.has(name)) {
1701
+ return this.pendingWorkflowRequests.get(name);
1702
+ }
1703
+ // ✅ Load from providers
1704
+ const requestPromise = this.loadWorkflowFromProviders(name);
1705
+ this.pendingWorkflowRequests.set(name, requestPromise);
1706
+ return requestPromise;
1907
1707
  }
1908
- evaluateCondition(condition) {
1909
- if (typeof condition === 'boolean') {
1910
- return condition;
1708
+ /**
1709
+ * Get category ID containing a specific workflow definition
1710
+ *
1711
+ * Optimization: Searches cache first, loads on-demand if needed
1712
+ */
1713
+ async getCategoryIdByWorkflowName(workflowName) {
1714
+ // ✅ Search in cached workflow definitions
1715
+ for (const [categoryId, definitions] of this.workflowsByCategory.entries()) {
1716
+ if (definitions.some(def => def.name === workflowName)) {
1717
+ return categoryId;
1718
+ }
1911
1719
  }
1912
- if (typeof condition === 'string') {
1913
- // Simple evaluation - in real implementation, use proper expression evaluator
1914
- return condition === 'true' || condition === '1' || condition === 'yes';
1720
+ // Try loading the workflow definition to find its category
1721
+ const definition = await this.getWorkflowByName(workflowName);
1722
+ if (definition && definition.category) {
1723
+ // Try to find category by name/id
1724
+ const categories = await this.getCategories();
1725
+ const found = categories.find(cat => cat.id === definition.category || cat.title === definition.category);
1726
+ if (found) {
1727
+ return found.id;
1728
+ }
1915
1729
  }
1916
- return false;
1730
+ return undefined;
1917
1731
  }
1918
- }
1919
-
1920
- var if_activity = /*#__PURE__*/Object.freeze({
1921
- __proto__: null,
1922
- If: If
1923
- });
1924
-
1925
- /**
1926
- * While Activity - Loop execution while condition is true.
1927
- *
1928
- * Usage:
1929
- * ```typescript
1930
- * const whileActivity = new While();
1931
- * whileActivity.condition = '{{counter < 10}}';
1932
- * whileActivity.activities = [incrementActivity, logActivity];
1933
- * ```
1934
- */
1935
- class While extends Activity {
1936
- constructor() {
1937
- super('While');
1732
+ /**
1733
+ * Get category path for a workflow
1734
+ */
1735
+ async getCategoriesPathByWorkflowName(workflowName) {
1736
+ const categoryId = await this.getCategoryIdByWorkflowName(workflowName);
1737
+ if (!categoryId) {
1738
+ throw new Error(`Workflow '${workflowName}' not found in any category`);
1739
+ }
1740
+ return this.getCategoriesPathById(categoryId);
1938
1741
  }
1939
- async execute(input) {
1940
- const { condition, activities = [], maxIterations = 1000 } = input;
1742
+ //#endregion
1743
+ //#region ---- Private: Data Loading ----
1744
+ /**
1745
+ * Load categories from providers and cache results
1746
+ *
1747
+ * Optimization: Tracks provider ownership to avoid unnecessary API calls
1748
+ * - For root (parentId = undefined): Query ALL providers
1749
+ * - For children: Only query the provider that owns the parent
1750
+ */
1751
+ async loadCategoriesFromProviders(parentId) {
1941
1752
  try {
1942
- let iteration = 0;
1943
- let conditionResult = this.evaluateCondition(condition);
1944
- while (conditionResult && iteration < maxIterations) {
1945
- // Execute activities in the loop
1946
- for (const activity of activities) {
1947
- const activityResult = await activity.execute(input);
1948
- if (!activityResult.success) {
1949
- return {
1950
- success: false,
1951
- message: activityResult.message,
1952
- data: {
1953
- output: {
1954
- iterations: iteration,
1955
- completed: false,
1956
- },
1957
- outcomes: {
1958
- Failed: true,
1959
- },
1960
- },
1961
- };
1753
+ const resolvedProviders = await Promise.allSettled(this.categoryProviders);
1754
+ const categories = [];
1755
+ // Determine which provider(s) to query
1756
+ const providerIndicesToQuery = parentId
1757
+ ? this.getProviderIndexForCategory(parentId)
1758
+ : null; // Root: query all providers
1759
+ for (let i = 0; i < resolvedProviders.length; i++) {
1760
+ const p = resolvedProviders[i];
1761
+ // Skip if we have a specific provider index and this isn't it
1762
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
1763
+ continue;
1764
+ }
1765
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
1766
+ try {
1767
+ const cats = await p.value.getList(parentId);
1768
+ if (Array.isArray(cats) && cats.length > 0) {
1769
+ categories.push(...cats);
1770
+ // ✅ Track ownership: This provider INDEX owns these categories
1771
+ cats.forEach(cat => this.categoryOwnership.set(cat.id, i));
1772
+ }
1773
+ }
1774
+ catch {
1775
+ // Continue on error - try other providers
1962
1776
  }
1963
1777
  }
1964
- iteration++;
1965
- conditionResult = this.evaluateCondition(condition);
1966
1778
  }
1967
- const result = {
1968
- iterations: iteration,
1969
- completed: iteration < maxIterations
1970
- };
1971
- return this.createResult(result, 'Done');
1779
+ // Cache results for fast subsequent access
1780
+ this.categoriesByParentId.set(parentId, categories);
1781
+ categories.forEach(cat => this.categoriesById.set(cat.id, cat));
1782
+ return categories;
1972
1783
  }
1973
- catch (err) {
1974
- console.error('[While] Error in loop execution:', err);
1975
- return {
1976
- success: false,
1977
- message: {
1978
- text: err instanceof Error ? err.message : 'Failed during loop execution',
1979
- },
1980
- data: {
1981
- output: { iterations: 0, completed: false },
1982
- outcomes: {
1983
- Failed: true,
1984
- },
1985
- },
1986
- };
1784
+ finally {
1785
+ this.pendingCategoriesRequests.delete(parentId);
1987
1786
  }
1988
1787
  }
1989
- evaluateCondition(condition) {
1990
- if (typeof condition === 'boolean') {
1991
- return condition;
1992
- }
1993
- if (typeof condition === 'string') {
1994
- // Simple evaluation - in real implementation, use proper expression evaluator
1995
- return condition === 'true' || condition === '1' || condition === 'yes';
1788
+ /**
1789
+ * Get the provider index that owns a specific category
1790
+ *
1791
+ * @returns Array with provider index, or null if ownership unknown (query all)
1792
+ */
1793
+ getProviderIndexForCategory(categoryId) {
1794
+ const ownerIndex = this.categoryOwnership.get(categoryId);
1795
+ if (ownerIndex !== undefined) {
1796
+ return [ownerIndex];
1996
1797
  }
1997
- return false;
1998
- }
1999
- }
2000
-
2001
- var while_activity = /*#__PURE__*/Object.freeze({
2002
- __proto__: null,
2003
- While: While
2004
- });
2005
-
2006
- /**
2007
- * ForEach Activity - Iterates over a collection of items.
2008
- *
2009
- * Usage:
2010
- * ```typescript
2011
- * const forEach = new ForEach();
2012
- * await forEach.execute({
2013
- * items: ['item1', 'item2', 'item3'],
2014
- * activities: [processItemActivity]
2015
- * });
2016
- * ```
2017
- */
2018
- class ForEach extends Activity {
2019
- constructor() {
2020
- super('ForEach');
1798
+ // Ownership unknown - will query all providers (fallback)
1799
+ return null;
2021
1800
  }
2022
- async execute(input) {
2023
- const { items = [], activities = [], itemVariableName = 'currentItem', indexVariableName = 'currentIndex' } = input;
1801
+ /**
1802
+ * Load workflow definitions from providers and cache results
1803
+ *
1804
+ * Optimization: Only queries the provider that owns the category
1805
+ * Uses provider INDEX to match category provider with workflow provider
1806
+ */
1807
+ async loadWorkflowsFromProviders(categoryId) {
2024
1808
  try {
2025
- const results = [];
2026
- for (let index = 0; index < items.length; index++) {
2027
- const currentItem = items[index];
2028
- // Create context with current item and index
2029
- const itemContext = {
2030
- ...input,
2031
- [itemVariableName]: currentItem,
2032
- [indexVariableName]: index
2033
- };
2034
- // Execute activities for current item
2035
- for (const activity of activities) {
2036
- const activityResult = await activity.execute(itemContext);
2037
- if (!activityResult.success) {
2038
- return {
2039
- success: false,
2040
- message: activityResult.message,
2041
- data: {
2042
- output: {
2043
- totalItems: items.length,
2044
- processedItems: results.length,
2045
- results,
2046
- },
2047
- outcomes: {
2048
- Failed: true,
2049
- },
2050
- },
2051
- };
1809
+ const resolvedProviders = await Promise.allSettled(this.workflowProviders);
1810
+ const definitions = [];
1811
+ // Smart routing: Get provider INDEX that owns this category
1812
+ const ownerIndex = this.categoryOwnership.get(categoryId);
1813
+ const providerIndicesToQuery = ownerIndex !== undefined ? [ownerIndex] : null;
1814
+ for (let i = 0; i < resolvedProviders.length; i++) {
1815
+ const p = resolvedProviders[i];
1816
+ // Skip if we have a specific provider index and this isn't it
1817
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
1818
+ continue;
1819
+ }
1820
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
1821
+ try {
1822
+ const defs = await p.value.getList(categoryId);
1823
+ if (Array.isArray(defs)) {
1824
+ definitions.push(...defs);
1825
+ }
1826
+ }
1827
+ catch {
1828
+ // Continue on error - try other providers
2052
1829
  }
2053
1830
  }
2054
- results.push({
2055
- item: currentItem,
2056
- index,
2057
- processed: true
2058
- });
2059
1831
  }
2060
- const result = {
2061
- totalItems: items.length,
2062
- processedItems: results.length,
2063
- results
2064
- };
2065
- return this.createResult(result, 'Done');
1832
+ // Cache results for fast subsequent access
1833
+ this.workflowsByCategory.set(categoryId, definitions);
1834
+ definitions.forEach(def => {
1835
+ if (def.name) {
1836
+ this.workflowsByName.set(def.name, def);
1837
+ }
1838
+ });
1839
+ return definitions;
2066
1840
  }
2067
- catch (err) {
2068
- console.error('[ForEach] Error in iteration:', err);
2069
- return {
2070
- success: false,
2071
- message: {
2072
- text: err instanceof Error ? err.message : 'Failed during iteration',
2073
- },
2074
- data: {
2075
- output: { totalItems: 0, processedItems: 0, results: [] },
2076
- outcomes: {
2077
- Failed: true,
2078
- },
2079
- },
2080
- };
1841
+ finally {
1842
+ this.pendingWorkflowsRequests.delete(categoryId);
2081
1843
  }
2082
1844
  }
2083
- }
2084
-
2085
- var forEach_activity = /*#__PURE__*/Object.freeze({
2086
- __proto__: null,
2087
- ForEach: ForEach
2088
- });
2089
-
2090
- /**
2091
- * Execute Command Activity - Executes a command through Command Bus.
2092
- *
2093
- * Usage:
2094
- * ```typescript
2095
- * const executeCmd = new ExecuteCommand();
2096
- * await executeCmd.execute({
2097
- * commandKey: 'UserManagement.CreateUser',
2098
- * input: { name: 'John', email: 'john@example.com' }
2099
- * });
2100
- * ```
2101
- */
2102
- class ExecuteCommand extends Activity {
2103
- constructor() {
2104
- super('workflow-activity:execute-command', 'Execute Command');
2105
- this.commandService = inject(AXPCommandService);
2106
- }
2107
- async execute(input) {
2108
- const { commandKey, input: commandInput = {} } = input;
1845
+ /**
1846
+ * Load single workflow definition from providers and cache result
1847
+ */
1848
+ async loadWorkflowFromProviders(name) {
2109
1849
  try {
2110
- // Check if command exists
2111
- if (!this.commandService.exists(commandKey)) {
2112
- console.warn(`[ExecuteCommand] Command '${commandKey}' is not registered. Simulating execution.`);
2113
- // Simulate command execution for unregistered commands
2114
- const result = {
2115
- commandKey,
2116
- success: true,
2117
- output: commandInput,
2118
- executedAt: new Date().toISOString(),
2119
- simulated: true
2120
- };
2121
- return this.createResult(result, 'Done');
2122
- }
2123
- // Execute command through Command Bus
2124
- const result = await this.commandService.execute(commandKey, commandInput);
2125
- if (!result) {
2126
- return {
2127
- success: false,
2128
- message: {
2129
- text: `Command '${commandKey}' returned no result`,
2130
- },
2131
- data: {
2132
- output: {
2133
- commandKey,
2134
- success: false,
2135
- executedAt: new Date().toISOString(),
2136
- },
2137
- outcomes: {
2138
- Failed: true,
2139
- },
2140
- },
2141
- };
1850
+ const resolvedProviders = await Promise.allSettled(this.workflowProviders);
1851
+ // Try providers first
1852
+ for (const p of resolvedProviders) {
1853
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getById === 'function') {
1854
+ try {
1855
+ const result = await p.value.getById(name);
1856
+ if (result) {
1857
+ this.workflowsByName.set(name, result);
1858
+ return result;
1859
+ }
1860
+ }
1861
+ catch {
1862
+ // Continue on error
1863
+ }
1864
+ }
2142
1865
  }
2143
- if (!result.success) {
2144
- return {
2145
- success: false,
2146
- message: result.message,
2147
- data: {
2148
- output: {
2149
- commandKey,
2150
- success: false,
2151
- executedAt: new Date().toISOString(),
2152
- error: result.message?.text,
2153
- },
2154
- outcomes: {
2155
- Failed: true,
2156
- },
2157
- },
2158
- };
1866
+ // Fallback: Search in cached workflow definitions
1867
+ for (const definitions of this.workflowsByCategory.values()) {
1868
+ const found = definitions.find(def => def.name === name);
1869
+ if (found) {
1870
+ this.workflowsByName.set(name, found);
1871
+ return found;
1872
+ }
2159
1873
  }
2160
- return this.createResult({
2161
- commandKey,
2162
- success: true,
2163
- output: result.data,
2164
- executedAt: new Date().toISOString(),
2165
- }, 'Done');
1874
+ return undefined;
2166
1875
  }
2167
- catch (err) {
2168
- console.error('[ExecuteCommand] Error executing command:', err);
2169
- return {
2170
- success: false,
2171
- message: {
2172
- text: err instanceof Error ? err.message : 'Unknown error',
2173
- },
2174
- data: {
2175
- output: {
2176
- commandKey,
2177
- success: false,
2178
- error: err instanceof Error ? err.message : 'Unknown error',
2179
- },
2180
- outcomes: {
2181
- Failed: true,
2182
- },
2183
- },
2184
- };
1876
+ finally {
1877
+ this.pendingWorkflowRequests.delete(name);
2185
1878
  }
2186
1879
  }
2187
- }
2188
-
2189
- var executeCommand_activity = /*#__PURE__*/Object.freeze({
2190
- __proto__: null,
2191
- ExecuteCommand: ExecuteCommand
2192
- });
2193
-
2194
- /**
2195
- * Execute Query Activity - Executes a query through Query Bus.
2196
- *
2197
- * Usage:
2198
- * ```typescript
2199
- * const executeQuery = new ExecuteQuery();
2200
- * await executeQuery.execute({
2201
- * queryKey: 'UserManagement.GetUsers',
2202
- * input: { page: 1, pageSize: 10 }
2203
- * });
2204
- * ```
2205
- */
2206
- class ExecuteQuery extends Activity {
2207
- constructor() {
2208
- super('workflow-activity:execute-query', 'Execute Query');
2209
- this.queryService = inject(AXPQueryService);
2210
- }
2211
- async execute(input) {
2212
- const { queryKey, input: queryInput = {} } = input;
2213
- try {
2214
- // Check if query exists
2215
- if (!this.queryService.exists(queryKey)) {
2216
- console.warn(`[ExecuteQuery] Query '${queryKey}' is not registered. Simulating execution.`);
2217
- // Simulate query execution for unregistered queries
2218
- const result = {
2219
- queryKey,
2220
- success: true,
2221
- data: [],
2222
- totalCount: 0,
2223
- executedAt: new Date().toISOString(),
2224
- simulated: true
2225
- };
2226
- return this.createResult(result, 'Done');
1880
+ /**
1881
+ * Breadth-first search through category hierarchy
1882
+ */
1883
+ async searchCategoryInHierarchy(categoryId) {
1884
+ const searchQueue = [undefined];
1885
+ const searched = new Set();
1886
+ while (searchQueue.length > 0) {
1887
+ const parentId = searchQueue.shift();
1888
+ if (searched.has(parentId))
1889
+ continue;
1890
+ searched.add(parentId);
1891
+ const categories = await this.getCategories(parentId);
1892
+ const found = categories.find(cat => cat.id === categoryId);
1893
+ if (found) {
1894
+ return found;
1895
+ }
1896
+ // Optimization: Only search children if childrenCount > 0
1897
+ for (const category of categories) {
1898
+ if (category.childrenCount > 0 && !searched.has(category.id)) {
1899
+ searchQueue.push(category.id);
1900
+ }
2227
1901
  }
2228
- // Execute query through Query Bus
2229
- const result = await this.queryService.fetch(queryKey, queryInput);
2230
- return this.createResult({
2231
- queryKey,
2232
- success: true,
2233
- data: result,
2234
- totalCount: Array.isArray(result) ? result.length : (result?.totalCount || 0),
2235
- executedAt: new Date().toISOString()
2236
- }, 'Done');
2237
- }
2238
- catch (err) {
2239
- console.error('[ExecuteQuery] Error executing query:', err);
2240
- return {
2241
- success: false,
2242
- message: {
2243
- text: err instanceof Error ? err.message : 'Unknown error',
2244
- },
2245
- data: {
2246
- output: {
2247
- queryKey,
2248
- success: false,
2249
- error: err instanceof Error ? err.message : 'Unknown error',
2250
- },
2251
- outcomes: {
2252
- Failed: true,
2253
- },
2254
- },
2255
- };
2256
1902
  }
1903
+ return undefined;
2257
1904
  }
2258
- }
2259
-
2260
- var executeQuery_activity = /*#__PURE__*/Object.freeze({
2261
- __proto__: null,
2262
- ExecuteQuery: ExecuteQuery
2263
- });
2264
-
2265
- /**
2266
- * Start Activity - Marks the start point of a workflow.
2267
- *
2268
- * This is a visual marker activity that doesn't perform any actual work.
2269
- * It's used in workflow designers to clearly indicate where a workflow begins.
2270
- *
2271
- * Usage:
2272
- * ```typescript
2273
- * const start = new StartActivity();
2274
- * await start.execute({});
2275
- * ```
2276
- */
2277
- class StartActivity extends Activity {
2278
- constructor() {
2279
- super('StartActivity');
1905
+ //#endregion
1906
+ //#region ---- Cache Management ----
1907
+ /**
1908
+ * Check if category has children (uses cached count)
1909
+ */
1910
+ categoryHasChildren(categoryId) {
1911
+ const category = this.categoriesById.get(categoryId);
1912
+ return category ? category.childrenCount > 0 : false;
2280
1913
  }
2281
- async execute(input) {
2282
- // This activity is a visual marker only
2283
- // It immediately completes and allows workflow to proceed
2284
- console.log('[StartActivity] Workflow started');
2285
- return this.createResult(undefined, 'Done');
1914
+ /**
1915
+ * Check if category has workflows (uses cached count)
1916
+ */
1917
+ categoryHasWorkflows(categoryId) {
1918
+ const category = this.categoriesById.get(categoryId);
1919
+ return category ? (category.itemsCount ?? 0) > 0 : false;
2286
1920
  }
2287
- }
2288
-
2289
- var startActivity_activity = /*#__PURE__*/Object.freeze({
2290
- __proto__: null,
2291
- StartActivity: StartActivity
2292
- });
2293
-
2294
- /**
2295
- * End Activity - Marks the end point of a workflow.
2296
- *
2297
- * This is a visual marker activity that terminates the workflow execution.
2298
- * When this activity is reached, the workflow completes successfully.
2299
- *
2300
- * Usage:
2301
- * ```typescript
2302
- * const end = new EndActivity();
2303
- * await end.execute({});
2304
- * ```
2305
- */
2306
- class EndActivity extends Activity {
2307
- constructor() {
2308
- super('EndActivity');
1921
+ /**
1922
+ * Clear all caches
1923
+ */
1924
+ clearAllCache() {
1925
+ this.categoriesById.clear();
1926
+ this.categoriesByParentId.clear();
1927
+ this.workflowsByCategory.clear();
1928
+ this.workflowsByName.clear();
1929
+ this.categoryOwnership.clear();
1930
+ this.pendingCategoriesRequests.clear();
1931
+ this.pendingWorkflowsRequests.clear();
1932
+ this.pendingWorkflowRequests.clear();
2309
1933
  }
2310
- async execute(input) {
2311
- // This activity marks the end of workflow execution
2312
- console.log('[EndActivity] Workflow completed');
2313
- return this.createResult(undefined, 'Done');
1934
+ /**
1935
+ * Clear categories cache only
1936
+ */
1937
+ clearCategoriesCache() {
1938
+ this.categoriesById.clear();
1939
+ this.categoriesByParentId.clear();
1940
+ this.categoryOwnership.clear();
1941
+ this.pendingCategoriesRequests.clear();
2314
1942
  }
1943
+ /**
1944
+ * Clear workflows cache only
1945
+ */
1946
+ clearWorkflowsCache() {
1947
+ this.workflowsByCategory.clear();
1948
+ this.workflowsByName.clear();
1949
+ this.pendingWorkflowsRequests.clear();
1950
+ this.pendingWorkflowRequests.clear();
1951
+ }
1952
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1953
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionService, providedIn: 'root' }); }
2315
1954
  }
2316
-
2317
- var endActivity_activity = /*#__PURE__*/Object.freeze({
2318
- __proto__: null,
2319
- EndActivity: EndActivity
2320
- });
2321
-
2322
- /**
2323
- * Command setups for all built-in workflow activities.
2324
- * Registers activities as AXPCommand instances.
2325
- */
2326
- const provideWorkflowActivityCommands = () => provideCommandSetups([
2327
- // Workflow Markers
2328
- {
2329
- key: 'workflow-activity:start',
2330
- command: () => Promise.resolve().then(function () { return startActivity_activity; }).then(m => m.StartActivity),
2331
- },
2332
- {
2333
- key: 'workflow-activity:end',
2334
- command: () => Promise.resolve().then(function () { return endActivity_activity; }).then(m => m.EndActivity),
2335
- },
2336
- // Console Activities
2337
- {
2338
- key: 'workflow-activity:write-line',
2339
- command: () => Promise.resolve().then(function () { return writeLine_activity; }).then(m => m.WriteLine),
2340
- },
2341
- // Control Flow Activities
2342
- {
2343
- key: 'workflow-activity:sequence',
2344
- command: () => Promise.resolve().then(function () { return sequence_activity; }).then(m => m.Sequence),
2345
- },
2346
- {
2347
- key: 'workflow-activity:if',
2348
- command: () => Promise.resolve().then(function () { return if_activity; }).then(m => m.If),
2349
- },
2350
- {
2351
- key: 'workflow-activity:while',
2352
- command: () => Promise.resolve().then(function () { return while_activity; }).then(m => m.While),
2353
- },
2354
- {
2355
- key: 'workflow-activity:for-each',
2356
- command: () => Promise.resolve().then(function () { return forEach_activity; }).then(m => m.ForEach),
2357
- },
2358
- // Dialog Activities
2359
- {
2360
- key: 'workflow-activity:show-confirm-dialog',
2361
- command: () => Promise.resolve().then(function () { return showConfirmDialog_activity; }).then(m => m.ShowConfirmDialog),
2362
- },
2363
- {
2364
- key: 'workflow-activity:show-alert-dialog',
2365
- command: () => Promise.resolve().then(function () { return showAlertDialog_activity; }).then(m => m.ShowAlertDialog),
2366
- },
2367
- // Notification Activities
2368
- {
2369
- key: 'workflow-activity:show-toast',
2370
- command: () => Promise.resolve().then(function () { return showToast_activity; }).then(m => m.ShowToast),
2371
- },
2372
- // Event Activities
2373
- {
2374
- key: 'workflow-activity:dispatch-event',
2375
- command: () => Promise.resolve().then(function () { return dispatchEvent_activity; }).then(m => m.DispatchEvent),
2376
- },
2377
- // Variable Activities
2378
- {
2379
- key: 'workflow-activity:set-variable',
2380
- command: () => Promise.resolve().then(function () { return setVariable_activity; }).then(m => m.SetVariable),
2381
- },
2382
- // Command & Query Activities
2383
- {
2384
- key: 'workflow-activity:execute-command',
2385
- command: () => Promise.resolve().then(function () { return executeCommand_activity; }).then(m => m.ExecuteCommand),
2386
- },
2387
- {
2388
- key: 'workflow-activity:execute-query',
2389
- command: () => Promise.resolve().then(function () { return executeQuery_activity; }).then(m => m.ExecuteQuery),
2390
- },
2391
- // Navigation Activities
2392
- {
2393
- key: 'workflow-activity:navigate',
2394
- command: () => Promise.resolve().then(function () { return navigate_activity; }).then(m => m.Navigate),
2395
- },
2396
- ]);
2397
-
2398
- // Built-in Activities
1955
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDefinitionService, decorators: [{
1956
+ type: Injectable,
1957
+ args: [{
1958
+ providedIn: 'root',
1959
+ }]
1960
+ }] });
2399
1961
 
2400
1962
  // Workflow Definition Types (Storage/Database)
2401
1963
 
@@ -2404,7 +1966,6 @@ class AXPWorkflowModule {
2404
1966
  return {
2405
1967
  ngModule: AXPWorkflowModule,
2406
1968
  providers: [
2407
- provideWorkflowActivityCommands(),
2408
1969
  {
2409
1970
  provide: 'AXPWorkflowModuleFactory',
2410
1971
  useFactory: (registry) => () => {
@@ -2479,8 +2040,6 @@ class AXPWorkflowModule {
2479
2040
  * @ignore
2480
2041
  */
2481
2042
  constructor(instances) {
2482
- // Inject AXP_COMMAND_SETUP to trigger command registration factory
2483
- this._commandSetup = inject(AXP_COMMAND_SETUP, { optional: true });
2484
2043
  instances?.forEach((f) => {
2485
2044
  f();
2486
2045
  });
@@ -2488,7 +2047,12 @@ class AXPWorkflowModule {
2488
2047
  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 }); }
2489
2048
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule }); }
2490
2049
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule, providers: [
2491
- provideWorkflowActivityCommands(),
2050
+ AXPWorkflowLocalEngine,
2051
+ {
2052
+ provide: AXP_WORKFLOW_ENGINE,
2053
+ useExisting: AXPWorkflowLocalEngine,
2054
+ },
2055
+ AXPWorkflowManager,
2492
2056
  ] }); }
2493
2057
  }
2494
2058
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule, decorators: [{
@@ -2498,7 +2062,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
2498
2062
  exports: [],
2499
2063
  declarations: [],
2500
2064
  providers: [
2501
- provideWorkflowActivityCommands(),
2065
+ AXPWorkflowLocalEngine,
2066
+ {
2067
+ provide: AXP_WORKFLOW_ENGINE,
2068
+ useExisting: AXPWorkflowLocalEngine,
2069
+ },
2070
+ AXPWorkflowManager,
2502
2071
  ],
2503
2072
  }]
2504
2073
  }], ctorParameters: () => [{ type: undefined, decorators: [{
@@ -2512,5 +2081,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
2512
2081
  * Generated bundle index. Do not edit.
2513
2082
  */
2514
2083
 
2515
- export { AXPActivityCategoryProviderService, AXPActivityProviderService, AXPWorkflowAction, AXPWorkflowContext, AXPWorkflowDefinitionRegistryService, AXPWorkflowDefinitionResolver, AXPWorkflowError, AXPWorkflowEventService, AXPWorkflowExecutionService, AXPWorkflowFunction, AXPWorkflowModule, AXPWorkflowRegistryService, AXPWorkflowService, AXP_ACTIVITY_CATEGORY_PROVIDER, AXP_ACTIVITY_PROVIDER, AXP_WORKFLOW_DEFINITION_LOADER, Activity, ActivityRegistry, DispatchEvent, EndActivity, ExecuteCommand, ExecuteQuery, ForEach, If, Navigate, Sequence, SetVariable, ShowAlertDialog, ShowConfirmDialog, ShowDialogLayoutBuilder, ShowToast, StartActivity, While, WorkflowCoordinator, WriteLine, createWorkFlowEvent, ofType, provideWorkflowActivityCommands };
2084
+ 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 };
2516
2085
  //# sourceMappingURL=acorex-platform-workflow.mjs.map