@acorex/platform 21.0.0-next.0 → 21.0.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/auth/index.d.ts +235 -5
  2. package/common/index.d.ts +220 -218
  3. package/core/index.d.ts +261 -431
  4. package/fesm2022/acorex-platform-auth.mjs +200 -37
  5. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  6. package/fesm2022/{acorex-platform-common-common-settings.provider-zhqNP3xb.mjs → acorex-platform-common-common-settings.provider-O8usQ0VC.mjs} +4 -4
  7. package/fesm2022/{acorex-platform-common-common-settings.provider-zhqNP3xb.mjs.map → acorex-platform-common-common-settings.provider-O8usQ0VC.mjs.map} +1 -1
  8. package/fesm2022/acorex-platform-common.mjs +215 -198
  9. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  10. package/fesm2022/acorex-platform-core.mjs +545 -585
  11. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-domain.mjs +16 -16
  13. package/fesm2022/acorex-platform-domain.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-builder.mjs +84 -35
  15. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-components.mjs +298 -190
  17. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  18. package/fesm2022/acorex-platform-layout-designer.mjs +72 -72
  19. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  20. package/fesm2022/acorex-platform-layout-entity.mjs +2955 -1278
  21. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  22. package/fesm2022/acorex-platform-layout-views.mjs +25 -17
  23. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  24. package/fesm2022/acorex-platform-layout-widget-core.mjs +189 -108
  25. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  26. package/fesm2022/{acorex-platform-layout-widgets-button-widget-designer.component-C3VoBb_b.mjs → acorex-platform-layout-widgets-button-widget-designer.component-D-NsRvRl.mjs} +4 -4
  27. package/fesm2022/{acorex-platform-layout-widgets-button-widget-designer.component-C3VoBb_b.mjs.map → acorex-platform-layout-widgets-button-widget-designer.component-D-NsRvRl.mjs.map} +1 -1
  28. package/fesm2022/{acorex-platform-layout-widgets-file-list-popup.component-CxrsI6Hn.mjs → acorex-platform-layout-widgets-file-list-popup.component-CCcKHSHj.mjs} +4 -4
  29. package/fesm2022/{acorex-platform-layout-widgets-file-list-popup.component-CxrsI6Hn.mjs.map → acorex-platform-layout-widgets-file-list-popup.component-CCcKHSHj.mjs.map} +1 -1
  30. package/fesm2022/{acorex-platform-layout-widgets-image-preview.popup-V31OpYah.mjs → acorex-platform-layout-widgets-image-preview.popup-IrT52Nh1.mjs} +4 -4
  31. package/fesm2022/{acorex-platform-layout-widgets-image-preview.popup-V31OpYah.mjs.map → acorex-platform-layout-widgets-image-preview.popup-IrT52Nh1.mjs.map} +1 -1
  32. package/fesm2022/{acorex-platform-layout-widgets-page-widget-designer.component-BtZMBxYp.mjs → acorex-platform-layout-widgets-page-widget-designer.component-DFbfh-OX.mjs} +4 -4
  33. package/fesm2022/{acorex-platform-layout-widgets-page-widget-designer.component-BtZMBxYp.mjs.map → acorex-platform-layout-widgets-page-widget-designer.component-DFbfh-OX.mjs.map} +1 -1
  34. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-Dw6naXvq.mjs +111 -0
  35. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-Dw6naXvq.mjs.map +1 -0
  36. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-Ck7-wpT2.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component--4R3TUdJ.mjs} +4 -4
  37. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-Ck7-wpT2.mjs.map → acorex-platform-layout-widgets-tabular-data-edit-popup.component--4R3TUdJ.mjs.map} +1 -1
  38. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-y8vjUiVs.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-BeVQuHFj.mjs} +4 -4
  39. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-y8vjUiVs.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-BeVQuHFj.mjs.map} +1 -1
  40. package/fesm2022/{acorex-platform-layout-widgets-text-block-widget-designer.component-Df1BFkSa.mjs → acorex-platform-layout-widgets-text-block-widget-designer.component-CdQxqi0D.mjs} +4 -4
  41. package/fesm2022/{acorex-platform-layout-widgets-text-block-widget-designer.component-Df1BFkSa.mjs.map → acorex-platform-layout-widgets-text-block-widget-designer.component-CdQxqi0D.mjs.map} +1 -1
  42. package/fesm2022/acorex-platform-layout-widgets.mjs +2247 -1221
  43. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  44. package/fesm2022/acorex-platform-native.mjs +7 -7
  45. package/fesm2022/acorex-platform-native.mjs.map +1 -1
  46. package/fesm2022/acorex-platform-runtime.mjs +43 -43
  47. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  48. package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-VIGuU5M4.mjs → acorex-platform-themes-default-entity-master-create-view.component-D2ucwC3F.mjs} +4 -4
  49. package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-VIGuU5M4.mjs.map → acorex-platform-themes-default-entity-master-create-view.component-D2ucwC3F.mjs.map} +1 -1
  50. package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs → acorex-platform-themes-default-entity-master-list-view.component-CIV6YDDZ.mjs} +12 -12
  51. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-CIV6YDDZ.mjs.map +1 -0
  52. package/fesm2022/{acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs → acorex-platform-themes-default-entity-master-modify-view.component-D5BYbUGK.mjs} +7 -8
  53. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-D5BYbUGK.mjs.map +1 -0
  54. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-eMBby9k4.mjs → acorex-platform-themes-default-entity-master-single-view.component-Cf4ei46_.mjs} +7 -7
  55. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-eMBby9k4.mjs.map → acorex-platform-themes-default-entity-master-single-view.component-Cf4ei46_.mjs.map} +1 -1
  56. package/fesm2022/{acorex-platform-themes-default-error-401.component-cfREo88K.mjs → acorex-platform-themes-default-error-401.component-DdMToZ9q.mjs} +4 -4
  57. package/fesm2022/{acorex-platform-themes-default-error-401.component-cfREo88K.mjs.map → acorex-platform-themes-default-error-401.component-DdMToZ9q.mjs.map} +1 -1
  58. package/fesm2022/{acorex-platform-themes-default-error-404.component-CdCV5ZoA.mjs → acorex-platform-themes-default-error-404.component-CKsa5aPE.mjs} +4 -4
  59. package/fesm2022/{acorex-platform-themes-default-error-404.component-CdCV5ZoA.mjs.map → acorex-platform-themes-default-error-404.component-CKsa5aPE.mjs.map} +1 -1
  60. package/fesm2022/{acorex-platform-themes-default-error-offline.component-E7SzBcAt.mjs → acorex-platform-themes-default-error-offline.component-1gxFTAuX.mjs} +4 -4
  61. package/fesm2022/{acorex-platform-themes-default-error-offline.component-E7SzBcAt.mjs.map → acorex-platform-themes-default-error-offline.component-1gxFTAuX.mjs.map} +1 -1
  62. package/fesm2022/acorex-platform-themes-default.mjs +39 -42
  63. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  64. package/fesm2022/{acorex-platform-themes-shared-icon-chooser-column.component-C0EpfU2k.mjs → acorex-platform-themes-shared-icon-chooser-column.component-Ds8eRMuV.mjs} +4 -4
  65. package/fesm2022/{acorex-platform-themes-shared-icon-chooser-column.component-C0EpfU2k.mjs.map → acorex-platform-themes-shared-icon-chooser-column.component-Ds8eRMuV.mjs.map} +1 -1
  66. package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-9W52W6Nu.mjs → acorex-platform-themes-shared-icon-chooser-view.component-Dv38sOzr.mjs} +4 -4
  67. package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-9W52W6Nu.mjs.map → acorex-platform-themes-shared-icon-chooser-view.component-Dv38sOzr.mjs.map} +1 -1
  68. package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-column.component-DTnfRy5f.mjs → acorex-platform-themes-shared-theme-color-chooser-column.component-DcO6P6OG.mjs} +11 -11
  69. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-DcO6P6OG.mjs.map +1 -0
  70. package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-DY0JtT1v.mjs → acorex-platform-themes-shared-theme-color-chooser-view.component-B7T2qtaI.mjs} +4 -4
  71. package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-DY0JtT1v.mjs.map → acorex-platform-themes-shared-theme-color-chooser-view.component-B7T2qtaI.mjs.map} +1 -1
  72. package/fesm2022/acorex-platform-themes-shared.mjs +41 -41
  73. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  74. package/fesm2022/acorex-platform-workflow.mjs +1529 -1758
  75. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  76. package/layout/builder/index.d.ts +15 -3
  77. package/layout/components/index.d.ts +23 -6
  78. package/layout/entity/index.d.ts +392 -144
  79. package/layout/views/index.d.ts +16 -11
  80. package/layout/widget-core/index.d.ts +47 -10
  81. package/layout/widgets/index.d.ts +212 -39
  82. package/package.json +5 -5
  83. package/themes/default/index.d.ts +0 -1
  84. package/workflow/index.d.ts +1050 -1759
  85. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs.map +0 -1
  86. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs.map +0 -1
  87. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-DTnfRy5f.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, AXPExpressionEvaluatorService, 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) {
@@ -28,10 +23,10 @@ class AXPWorkflowEventService {
28
23
  get events$() {
29
24
  return this.eventSubject.asObservable();
30
25
  }
31
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowEventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
32
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowEventService, providedIn: 'root' }); }
26
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowEventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
27
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowEventService, providedIn: 'root' }); }
33
28
  }
34
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowEventService, decorators: [{
29
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowEventService, decorators: [{
35
30
  type: Injectable,
36
31
  args: [{
37
32
  providedIn: 'root',
@@ -121,10 +116,10 @@ class AXPWorkflowRegistryService {
121
116
  });
122
117
  }
123
118
  }
124
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
125
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowRegistryService, providedIn: 'root' }); }
119
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
120
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowRegistryService, providedIn: 'root' }); }
126
121
  }
127
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowRegistryService, decorators: [{
122
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowRegistryService, decorators: [{
128
123
  type: Injectable,
129
124
  args: [{
130
125
  providedIn: 'root',
@@ -163,17 +158,17 @@ class AXPWorkflowAction {
163
158
  dispatch(event) {
164
159
  this.eventService.dispatch(event);
165
160
  }
166
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowAction, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
167
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowAction }); }
161
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowAction, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
162
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowAction }); }
168
163
  }
169
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowAction, decorators: [{
164
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowAction, decorators: [{
170
165
  type: Injectable
171
166
  }] });
172
167
  class AXPWorkflowFunction {
173
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowFunction, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
174
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowFunction }); }
168
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowFunction, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
169
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowFunction }); }
175
170
  }
176
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowFunction, decorators: [{
171
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowFunction, decorators: [{
177
172
  type: Injectable
178
173
  }] });
179
174
  function createWorkFlowEvent(type) {
@@ -189,10 +184,10 @@ class AXPWorkflowDecideAction extends AXPWorkflowAction {
189
184
  async execute(context) {
190
185
  // its a fake action
191
186
  }
192
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDecideAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
193
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDecideAction, providedIn: 'root' }); }
187
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowDecideAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
188
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowDecideAction, providedIn: 'root' }); }
194
189
  }
195
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowDecideAction, decorators: [{
190
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowDecideAction, decorators: [{
196
191
  type: Injectable,
197
192
  args: [{
198
193
  providedIn: 'root',
@@ -378,10 +373,10 @@ class AXPWorkflowService {
378
373
  }
379
374
  return this.injector.get(functionType);
380
375
  }
381
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowService, deps: [{ token: AXPWorkflowRegistryService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
382
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowService, providedIn: 'root' }); }
376
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowService, deps: [{ token: AXPWorkflowRegistryService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
377
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowService, providedIn: 'root' }); }
383
378
  }
384
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowService, decorators: [{
379
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowService, decorators: [{
385
380
  type: Injectable,
386
381
  args: [{
387
382
  providedIn: 'root',
@@ -406,629 +401,897 @@ class AXPStartWorkflowAction extends AXPWorkflowAction {
406
401
  throw e;
407
402
  }
408
403
  }
409
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPStartWorkflowAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
410
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPStartWorkflowAction, providedIn: 'root' }); }
404
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPStartWorkflowAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
405
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPStartWorkflowAction, providedIn: 'root' }); }
411
406
  }
412
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPStartWorkflowAction, decorators: [{
407
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPStartWorkflowAction, decorators: [{
413
408
  type: Injectable,
414
409
  args: [{
415
410
  providedIn: 'root',
416
411
  }]
417
412
  }] });
418
413
 
414
+ // ============================================
415
+ // WORKFLOW INSTANCE v3.0.0 TYPES
416
+ // Based on Elsa Workflow Instance schema: https://elsaworkflows.io/schemas/workflow-instance/v3.0.0/schema.json
417
+ // Compatible with Elsa backend while using ACoreX naming conventions
418
+ // ============================================
419
+
420
+ const AXP_ACTIVITY_PROVIDER = new InjectionToken('AXP_ACTIVITY_PROVIDER', {
421
+ factory: () => [],
422
+ });
423
+ const AXP_ACTIVITY_CATEGORY_PROVIDER = new InjectionToken('AXP_ACTIVITY_CATEGORY_PROVIDER', {
424
+ factory: () => [],
425
+ });
426
+
427
+ //#region ---- Imports ----
428
+ //#endregion
419
429
  /**
420
- * Injection token for workflow definition loaders.
421
- * Multiple loaders can be provided (multi: true).
422
- */
423
- const AXP_WORKFLOW_DEFINITION_LOADER = new InjectionToken('AXP_WORKFLOW_DEFINITION_LOADER');
424
- /**
425
- * Resolver service for workflow definitions.
426
- * Aggregates all registered loaders and resolves workflow definitions.
430
+ * Optimized Activity Definition Service
431
+ *
432
+ * Manages activity definitions (metadata) for UI and tooling.
433
+ * Similar to AXPReportDefinitionService - only handles metadata, not execution.
434
+ *
435
+ * Performance optimizations:
436
+ * 1. Uses childrenCount to determine if category has children (no query needed)
437
+ * 2. Uses itemsCount to determine if category has activities (no query needed)
438
+ * 3. Aggressive caching prevents duplicate API calls
439
+ * 4. Single pending request per resource prevents race conditions
440
+ * 5. Lazy loading - only loads data when needed
427
441
  */
428
- class AXPWorkflowDefinitionResolver {
442
+ class AXPActivityDefinitionService {
429
443
  constructor() {
430
- this.loaders = inject(AXP_WORKFLOW_DEFINITION_LOADER, { optional: true });
444
+ //#region ---- Providers & Caches ----
445
+ this.categoryProviders = inject(AXP_ACTIVITY_CATEGORY_PROVIDER, { optional: true }) || [];
446
+ this.activityProviders = inject(AXP_ACTIVITY_PROVIDER, { optional: true }) || [];
447
+ //#endregion
448
+ //#region ---- Cache Storage ----
449
+ /** Cache for categories by id - O(1) lookup */
450
+ this.categoriesById = new Map();
451
+ /** Cache for categories by parentId - O(1) lookup */
452
+ this.categoriesByParentId = new Map();
453
+ /** Cache for activity definitions by categoryId - O(1) lookup */
454
+ this.activitiesByCategory = new Map();
455
+ /** Cache for individual activity definitions by name - O(1) lookup */
456
+ this.activitiesByName = new Map();
457
+ /** Track which provider index owns each category (by category ID) */
458
+ this.categoryOwnership = new Map(); // Maps categoryId → provider index
459
+ /** Pending API requests to prevent duplicate calls */
460
+ this.pendingCategoriesRequests = new Map();
461
+ this.pendingActivitiesRequests = new Map();
462
+ this.pendingActivityRequests = new Map();
431
463
  }
464
+ //#endregion
465
+ //#region ---- Initialization ----
466
+ //#endregion
467
+ //#region ---- Public API: Categories ----
432
468
  /**
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
469
+ * Get categories by parentId with aggressive caching
470
+ *
471
+ * Optimization: Returns cached result immediately if available,
472
+ * preventing unnecessary API calls during navigation
473
+ *
474
+ * @param parentId - Parent category ID (undefined = root categories)
475
+ * @returns Array of categories with count metadata (childrenCount, itemsCount)
437
476
  */
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
- }
446
- }
447
- catch (error) {
448
- console.warn(`[AXPWorkflowDefinitionResolver] Loader failed for ${name}:`, error);
449
- }
477
+ async getCategories(parentId) {
478
+ // Fast path: Return cached result
479
+ if (this.categoriesByParentId.has(parentId)) {
480
+ return this.categoriesByParentId.get(parentId);
450
481
  }
451
- return null;
482
+ // ✅ Prevent duplicate requests: Return pending promise
483
+ if (this.pendingCategoriesRequests.has(parentId)) {
484
+ return this.pendingCategoriesRequests.get(parentId);
485
+ }
486
+ // ✅ Create single request and cache it
487
+ const requestPromise = this.loadCategoriesFromProviders(parentId);
488
+ this.pendingCategoriesRequests.set(parentId, requestPromise);
489
+ return requestPromise;
452
490
  }
453
491
  /**
454
- * Get all available workflow names from all loaders.
455
- * @returns Array of unique workflow names
492
+ * Get single category by ID with O(1) lookup
493
+ *
494
+ * Optimization: Uses Map for instant retrieval, falls back to
495
+ * searching cache, then providers if not found
456
496
  */
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
- }
497
+ async getCategoryById(categoryId) {
498
+ // Fast path: O(1) lookup in cache
499
+ if (this.categoriesById.has(categoryId)) {
500
+ return this.categoriesById.get(categoryId);
501
+ }
502
+ // ✅ Search in cached parent-child lists
503
+ for (const categories of this.categoriesByParentId.values()) {
504
+ const found = categories.find(cat => cat.id === categoryId);
505
+ if (found) {
506
+ this.categoriesById.set(categoryId, found);
507
+ return found;
469
508
  }
470
509
  }
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();
490
- }
491
- /**
492
- * Observable for workflow definition changes.
493
- */
494
- get onChanged$() {
495
- return this.onChanged.asObservable();
510
+ // ✅ Load root categories if not loaded
511
+ if (!this.categoriesByParentId.has(undefined)) {
512
+ await this.getCategories();
513
+ if (this.categoriesById.has(categoryId)) {
514
+ return this.categoriesById.get(categoryId);
515
+ }
516
+ }
517
+ // Breadth-first search through hierarchy
518
+ return this.searchCategoryInHierarchy(categoryId);
496
519
  }
497
520
  /**
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
521
+ * Get category path from root to specified category
522
+ *
523
+ * Optimization: Builds path using cached categories only
502
524
  */
503
- async get(name) {
504
- // Check cache first
505
- if (this.cache.has(name)) {
506
- return this.cache.get(name);
507
- }
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' });
525
+ async getCategoriesPathById(categoryId) {
526
+ const path = [];
527
+ let currentCategoryId = categoryId;
528
+ while (currentCategoryId) {
529
+ const category = await this.getCategoryById(currentCategoryId);
530
+ if (!category) {
531
+ throw new Error(`Category '${currentCategoryId}' not found`);
532
+ }
533
+ path.unshift(category);
534
+ currentCategoryId = category.parentId;
513
535
  }
514
- return definition;
536
+ return path;
515
537
  }
538
+ //#endregion
539
+ //#region ---- Public API: Activity Definitions ----
516
540
  /**
517
- * Register a workflow definition in the cache.
518
- * @param definition - The workflow definition to register
541
+ * Get activity definitions for a category with smart caching
542
+ *
543
+ * Optimization: Checks itemsCount before querying
544
+ * - If itemsCount = 0, returns empty array (no API call)
545
+ * - If itemsCount > 0, loads and caches activity definitions
546
+ * - Returns cached result on subsequent calls
547
+ *
548
+ * @param categoryId - Category ID to get activity definitions from
549
+ * @returns Array of activity definitions
519
550
  */
520
- register(definition) {
521
- this.cache.set(definition.name, definition);
522
- this.onChanged.next({ name: definition.name, action: 'registered' });
551
+ async getActivitiesByCategoryId(categoryId) {
552
+ // ✅ Fast path: Return cached result
553
+ if (this.activitiesByCategory.has(categoryId)) {
554
+ return this.activitiesByCategory.get(categoryId);
555
+ }
556
+ // ✅ Smart optimization: Check itemsCount before querying
557
+ const category = await this.getCategoryById(categoryId);
558
+ if (category && category.itemsCount !== undefined && category.itemsCount === 0) {
559
+ // Category has no activities - cache empty array and skip API call
560
+ const emptyArray = [];
561
+ this.activitiesByCategory.set(categoryId, emptyArray);
562
+ return emptyArray;
563
+ }
564
+ // ✅ Prevent duplicate requests
565
+ if (this.pendingActivitiesRequests.has(categoryId)) {
566
+ return this.pendingActivitiesRequests.get(categoryId);
567
+ }
568
+ // ✅ Load from providers
569
+ const requestPromise = this.loadActivitiesFromProviders(categoryId);
570
+ this.pendingActivitiesRequests.set(categoryId, requestPromise);
571
+ return requestPromise;
523
572
  }
524
573
  /**
525
- * Update a workflow definition in the cache.
526
- * @param definition - The updated workflow definition
574
+ * Get single activity definition by name with O(1) lookup
575
+ *
576
+ * Optimization: Uses Map for instant retrieval
577
+ *
578
+ * @param name - Activity name (unique identifier and command key)
579
+ * @returns Activity definition or undefined if not found
527
580
  */
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' });
581
+ async getActivityByName(name) {
582
+ // ✅ Fast path: O(1) lookup in cache
583
+ if (this.activitiesByName.has(name)) {
584
+ return this.activitiesByName.get(name);
532
585
  }
533
- }
534
- /**
535
- * Remove a workflow definition from the cache.
536
- * @param name - The workflow name to remove
537
- */
538
- remove(name) {
539
- if (this.cache.has(name)) {
540
- this.cache.delete(name);
541
- this.onChanged.next({ name, action: 'removed' });
586
+ // ✅ Prevent duplicate requests
587
+ if (this.pendingActivityRequests.has(name)) {
588
+ return this.pendingActivityRequests.get(name);
542
589
  }
590
+ // ✅ Load from providers
591
+ const requestPromise = this.loadActivityFromProviders(name);
592
+ this.pendingActivityRequests.set(name, requestPromise);
593
+ return requestPromise;
543
594
  }
544
595
  /**
545
- * Clear all cached workflow definitions.
546
- */
547
- clear() {
548
- this.cache.clear();
549
- }
550
- /**
551
- * Check if a workflow definition is cached.
552
- * @param definitionId - The workflow definition ID
553
- * @returns True if cached, false otherwise
554
- */
555
- has(definitionId) {
556
- return this.cache.has(definitionId);
557
- }
558
- /**
559
- * Get all cached workflow definition IDs.
560
- * @returns Array of definition IDs (only those that have been loaded)
561
- */
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;
589
- }
590
- /**
591
- * Helper method that returns Done outcome by default.
592
- */
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();
610
- }
611
- /**
612
- * Register an activity type.
613
- */
614
- register(type, factory, descriptor) {
615
- this.registry.set(type, factory);
616
- this.descriptors.set(type, descriptor);
617
- }
618
- /**
619
- * Create an activity instance.
596
+ * Get category ID containing a specific activity definition
597
+ *
598
+ * Optimization: Searches cache first, loads on-demand if needed
620
599
  */
621
- create(type) {
622
- const factory = this.registry.get(type);
623
- if (!factory) {
624
- throw new Error(`Unknown activity type: ${type}`);
600
+ async getCategoryIdByActivityName(activityName) {
601
+ // Search in cached activity definitions
602
+ for (const [categoryId, definitions] of this.activitiesByCategory.entries()) {
603
+ if (definitions.some(def => def.name === activityName)) {
604
+ return categoryId;
605
+ }
625
606
  }
626
- return factory();
627
- }
628
- /**
629
- * Get activity descriptor.
630
- */
631
- getDescriptor(type) {
632
- return this.descriptors.get(type);
607
+ // ✅ Try loading the activity definition to find its category
608
+ const definition = await this.getActivityByName(activityName);
609
+ if (definition && definition.category) {
610
+ // Try to find category by name/id
611
+ const categories = await this.getCategories();
612
+ const found = categories.find(cat => cat.id === definition.category || cat.title === definition.category);
613
+ if (found) {
614
+ return found.id;
615
+ }
616
+ }
617
+ return undefined;
633
618
  }
634
619
  /**
635
- * Get all registered types.
620
+ * Get category path for an activity
636
621
  */
637
- getTypes() {
638
- return Array.from(this.registry.keys());
622
+ async getCategoriesPathByActivityName(activityName) {
623
+ const categoryId = await this.getCategoryIdByActivityName(activityName);
624
+ if (!categoryId) {
625
+ throw new Error(`Activity '${activityName}' not found in any category`);
626
+ }
627
+ return this.getCategoriesPathById(categoryId);
639
628
  }
629
+ //#endregion
630
+ //#region ---- Private: Data Loading ----
640
631
  /**
641
- * Get all descriptors.
632
+ * Load categories from providers and cache results
633
+ *
634
+ * Optimization: Tracks provider ownership to avoid unnecessary API calls
635
+ * - For root (parentId = undefined): Query ALL providers
636
+ * - For children: Only query the provider that owns the parent
642
637
  */
643
- getAllDescriptors() {
644
- return Array.from(this.descriptors.values());
638
+ async loadCategoriesFromProviders(parentId) {
639
+ try {
640
+ const resolvedProviders = await Promise.allSettled(this.categoryProviders);
641
+ const categories = [];
642
+ // Determine which provider(s) to query
643
+ const providerIndicesToQuery = parentId
644
+ ? this.getProviderIndexForCategory(parentId)
645
+ : null; // Root: query all providers
646
+ for (let i = 0; i < resolvedProviders.length; i++) {
647
+ const p = resolvedProviders[i];
648
+ // Skip if we have a specific provider index and this isn't it
649
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
650
+ continue;
651
+ }
652
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
653
+ try {
654
+ const cats = await p.value.getList(parentId);
655
+ if (Array.isArray(cats) && cats.length > 0) {
656
+ categories.push(...cats);
657
+ // ✅ Track ownership: This provider INDEX owns these categories
658
+ cats.forEach(cat => this.categoryOwnership.set(cat.id, i));
659
+ }
660
+ }
661
+ catch {
662
+ // Continue on error - try other providers
663
+ }
664
+ }
665
+ }
666
+ // ✅ Cache results for fast subsequent access
667
+ this.categoriesByParentId.set(parentId, categories);
668
+ categories.forEach(cat => this.categoriesById.set(cat.id, cat));
669
+ return categories;
670
+ }
671
+ finally {
672
+ this.pendingCategoriesRequests.delete(parentId);
673
+ }
645
674
  }
646
675
  /**
647
- * Get descriptors by category.
676
+ * Get the provider index that owns a specific category
677
+ *
678
+ * @returns Array with provider index, or null if ownership unknown (query all)
648
679
  */
649
- getDescriptorsByCategory(category) {
650
- return this.getAllDescriptors().filter(d => d.category === category);
651
- }
652
- }
653
-
654
- /**
655
- * Injection token for activity providers.
656
- */
657
- const AXP_ACTIVITY_PROVIDER = new InjectionToken('AXP_ACTIVITY_PROVIDER');
658
- /**
659
- * Activity Provider Service.
660
- * Collects all activity providers and manages activity registration.
661
- */
662
- class AXPActivityProviderService {
663
- constructor() {
664
- this.activityDescriptors = new Map();
665
- this.categories = [];
666
- this.providers = [];
667
- this.categoryProviders = [];
668
- this.initialized = false;
669
- this.commandService = inject(AXPCommandService);
670
- this.commandRegistry = inject(AXPCommandRegistry);
680
+ getProviderIndexForCategory(categoryId) {
681
+ const ownerIndex = this.categoryOwnership.get(categoryId);
682
+ if (ownerIndex !== undefined) {
683
+ return [ownerIndex];
684
+ }
685
+ // Ownership unknown - will query all providers (fallback)
686
+ return null;
671
687
  }
672
688
  /**
673
- * Register an activity provider.
689
+ * Load activity definitions from providers and cache results
690
+ *
691
+ * Optimization: Only queries the provider that owns the category
692
+ * Uses provider INDEX to match category provider with activity provider
674
693
  */
675
- registerProvider(provider) {
676
- this.providers.push(provider);
677
- if (this.initialized) {
678
- this.initializeProvider(provider);
694
+ async loadActivitiesFromProviders(categoryId) {
695
+ try {
696
+ const resolvedProviders = await Promise.allSettled(this.activityProviders);
697
+ const definitions = [];
698
+ // ✅ Smart routing: Get provider INDEX that owns this category
699
+ const ownerIndex = this.categoryOwnership.get(categoryId);
700
+ const providerIndicesToQuery = ownerIndex !== undefined ? [ownerIndex] : null;
701
+ for (let i = 0; i < resolvedProviders.length; i++) {
702
+ const p = resolvedProviders[i];
703
+ // Skip if we have a specific provider index and this isn't it
704
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
705
+ continue;
706
+ }
707
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
708
+ try {
709
+ const defs = await p.value.getList(categoryId);
710
+ if (Array.isArray(defs)) {
711
+ definitions.push(...defs);
712
+ }
713
+ }
714
+ catch {
715
+ // Continue on error - try other providers
716
+ }
717
+ }
718
+ }
719
+ // ✅ Cache results for fast subsequent access
720
+ this.activitiesByCategory.set(categoryId, definitions);
721
+ definitions.forEach(def => {
722
+ if (def.name) {
723
+ this.activitiesByName.set(def.name, def);
724
+ }
725
+ });
726
+ return definitions;
727
+ }
728
+ finally {
729
+ this.pendingActivitiesRequests.delete(categoryId);
679
730
  }
680
731
  }
681
732
  /**
682
- * Register a category provider.
733
+ * Load single activity definition from providers and cache result
683
734
  */
684
- registerCategoryProvider(provider) {
685
- this.categoryProviders.push(provider);
686
- if (this.initialized) {
687
- this.initializeCategoryProvider(provider);
735
+ async loadActivityFromProviders(name) {
736
+ try {
737
+ const resolvedProviders = await Promise.allSettled(this.activityProviders);
738
+ // Try providers first
739
+ for (const p of resolvedProviders) {
740
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getById === 'function') {
741
+ try {
742
+ const result = await p.value.getById(name);
743
+ if (result) {
744
+ this.activitiesByName.set(name, result);
745
+ return result;
746
+ }
747
+ }
748
+ catch {
749
+ // Continue on error
750
+ }
751
+ }
752
+ }
753
+ // Fallback: Search in cached activity definitions
754
+ for (const definitions of this.activitiesByCategory.values()) {
755
+ const found = definitions.find(def => def.name === name);
756
+ if (found) {
757
+ this.activitiesByName.set(name, found);
758
+ return found;
759
+ }
760
+ }
761
+ return undefined;
762
+ }
763
+ finally {
764
+ this.pendingActivityRequests.delete(name);
688
765
  }
689
766
  }
690
767
  /**
691
- * Initialize all providers.
768
+ * Breadth-first search through category hierarchy
692
769
  */
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);
770
+ async searchCategoryInHierarchy(categoryId) {
771
+ const searchQueue = [undefined];
772
+ const searched = new Set();
773
+ while (searchQueue.length > 0) {
774
+ const parentId = searchQueue.shift();
775
+ if (searched.has(parentId))
776
+ continue;
777
+ searched.add(parentId);
778
+ const categories = await this.getCategories(parentId);
779
+ const found = categories.find(cat => cat.id === categoryId);
780
+ if (found) {
781
+ return found;
782
+ }
783
+ // ✅ Optimization: Only search children if childrenCount > 0
784
+ for (const category of categories) {
785
+ if (category.childrenCount > 0 && !searched.has(category.id)) {
786
+ searchQueue.push(category.id);
787
+ }
788
+ }
703
789
  }
704
- this.initialized = true;
790
+ return undefined;
705
791
  }
792
+ //#endregion
793
+ //#region ---- Cache Management ----
706
794
  /**
707
- * Get activity descriptor by key.
795
+ * Check if category has children (uses cached count)
708
796
  */
709
- getDescriptor(key) {
710
- return this.activityDescriptors.get(key);
797
+ categoryHasChildren(categoryId) {
798
+ const category = this.categoriesById.get(categoryId);
799
+ return category ? category.childrenCount > 0 : false;
711
800
  }
712
801
  /**
713
- * Get all activity descriptors.
802
+ * Check if category has activities (uses cached count)
714
803
  */
715
- getAllDescriptors() {
716
- return Array.from(this.activityDescriptors.values());
804
+ categoryHasActivities(categoryId) {
805
+ const category = this.categoriesById.get(categoryId);
806
+ return category ? (category.itemsCount ?? 0) > 0 : false;
717
807
  }
718
808
  /**
719
- * Get descriptors by category.
809
+ * Clear all caches
720
810
  */
721
- getDescriptorsByCategory(category) {
722
- return this.getAllDescriptors().filter(d => d.category === category);
811
+ clearAllCache() {
812
+ this.categoriesById.clear();
813
+ this.categoriesByParentId.clear();
814
+ this.activitiesByCategory.clear();
815
+ this.activitiesByName.clear();
816
+ this.categoryOwnership.clear();
817
+ this.pendingCategoriesRequests.clear();
818
+ this.pendingActivitiesRequests.clear();
819
+ this.pendingActivityRequests.clear();
723
820
  }
724
821
  /**
725
- * Get all categories.
822
+ * Clear categories cache only
726
823
  */
727
- getCategories() {
728
- return [...this.categories].sort((a, b) => (a.order || 999) - (b.order || 999));
824
+ clearCategoriesCache() {
825
+ this.categoriesById.clear();
826
+ this.categoriesByParentId.clear();
827
+ this.categoryOwnership.clear();
828
+ this.pendingCategoriesRequests.clear();
729
829
  }
730
830
  /**
731
- * Create an activity instance by key using AXPCommandService.
831
+ * Clear activities cache only
732
832
  */
733
- async createActivity(key) {
734
- try {
735
- // Check if command exists
736
- if (!this.commandService.exists(key)) {
737
- return null;
738
- }
739
- // Get command loader from registry
740
- const loader = this.commandRegistry.getLoader(key);
741
- if (!loader) {
742
- return null;
743
- }
744
- // Create command instance
745
- const command = await loader();
746
- return command;
747
- }
748
- catch (error) {
749
- return null;
750
- }
751
- }
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' }); }
833
+ clearActivitiesCache() {
834
+ this.activitiesByCategory.clear();
835
+ this.activitiesByName.clear();
836
+ this.pendingActivitiesRequests.clear();
837
+ this.pendingActivityRequests.clear();
838
+ }
839
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPActivityDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
840
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPActivityDefinitionService, providedIn: 'root' }); }
773
841
  }
774
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityProviderService, decorators: [{
842
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPActivityDefinitionService, decorators: [{
775
843
  type: Injectable,
776
844
  args: [{
777
- providedIn: 'root'
845
+ providedIn: 'root',
778
846
  }]
779
847
  }] });
780
848
 
781
849
  /**
782
- * Injection token for activity category providers.
850
+ * Injection token for workflow engine.
851
+ * Default implementation is AXPWorkflowLocalEngine.
783
852
  */
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
- }] }] });
853
+ const AXP_WORKFLOW_ENGINE = new InjectionToken('AXP_WORKFLOW_ENGINE');
830
854
 
855
+ //#endregion
831
856
  /**
832
- * Abstract service for workflow execution operations.
857
+ * Workflow Expression Scope Service
858
+ *
859
+ * Shared service for building expression evaluation scope from workflow data.
860
+ * Used by both Local Engine and Mock Runtime to manage workflow data (inputs, variables, outputs).
833
861
  *
834
- * This service handles communication with backend for workflow execution.
862
+ * Responsibilities:
863
+ * - Build expression evaluation scope from workflow state
864
+ * - Provide context.eval() function for accessing workflow data
865
+ * - Manage workflow data structure (inputs, variables, outputs)
835
866
  *
836
- * Implementation should be provided in connectivity layer:
837
- * - Mock implementation: @acorex/connectivity/mock
838
- * - API implementation: @acorex/connectivity/api
867
+ * This service does NOT:
868
+ * - Execute activities
869
+ * - Evaluate expressions (delegates to AXPExpressionEvaluatorService)
870
+ * - Manage workflow state (handled by runtime)
839
871
  *
840
872
  * @example
841
873
  * ```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
- * }
874
+ * const scopeService = inject(WorkflowExpressionScopeService);
849
875
  *
850
- * // In connectivity/api
851
- * @Injectable()
852
- * export class AXCWorkflowExecutionService implements AXPWorkflowExecutionService {
853
- * constructor(private http: HttpClient) {}
876
+ * // Build scope from workflow state
877
+ * const scope = scopeService.buildScope({
878
+ * inputs: state.input || {},
879
+ * variables: state.variables || {},
880
+ * outputs: activityOutputs
881
+ * });
854
882
  *
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
- * }
883
+ * // Or build from state directly
884
+ * const scope = scopeService.buildScopeFromState(state, activityOutputs);
864
885
  * ```
865
886
  */
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 }); }
887
+ class WorkflowExpressionScopeService {
888
+ //#region ---- Public Methods ----
889
+ /**
890
+ * Build expression evaluation scope for workflow activities.
891
+ *
892
+ * Provides workflow-specific data (inputs, variables, outputs) and context.eval() function.
893
+ * Other data (session, current user, etc.) are provided by expression evaluator scope providers.
894
+ *
895
+ * Scope includes:
896
+ * - inputs: Workflow input values (accessible as inputs.propertyName)
897
+ * - variables: Workflow state variables (accessible as variables.propertyName or vars.propertyName)
898
+ * - outputs: Previous activity outputs (accessible as outputs.activityId)
899
+ * - context.eval(path): Function to access nested properties from workflow data
900
+ *
901
+ * Expressions can use:
902
+ * - {{inputs.userName}} - Direct access to workflow input
903
+ * - {{variables.someVar}} or {{vars.someVar}} - Direct access to workflow variable
904
+ * - {{outputs.activityId.property}} - Direct access to previous activity output
905
+ * - {{context.eval("inputs.userName")}} - Access via context.eval (supports nested paths)
906
+ * - {{context.eval("variables.someVar")}} - Access variables via context.eval
907
+ * - {{context.eval("outputs.activityId.property")}} - Access outputs via context.eval
908
+ * - {{session.currentUser().name}} - Access current user via expression evaluator scope providers
909
+ *
910
+ * The context.eval() function provides a unified way to access all workflow data,
911
+ * similar to how entity-detail-list uses context.eval() to access parent data.
912
+ *
913
+ * @param context - Workflow expression context containing inputs, variables, and outputs
914
+ * @returns Expression evaluator scope with workflow data and context.eval() function
915
+ */
916
+ buildScope(context) {
917
+ // Build merged workflow data object for context.eval()
918
+ // This allows expressions like: context.eval("inputs.userName") or context.eval("variables.count")
919
+ const workflowData = {
920
+ inputs: context.inputs || {},
921
+ variables: context.variables || {},
922
+ vars: context.variables || {}, // Alias for convenience
923
+ outputs: context.outputs || {},
924
+ };
925
+ // Build scope object with workflow-specific data and context.eval()
926
+ // Note: AXPExpressionEvaluatorScope type expects { [namespace]: { [name]: Function } }
927
+ // but evaluate() method actually accepts flat objects with values too
928
+ // We'll use 'any' to allow both values and functions
929
+ const scope = {
930
+ // Direct access to workflow data
931
+ inputs: workflowData.inputs,
932
+ variables: workflowData.variables,
933
+ vars: workflowData.vars,
934
+ outputs: workflowData.outputs,
935
+ // Context object with eval function (similar to entity-detail-list pattern)
936
+ context: {
937
+ eval: (path) => {
938
+ // Use lodash get to access nested properties
939
+ // Supports paths like: "inputs.userName", "variables.count", "outputs.activityId.property"
940
+ return get(workflowData, path);
941
+ },
942
+ },
943
+ };
944
+ return scope;
945
+ }
946
+ /**
947
+ * Build expression evaluation scope from workflow instance state.
948
+ *
949
+ * Convenience method that extracts data from AXPWorkflowInstanceState.
950
+ *
951
+ * @param state - Workflow instance state
952
+ * @param activityOutputs - Map of activity outputs (activityId -> output)
953
+ * @returns Expression evaluator scope with workflow data and context.eval() function
954
+ */
955
+ buildScopeFromState(state, activityOutputs) {
956
+ // Convert activity outputs to record format
957
+ const outputs = {};
958
+ if (activityOutputs) {
959
+ if (activityOutputs instanceof Map) {
960
+ activityOutputs.forEach((output, activityId) => {
961
+ outputs[activityId] = output;
962
+ });
963
+ }
964
+ else {
965
+ Object.assign(outputs, activityOutputs);
966
+ }
967
+ }
968
+ return this.buildScope({
969
+ inputs: state.input || {},
970
+ variables: state.variables || {},
971
+ outputs: outputs,
972
+ });
973
+ }
974
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: WorkflowExpressionScopeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
975
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: WorkflowExpressionScopeService, providedIn: 'root' }); }
869
976
  }
870
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowExecutionService, decorators: [{
871
- type: Injectable
977
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: WorkflowExpressionScopeService, decorators: [{
978
+ type: Injectable,
979
+ args: [{
980
+ providedIn: 'root'
981
+ }]
872
982
  }] });
873
983
 
874
984
  //#endregion
875
985
  /**
876
- * Production Workflow Coordinator.
986
+ * Activity Executor Service
877
987
  *
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
988
+ * Service for executing workflow activities via CommandBus.
989
+ * Automatically evaluates expressions in activity inputs before execution.
990
+ *
991
+ * @example
992
+ * ```typescript
993
+ * const executor = inject(ActivityExecutor);
882
994
  *
995
+ * // Execute activity with task and workflow state (expressions will be evaluated)
996
+ * const result = await executor.execute(task, workflowState, activityOutputs);
997
+ * ```
883
998
  */
884
- class WorkflowCoordinator {
999
+ class ActivityExecutor {
885
1000
  constructor() {
886
1001
  //#region ---- Services & Dependencies ----
887
- this.workflowExecutionService = inject(AXPWorkflowExecutionService);
888
1002
  this.commandService = inject(AXPCommandService);
889
- //#endregion
890
- //#region ---- State Cache (Client-side for Performance) ----
891
- /**
892
- * Cache workflow states in memory for quick access.
893
- * Key: executionId
894
- * Value: AXPWorkflowExecutionState
895
- */
896
- this.stateCache = new Map();
1003
+ this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
1004
+ this.expressionScopeService = inject(WorkflowExpressionScopeService);
897
1005
  }
898
1006
  //#endregion
899
1007
  //#region ---- Public Methods ----
900
1008
  /**
901
- * Start workflow execution in backend.
902
- *
903
- * Backend decides what to do: returns pendingTask or indicates completion.
904
- * Frontend only calls API - no business logic here.
905
- *
906
- * @param workflowId - Workflow ID
907
- * @param input - Initial input data
908
- * @returns Execution result with pendingTask (if any)
909
- *
910
- * @example
911
- * ```typescript
912
- * const result = await coordinator.startWorkflow('my-workflow', { userId: '123' });
913
- *
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);
919
- * }
920
- * }
921
- * ```
922
- */
923
- async startWorkflow(workflowId, input = {}) {
924
- try {
925
- const execution = await this.startWorkflowExecution(workflowId, input);
926
- const result = {
927
- success: true,
928
- output: execution.state.output,
929
- nextTask: execution.pendingTask || null,
930
- executionId: execution.executionId,
931
- state: execution.state
932
- };
933
- return result;
934
- }
935
- catch (error) {
936
- console.error('[WorkflowCoordinator] ❌ Error in startWorkflow', error);
937
- return {
938
- success: false,
939
- error: error.message || 'Failed to start workflow',
940
- nextTask: null
941
- };
942
- }
943
- }
944
- /**
945
- * Execute a frontend task using AXPCommand.
1009
+ * Execute a workflow activity with expression evaluation.
946
1010
  *
947
- * Only executes if task.executionMode is 'frontend' or 'both'.
948
- * Backend tasks are handled automatically by backend.
1011
+ * Evaluates expressions in activity inputs using workflow state,
1012
+ * then executes the activity via CommandBus.
949
1013
  *
950
- * @param task - Task to execute
1014
+ * @param task - Workflow task containing activity information
1015
+ * @param workflowState - Current workflow instance state (for expression evaluation)
1016
+ * @param activityOutputs - Map of previous activity outputs (for expression evaluation)
951
1017
  * @returns Execution result with output and outcome
952
1018
  */
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);
959
- }
960
- /**
961
- * Complete a task and get next task from backend.
1019
+ async execute(task, workflowState, activityOutputs) {
1020
+ try {
1021
+ console.log('[ActivityExecutor] 🔍 Evaluating inputs:', task.input);
1022
+ const activityName = task.activityType;
1023
+ // Evaluate inputs if workflow state is provided
1024
+ let evaluatedInputs = task.input || {};
1025
+ if (workflowState) {
1026
+ // Build expression scope from workflow state
1027
+ const scope = this.expressionScopeService.buildScopeFromState(workflowState, activityOutputs);
1028
+ // Evaluate all inputs recursively (handles nested objects and arrays)
1029
+ evaluatedInputs = await this.expressionEvaluator.evaluate(task.input || {}, scope);
1030
+ console.log('[ActivityExecutor] 🔍 Evaluated inputs:', evaluatedInputs);
1031
+ }
1032
+ // Check if command exists
1033
+ const commandExists = this.commandService.exists(activityName);
1034
+ if (!commandExists) {
1035
+ console.warn(`[ActivityExecutor] ⚠️ Activity '${activityName}' is not registered as Command. ` +
1036
+ `Skipping execution.`);
1037
+ return {
1038
+ output: null,
1039
+ outcome: 'Done'
1040
+ };
1041
+ }
1042
+ // Flatten properties if nested (workflow-studio format)
1043
+ let commandInput = evaluatedInputs;
1044
+ if (commandInput['properties'] && typeof commandInput['properties'] === 'object') {
1045
+ // Flatten: {properties: {text: "..."}} -> {text: "..."}
1046
+ commandInput = { ...commandInput['properties'] };
1047
+ }
1048
+ // Execute activity via CommandBus
1049
+ // Activities registered as AXPCommand return {output, outcomes}
1050
+ const result = await this.commandService.execute(activityName, commandInput);
1051
+ if (!result) {
1052
+ return {
1053
+ output: null,
1054
+ outcome: 'Failed',
1055
+ };
1056
+ }
1057
+ if (!result.success) {
1058
+ return {
1059
+ output: {
1060
+ error: result.message?.text,
1061
+ },
1062
+ outcome: 'Failed',
1063
+ };
1064
+ }
1065
+ const commandResult = result.data;
1066
+ const outcomes = commandResult?.outcomes ?? {};
1067
+ // Determine outcome from command results
1068
+ // Default to 'Done' if no outcomes specified
1069
+ let outcome = 'Done';
1070
+ if (Object.keys(outcomes).length > 0) {
1071
+ outcome = outcomes['Done'] ? 'Done' : Object.keys(outcomes)[0] || 'Done';
1072
+ }
1073
+ return {
1074
+ output: commandResult?.output ?? null,
1075
+ outcome,
1076
+ };
1077
+ }
1078
+ catch (error) {
1079
+ console.error(`[ActivityExecutor] ❌ Error evaluating expressions or executing activity:`, error);
1080
+ return {
1081
+ output: { error: error.message || 'Unknown error' },
1082
+ outcome: 'Failed'
1083
+ };
1084
+ }
1085
+ }
1086
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityExecutor, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1087
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityExecutor, providedIn: 'root' }); }
1088
+ }
1089
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityExecutor, decorators: [{
1090
+ type: Injectable,
1091
+ args: [{
1092
+ providedIn: 'root'
1093
+ }]
1094
+ }] });
1095
+
1096
+ //#endregion
1097
+ /**
1098
+ * Workflow Manager - Facade for workflow lifecycle orchestration.
1099
+ *
1100
+ * This service is the ONLY interface the frontend uses to interact with workflows.
1101
+ * It follows Clean Architecture principles and does NOT contain execution or business logic.
1102
+ *
1103
+ * Responsibilities:
1104
+ * - Orchestrate workflow lifecycle (start, execute, complete, resume)
1105
+ * - Delegate execution to ActivityExecutor
1106
+ * - Cache workflow state in memory
1107
+ * - Expose a stable API for UI
1108
+ *
1109
+ * Rules:
1110
+ * - No HTTP calls (delegates to AXPWorkflowEngine)
1111
+ * - No CommandBus / Command execution (delegates to ActivityExecutor)
1112
+ * - No workflow branching logic (backend decides)
1113
+ * - No business validation (backend validates)
1114
+ * - No backend assumptions (uses abstract runtime service)
1115
+ */
1116
+ class AXPWorkflowManager {
1117
+ constructor() {
1118
+ //#region ---- Services & Dependencies ----
1119
+ this.workflowEngine = inject(AXP_WORKFLOW_ENGINE);
1120
+ this.activityExecutor = inject(ActivityExecutor);
1121
+ //#endregion
1122
+ //#region ---- State Cache ----
1123
+ /**
1124
+ * Cache workflow states in memory for quick access.
1125
+ * Key: instanceId
1126
+ * Value: AXPWorkflowInstanceState
1127
+ */
1128
+ this.stateCache = new Map();
1129
+ /**
1130
+ * Cache TTL in milliseconds (5 minutes).
1131
+ */
1132
+ this.CACHE_TTL = 5 * 60 * 1000;
1133
+ }
1134
+ //#endregion
1135
+ //#region ---- Public Methods ----
1136
+ /**
1137
+ * Start a new workflow instance.
1138
+ *
1139
+ * Creates a new workflow instance in backend and returns instance ID.
1140
+ * Backend decides what to do: returns pendingTask or indicates completion.
1141
+ *
1142
+ * @param workflowId - Workflow ID to start
1143
+ * @param input - Initial input data (optional)
1144
+ * @returns Start result with instanceId, state, and nextTask
1145
+ *
1146
+ * @example
1147
+ * ```typescript
1148
+ * const result = await workflowManager.start('my-workflow', { userId: '123' });
1149
+ *
1150
+ * if (result.success && result.nextTask) {
1151
+ * // Execute task if frontend
1152
+ * if (result.nextTask.executionMode === 'frontend') {
1153
+ * const execResult = await workflowManager.execute(result.nextTask);
1154
+ * await workflowManager.complete(result.instanceId!, result.nextTask, execResult.outcome, execResult.output);
1155
+ * }
1156
+ * }
1157
+ * ```
1158
+ */
1159
+ async start(workflowId, input = {}) {
1160
+ try {
1161
+ const response = await this.workflowEngine.start({
1162
+ workflowId,
1163
+ input
1164
+ });
1165
+ // Cache state (normalize Date)
1166
+ const startNormalizedState = { ...response.state };
1167
+ if (startNormalizedState.lastUpdated && !(startNormalizedState.lastUpdated instanceof Date)) {
1168
+ startNormalizedState.lastUpdated = new Date(startNormalizedState.lastUpdated);
1169
+ }
1170
+ this.stateCache.set(response.instanceId, startNormalizedState);
1171
+ return {
1172
+ success: true,
1173
+ instanceId: response.instanceId,
1174
+ state: startNormalizedState,
1175
+ nextTask: response.pendingTask || null,
1176
+ output: startNormalizedState.output
1177
+ };
1178
+ }
1179
+ catch (error) {
1180
+ console.error('[AXPWorkflowManager] ❌ Error starting workflow:', error);
1181
+ return {
1182
+ success: false,
1183
+ error: error.message || 'Failed to start workflow'
1184
+ };
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Execute a frontend task.
1189
+ *
1190
+ * Delegates to ActivityExecutor for actual execution.
1191
+ * Only executes tasks with executionMode 'frontend' or 'both'.
1192
+ *
1193
+ * Provides workflow state to executor for expression evaluation.
1194
+ *
1195
+ * @param task - Task to execute
1196
+ * @returns Execution result with output and outcome
1197
+ *
1198
+ * @throws Error if task is not a frontend task
1199
+ */
1200
+ async execute(task) {
1201
+ // Validate execution mode
1202
+ if (task.executionMode !== 'frontend' && task.executionMode !== 'both') {
1203
+ console.error(`[AXPWorkflowManager] ❌ Invalid execution mode: ${task.executionMode}`);
1204
+ throw new Error(`Task '${task.activityId}' is not a frontend task. ` +
1205
+ `Execution mode: ${task.executionMode}. Backend tasks are handled automatically.`);
1206
+ }
1207
+ // Find workflow state from cache by matching currentStepId with task.activityId
1208
+ // This is needed for expression evaluation
1209
+ let workflowState;
1210
+ for (const [instanceId, state] of this.stateCache.entries()) {
1211
+ if (state.currentStepId === task.activityId) {
1212
+ workflowState = state;
1213
+ break;
1214
+ }
1215
+ }
1216
+ // If not found in cache, try to get from backend
1217
+ if (!workflowState) {
1218
+ console.warn(`[AXPWorkflowManager] ⚠️ Workflow state not found in cache for task ${task.activityId}. Expression evaluation may not work correctly.`);
1219
+ }
1220
+ // TODO: Get activity outputs from workflow state
1221
+ // For now, pass undefined - executor will build scope from state
1222
+ return this.activityExecutor.execute(task, workflowState, undefined);
1223
+ }
1224
+ /**
1225
+ * Complete a task and get next task from backend.
962
1226
  *
963
1227
  * Sends task result to backend API.
964
1228
  * Backend decides: next task, fail, or complete workflow.
965
1229
  *
966
- * @param executionId - Execution ID
1230
+ * @param instanceId - Workflow instance ID
967
1231
  * @param task - Completed task
968
1232
  * @param outcome - Task outcome (e.g., 'Done', 'Confirmed', 'Cancelled')
969
- * @param output - Task output/result
970
- * @returns Next task from backend (if any)
1233
+ * @param output - Task output/result (optional)
1234
+ * @returns Complete result with next task (if any)
971
1235
  */
972
- async completeTask(executionId, task, outcome, output) {
1236
+ async complete(instanceId, task, outcome, output) {
973
1237
  try {
974
1238
  // Send result to backend - backend decides next step
975
- const response = await this.workflowExecutionService.resumeExecution({
976
- executionId,
1239
+ const response = await this.workflowEngine.resume({
1240
+ instanceId,
977
1241
  stepId: task.activityId,
978
1242
  taskToken: task.taskToken,
979
1243
  outcome,
980
1244
  userInput: output
981
1245
  });
982
1246
  // Update cache
1247
+ let completeNormalizedState = response.state;
983
1248
  if (response.state) {
984
- this.stateCache.set(executionId, response.state);
1249
+ // Normalize lastUpdated to Date (for cache safety)
1250
+ completeNormalizedState = { ...response.state };
1251
+ if (completeNormalizedState.lastUpdated && !(completeNormalizedState.lastUpdated instanceof Date)) {
1252
+ completeNormalizedState.lastUpdated = new Date(completeNormalizedState.lastUpdated);
1253
+ }
1254
+ this.stateCache.set(instanceId, completeNormalizedState);
985
1255
  }
986
1256
  return {
987
1257
  success: true,
988
- output: response.output,
1258
+ instanceId,
1259
+ state: completeNormalizedState,
989
1260
  nextTask: response.nextTask || null,
990
- executionId,
991
- state: response.state
1261
+ output: response.output
992
1262
  };
993
1263
  }
994
1264
  catch (error) {
1265
+ console.error('[AXPWorkflowManager] ❌ Error completing task:', error);
995
1266
  return {
996
1267
  success: false,
997
- error: error.message || 'Failed to complete task',
998
- nextTask: null
1268
+ instanceId,
1269
+ error: error.message || 'Failed to complete task'
999
1270
  };
1000
1271
  }
1001
1272
  }
1002
- /**
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
1273
  /**
1013
1274
  * Resume a suspended workflow (e.g., after user interaction).
1014
1275
  *
1015
1276
  * Backend determines nextStep based on outcome and outcomeConnections.
1016
- * Client only provides executionId, stepId, outcome, and optional userInput.
1277
+ * Client only provides instanceId, stepId, outcome, and optional userInput.
1017
1278
  *
1018
- * @param executionId - Workflow execution ID
1279
+ * @param instanceId - Workflow instance ID
1019
1280
  * @param stepId - Step ID that was waiting for user input
1020
1281
  * @param outcome - User action outcome (e.g., 'Confirmed', 'Cancelled', 'Submitted')
1021
1282
  * @param userInput - Optional user input data
1283
+ * @param taskToken - Secure task token (required for secure resumption)
1284
+ * @returns Resume result with next task (if any)
1022
1285
  */
1023
- async resumeWorkflow(executionId, stepId, outcome, userInput, taskToken) {
1286
+ async resume(instanceId, stepId, outcome, userInput, taskToken) {
1024
1287
  try {
1025
1288
  // Ensure taskToken is provided for secure resumption
1026
1289
  if (!taskToken) {
1027
- throw new Error('Missing taskToken for resumeWorkflow');
1290
+ throw new Error('Missing taskToken for resume operation');
1028
1291
  }
1029
1292
  // Backend handles everything: checks outcomeConnections and determines nextStep
1030
- const response = await this.workflowExecutionService.resumeExecution({
1031
- executionId,
1293
+ const response = await this.workflowEngine.resume({
1294
+ instanceId,
1032
1295
  stepId,
1033
1296
  taskToken,
1034
1297
  outcome,
@@ -1036,1375 +1299,875 @@ class WorkflowCoordinator {
1036
1299
  });
1037
1300
  // Update cache with state from backend
1038
1301
  if (response.state) {
1039
- this.stateCache.set(executionId, response.state);
1302
+ // Normalize lastUpdated to Date (for cache safety)
1303
+ const normalizedState = { ...response.state };
1304
+ if (normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
1305
+ normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
1306
+ }
1307
+ this.stateCache.set(instanceId, normalizedState);
1308
+ }
1309
+ // Normalize state Date for return
1310
+ const normalizedState = response.state ? { ...response.state } : undefined;
1311
+ if (normalizedState && normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
1312
+ normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
1040
1313
  }
1041
1314
  return {
1042
1315
  success: true,
1043
- output: response.output,
1316
+ instanceId,
1317
+ state: normalizedState || response.state,
1044
1318
  nextTask: response.nextTask || null, // Backend determines this from outcomeConnections
1045
- executionId,
1046
- state: response.state
1319
+ output: response.output
1047
1320
  };
1048
1321
  }
1049
1322
  catch (error) {
1323
+ console.error('[AXPWorkflowManager] ❌ Error resuming workflow:', error);
1050
1324
  return {
1051
1325
  success: false,
1052
- error: error.message || 'Failed to resume workflow',
1053
- nextTask: null
1326
+ instanceId,
1327
+ error: error.message || 'Failed to resume workflow'
1054
1328
  };
1055
1329
  }
1056
1330
  }
1057
1331
  /**
1058
- * Get workflow execution state (from cache or backend).
1332
+ * Get workflow instance state.
1333
+ *
1334
+ * Retrieves state from cache (if valid) or from backend.
1335
+ *
1336
+ * @param instanceId - Workflow instance ID
1337
+ * @returns Workflow instance state or null if not found
1059
1338
  */
1060
- async getWorkflowState(executionId) {
1339
+ async getState(instanceId) {
1061
1340
  // Check cache first
1062
- const cached = this.stateCache.get(executionId);
1341
+ const cached = this.stateCache.get(instanceId);
1063
1342
  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;
1343
+ // Normalize lastUpdated to Date (for cache safety)
1344
+ const normalizedCached = { ...cached };
1345
+ if (normalizedCached.lastUpdated && !(normalizedCached.lastUpdated instanceof Date)) {
1346
+ normalizedCached.lastUpdated = new Date(normalizedCached.lastUpdated);
1347
+ }
1348
+ // Validate cache age
1349
+ const cacheAge = Date.now() - normalizedCached.lastUpdated.getTime();
1350
+ if (cacheAge < this.CACHE_TTL) {
1351
+ return normalizedCached;
1068
1352
  }
1069
1353
  }
1070
1354
  // Fetch from backend
1071
1355
  try {
1072
- const state = await this.workflowExecutionService.getExecutionState({
1073
- executionId
1356
+ const state = await this.workflowEngine.getState({
1357
+ instanceId
1074
1358
  });
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
- };
1359
+ // Normalize lastUpdated to Date (for cache safety)
1360
+ const normalizedState = { ...state };
1361
+ if (normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
1362
+ normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
1119
1363
  }
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';
1133
- }
1134
- return {
1135
- output: commandResult?.output ?? null,
1136
- outcome,
1137
- };
1364
+ // Update cache
1365
+ this.stateCache.set(instanceId, normalizedState);
1366
+ return normalizedState;
1138
1367
  }
1139
1368
  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
- };
1369
+ console.error('[AXPWorkflowManager] ❌ Error getting workflow state:', error);
1370
+ return null;
1145
1371
  }
1146
1372
  }
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' }); }
1373
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1374
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowManager, providedIn: 'root' }); }
1166
1375
  }
1167
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: WorkflowCoordinator, decorators: [{
1376
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowManager, decorators: [{
1168
1377
  type: Injectable,
1169
1378
  args: [{
1170
1379
  providedIn: 'root'
1171
1380
  }]
1172
1381
  }] });
1173
1382
 
1174
- /**
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.
1182
- *
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
1383
+ const AXP_WORKFLOW_PROVIDER = new InjectionToken('AXP_WORKFLOW_PROVIDER', {
1384
+ factory: () => [],
1385
+ });
1386
+ const AXP_WORKFLOW_CATEGORY_PROVIDER = new InjectionToken('AXP_WORKFLOW_CATEGORY_PROVIDER', {
1387
+ factory: () => [],
1205
1388
  });
1206
1389
 
1390
+ //#endregion
1207
1391
  /**
1208
- * Sequence Activity - Executes activities in sequential order.
1392
+ * Local engine implementation that manages workflow progression and state.
1209
1393
  *
1210
- * Usage:
1211
- * ```typescript
1212
- * const sequence = new Sequence();
1213
- * sequence.activities = [activity1, activity2, activity3];
1214
- * await sequence.execute({});
1215
- * ```
1394
+ * This engine:
1395
+ * - Returns frontend/both activities as pendingTask (does NOT execute them)
1396
+ * - Skips backend activities (does not error, continues execution)
1397
+ * - Maintains workflow state in memory
1398
+ * - Does not require backend API calls
1399
+ *
1400
+ * Execution of frontend tasks is handled by AXPWorkflowManager via ActivityExecutor.
1401
+ * This engine only manages workflow progression and state storage.
1402
+ *
1403
+ * This is the DEFAULT engine provider. Applications can override it with
1404
+ * an API-based engine implementation.
1216
1405
  */
1217
- class Sequence extends Activity {
1406
+ class AXPWorkflowLocalEngine {
1218
1407
  constructor() {
1219
- super('Sequence');
1408
+ //#region ---- Services & Dependencies ----
1409
+ this.activityDefinitionService = inject(AXPActivityDefinitionService);
1410
+ this.workflowProviders = inject(AXP_WORKFLOW_PROVIDER, { optional: true }) || [];
1411
+ //#endregion
1412
+ //#region ---- Instance Storage ----
1220
1413
  /**
1221
- * Activities to execute in sequence.
1414
+ * In-memory storage for workflow instances.
1415
+ * Key: instanceId
1416
+ * Value: LocalWorkflowState
1222
1417
  */
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');
1418
+ this.instances = new Map();
1419
+ /**
1420
+ * Task token storage for secure resume operations.
1421
+ * Key: taskToken
1422
+ * Value: { instanceId, activityId }
1423
+ */
1424
+ this.taskTokens = new Map();
1243
1425
  }
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
1426
  //#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
- },
1427
+ //#region ---- Public Methods (AXPWorkflowEngine) ----
1428
+ /**
1429
+ * Start a new workflow instance.
1430
+ *
1431
+ * Creates an in-memory workflow instance and progresses it.
1432
+ * Frontend/both activities are returned as pendingTask for external execution.
1433
+ * Backend activities are skipped.
1434
+ */
1435
+ async start(request) {
1436
+ console.log(`[WorkflowLocalEngine] 🚀 Starting workflow: ${request.workflowId}`, request);
1437
+ // Generate instance ID
1438
+ const instanceId = AXPDataGenerator.uuid();
1439
+ const now = new Date();
1440
+ // Load workflow definition
1441
+ console.log(`[WorkflowLocalEngine] 📥 Loading workflow definition: ${request.workflowId}`);
1442
+ const definition = await this.getDefinition(request.workflowId);
1443
+ if (!definition) {
1444
+ console.error(`[WorkflowLocalEngine] Workflow definition not found: ${request.workflowId}`);
1445
+ throw new Error(`Workflow definition not found: ${request.workflowId}`);
1446
+ }
1447
+ console.log(`[WorkflowLocalEngine] ✅ Definition loaded:`, {
1448
+ name: definition.name,
1449
+ activitiesCount: definition.graph?.activities?.length || 0,
1450
+ connectionsCount: definition.graph?.connections?.length || 0,
1451
+ });
1452
+ // Initialize workflow state
1453
+ const state = {
1454
+ instanceId,
1455
+ workflowId: request.workflowId,
1456
+ status: 'running',
1457
+ variables: {},
1458
+ input: request.input || {},
1459
+ output: undefined,
1460
+ lastUpdated: now,
1461
+ };
1462
+ // Create local state
1463
+ const localState = {
1464
+ instanceId,
1465
+ workflowId: request.workflowId,
1466
+ definition,
1467
+ state,
1468
+ completedActivities: new Set(),
1469
+ activityResults: new Map(),
1470
+ };
1471
+ // Store instance
1472
+ this.instances.set(instanceId, localState);
1473
+ // Execute workflow steps
1474
+ console.log(`[WorkflowLocalEngine] ⚙️ Executing workflow steps...`);
1475
+ const pendingTask = await this.executeWorkflowSteps(localState);
1476
+ // Update state
1477
+ localState.state.lastUpdated = new Date();
1478
+ console.log(`[WorkflowLocalEngine] ✅ Workflow started:`, {
1479
+ instanceId,
1480
+ status: localState.state.status,
1481
+ hasPendingTask: !!pendingTask,
1482
+ pendingTaskType: pendingTask?.activityType,
1483
+ });
1484
+ return {
1485
+ instanceId,
1486
+ state: localState.state,
1487
+ pendingTask: pendingTask || null,
1488
+ };
1489
+ }
1490
+ /**
1491
+ * Resume a suspended workflow instance.
1492
+ *
1493
+ * Validates task token, applies externally executed result,
1494
+ * and continues progressing workflow steps.
1495
+ */
1496
+ async resume(request) {
1497
+ // Validate task token
1498
+ const tokenInfo = this.taskTokens.get(request.taskToken);
1499
+ if (!tokenInfo || tokenInfo.instanceId !== request.instanceId || tokenInfo.activityId !== request.stepId) {
1500
+ throw new Error('Invalid task token');
1501
+ }
1502
+ // Get instance
1503
+ const localState = this.instances.get(request.instanceId);
1504
+ if (!localState) {
1505
+ throw new Error(`Workflow instance not found: ${request.instanceId}`);
1506
+ }
1507
+ // Store activity result (from external execution)
1508
+ localState.activityResults.set(request.stepId, {
1509
+ output: request.userInput || {},
1510
+ outcome: request.outcome,
1511
+ });
1512
+ localState.completedActivities.add(request.stepId);
1513
+ // Merge output/userInput into state variables
1514
+ if (request.userInput) {
1515
+ localState.state.variables = {
1516
+ ...localState.state.variables,
1517
+ ...(request.userInput || {}),
1315
1518
  };
1316
1519
  }
1520
+ // Mark activity as completed and continue progression
1521
+ // Continue progressing workflow steps (skipping backend activities)
1522
+ const nextTask = await this.executeWorkflowSteps(localState);
1523
+ // Update state
1524
+ localState.state.lastUpdated = new Date();
1525
+ // Determine final status
1526
+ if (!nextTask && localState.state.status === 'running') {
1527
+ localState.state.status = 'completed';
1528
+ localState.state.output = localState.state.variables;
1529
+ }
1530
+ return {
1531
+ output: request.userInput || {},
1532
+ outcomes: { [request.outcome]: true },
1533
+ state: localState.state,
1534
+ nextTask: nextTask || null,
1535
+ };
1317
1536
  }
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
- },
1386
- };
1537
+ /**
1538
+ * Get current workflow instance state.
1539
+ */
1540
+ async getState(request) {
1541
+ const localState = this.instances.get(request.instanceId);
1542
+ if (!localState) {
1543
+ throw new Error(`Workflow instance not found: ${request.instanceId}`);
1544
+ }
1545
+ // Normalize lastUpdated to Date (for cache safety)
1546
+ const state = { ...localState.state };
1547
+ if (state.lastUpdated && !(state.lastUpdated instanceof Date)) {
1548
+ state.lastUpdated = new Date(state.lastUpdated);
1387
1549
  }
1550
+ return state;
1388
1551
  }
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
1552
  //#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;
1553
+ //#region ---- Private Methods ----
1554
+ /**
1555
+ * Get workflow definition from available providers.
1556
+ */
1557
+ async getDefinition(workflowId) {
1558
+ // Try all providers in order
1559
+ const resolvedProviders = await Promise.allSettled(this.workflowProviders);
1560
+ for (const p of resolvedProviders) {
1561
+ if (p.status === 'fulfilled' && p.value) {
1562
+ const provider = p.value;
1563
+ // Check if provider has getByName method
1564
+ if (typeof provider.getByName === 'function') {
1565
+ try {
1566
+ const definition = await provider.getByName(workflowId);
1567
+ if (definition) {
1568
+ return definition;
1569
+ }
1532
1570
  }
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;
1571
+ catch {
1572
+ // Continue on error - try other providers
1545
1573
  }
1546
1574
  }
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
- };
1601
- }
1602
- }
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');
1648
- }
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
- };
1575
+ }
1663
1576
  }
1577
+ return null;
1664
1578
  }
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);
1689
- }
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');
1579
+ /**
1580
+ * Progress workflow steps starting from the current position.
1581
+ *
1582
+ * For frontend/both activities: returns task immediately (suspends workflow).
1583
+ * For backend activities: skips and continues.
1584
+ *
1585
+ * Returns the next pending task (if frontend activity found) or null (if completed).
1586
+ */
1587
+ async executeWorkflowSteps(localState) {
1588
+ const graph = localState.definition.graph;
1589
+ const activities = graph.activities || [];
1590
+ const connections = graph.connections || [];
1591
+ // Build activity map
1592
+ const activityMap = new Map();
1593
+ activities.forEach(activity => {
1594
+ activityMap.set(activity.id, activity);
1595
+ });
1596
+ // Build connection graph
1597
+ const outgoingConnections = new Map();
1598
+ const incomingConnections = new Map();
1599
+ connections.forEach(conn => {
1600
+ const sourceId = conn.source.activtyName;
1601
+ const targetId = conn.target.activtyName;
1602
+ if (!outgoingConnections.has(sourceId)) {
1603
+ outgoingConnections.set(sourceId, []);
1604
+ }
1605
+ outgoingConnections.get(sourceId).push(conn);
1606
+ if (!incomingConnections.has(targetId)) {
1607
+ incomingConnections.set(targetId, []);
1608
+ }
1609
+ incomingConnections.get(targetId).push(conn);
1610
+ });
1611
+ // Find starting activity (use startActivityId or no incoming connections, or first activity)
1612
+ let currentActivityId = localState.currentActivityId;
1613
+ if (!currentActivityId) {
1614
+ // Use startActivityId if available
1615
+ if (graph.startActivityId) {
1616
+ currentActivityId = graph.startActivityId;
1617
+ }
1618
+ else {
1619
+ // Find root activity (no incoming connections)
1620
+ for (const activity of activities) {
1621
+ if (!incomingConnections.has(activity.id)) {
1622
+ currentActivityId = activity.id;
1623
+ break;
1708
1624
  }
1709
- 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
- };
1625
+ }
1626
+ // If no root found, use first activity
1627
+ if (!currentActivityId && activities.length > 0) {
1628
+ currentActivityId = activities[0].id;
1629
+ }
1724
1630
  }
1725
- return this.createResult(undefined, 'Done');
1726
1631
  }
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
- }
1742
- }
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');
1632
+ // Execute workflow steps
1633
+ while (currentActivityId) {
1634
+ const activity = activityMap.get(currentActivityId);
1635
+ if (!activity) {
1636
+ break;
1637
+ }
1638
+ // Skip if already completed
1639
+ if (localState.completedActivities.has(currentActivityId)) {
1640
+ // Move to next activity
1641
+ const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
1642
+ currentActivityId = nextId ?? undefined;
1643
+ continue;
1644
+ }
1645
+ // Get activity definition to retrieve executionMode and title
1646
+ console.log(`[WorkflowLocalEngine] 🔍 Getting activity definition for: ${activity.name}`);
1647
+ const activityDefinition = await this.activityDefinitionService.getActivityByName(activity.name);
1648
+ console.log(`[WorkflowLocalEngine] 📋 Activity definition:`, {
1649
+ name: activityDefinition?.name,
1650
+ type: activityDefinition?.type,
1651
+ executionMode: activityDefinition?.executionMode,
1652
+ title: activityDefinition?.title,
1653
+ found: !!activityDefinition,
1654
+ });
1655
+ const executionMode = activityDefinition?.executionMode || 'frontend';
1656
+ const activityTitle = activityDefinition?.title;
1657
+ // Handle backend activities: skip
1658
+ if (executionMode === 'backend') {
1659
+ console.log(`[WorkflowLocalEngine] ⏭️ Skipping backend activity: ${activity.name} (${activity.id})`);
1660
+ localState.completedActivities.add(currentActivityId);
1661
+ localState.activityResults.set(currentActivityId, {
1662
+ output: null,
1663
+ outcome: 'Done',
1664
+ });
1665
+ // Move to next activity
1666
+ const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
1667
+ currentActivityId = nextId ?? undefined;
1668
+ continue;
1669
+ }
1670
+ // Handle frontend/both activities: return as pendingTask (do NOT execute)
1671
+ if (executionMode === 'frontend' || executionMode === 'both') {
1672
+ // Create task for external execution
1673
+ // Note: Expression evaluation will be done by ActivityExecutor
1674
+ const task = {
1675
+ taskToken: AXPDataGenerator.uuid(),
1676
+ activityId: activity.id,
1677
+ activityType: activity.name,
1678
+ activityName: activityTitle || undefined,
1679
+ executionMode: executionMode,
1680
+ input: activity.inputs || {}, // Pass raw inputs - executor will evaluate
1681
+ config: activity.customProperties || {},
1682
+ };
1683
+ // Store task token for secure resume
1684
+ this.taskTokens.set(task.taskToken, {
1685
+ instanceId: localState.instanceId,
1686
+ activityId: activity.id,
1687
+ });
1688
+ // Update state to indicate suspension
1689
+ localState.currentActivityId = currentActivityId;
1690
+ localState.state.status = 'suspended';
1691
+ localState.state.currentStepId = currentActivityId;
1692
+ // Return task immediately - AXPWorkflowManager will execute it
1693
+ return task;
1694
+ }
1695
+ // Move to next activity
1696
+ const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
1697
+ currentActivityId = nextId ?? undefined;
1698
+ }
1699
+ // Workflow completed
1700
+ localState.state.status = 'completed';
1701
+ localState.state.output = localState.state.variables;
1702
+ localState.currentActivityId = undefined;
1703
+ return null;
1765
1704
  }
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');
1705
+ /**
1706
+ * Get next activity ID based on connections and outcomes.
1707
+ */
1708
+ getNextActivityId(currentActivityId, outgoingConnections, activityResults) {
1709
+ const connections = outgoingConnections.get(currentActivityId) || [];
1710
+ if (connections.length === 0) {
1711
+ return null; // No outgoing connections - workflow ends
1712
+ }
1713
+ // Get current activity result
1714
+ const result = activityResults.get(currentActivityId);
1715
+ const outcome = result?.outcome || 'Done';
1716
+ // Find connection matching outcome
1717
+ // Outcome matching is typically done via port name (e.g., "Done", "Failed")
1718
+ for (const conn of connections) {
1719
+ const sourcePort = conn.source.port || 'Done';
1720
+ if (sourcePort === outcome) {
1721
+ return conn.target.activtyName;
1722
+ }
1773
1723
  }
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
- };
1724
+ // If no matching outcome, use first connection (default path)
1725
+ if (connections.length > 0) {
1726
+ return connections[0].target.activtyName;
1788
1727
  }
1728
+ return null;
1789
1729
  }
1730
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowLocalEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1731
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowLocalEngine }); }
1790
1732
  }
1733
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowLocalEngine, decorators: [{
1734
+ type: Injectable
1735
+ }] });
1791
1736
 
1792
- var setVariable_activity = /*#__PURE__*/Object.freeze({
1793
- __proto__: null,
1794
- SetVariable: SetVariable
1795
- });
1737
+ // Workflow Runtime Services
1796
1738
 
1739
+ //#region ---- Imports ----
1740
+ //#endregion
1797
1741
  /**
1798
- * Dispatch Event Activity - Dispatches an event to the system.
1742
+ * Optimized Workflow Definition Service
1799
1743
  *
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
- * ```
1744
+ * Manages workflow definitions (metadata) for UI and tooling.
1745
+ * Similar to AXPActivityDefinitionService - only handles metadata, not execution.
1746
+ *
1747
+ * Performance optimizations:
1748
+ * 1. Uses childrenCount to determine if category has children (no query needed)
1749
+ * 2. Uses itemsCount to determine if category has workflows (no query needed)
1750
+ * 3. Aggressive caching prevents duplicate API calls
1751
+ * 4. Single pending request per resource prevents race conditions
1752
+ * 5. Lazy loading - only loads data when needed
1808
1753
  */
1809
- class DispatchEvent extends Activity {
1754
+ class AXPWorkflowDefinitionService {
1810
1755
  constructor() {
1811
- super('DispatchEvent');
1756
+ //#region ---- Providers & Caches ----
1757
+ this.categoryProviders = inject(AXP_WORKFLOW_CATEGORY_PROVIDER, { optional: true }) || [];
1758
+ this.workflowProviders = inject(AXP_WORKFLOW_PROVIDER, { optional: true }) || [];
1759
+ //#endregion
1760
+ //#region ---- Cache Storage ----
1761
+ /** Cache for categories by id - O(1) lookup */
1762
+ this.categoriesById = new Map();
1763
+ /** Cache for categories by parentId - O(1) lookup */
1764
+ this.categoriesByParentId = new Map();
1765
+ /** Cache for workflow definitions by categoryId - O(1) lookup */
1766
+ this.workflowsByCategory = new Map();
1767
+ /** Cache for individual workflow definitions by name - O(1) lookup */
1768
+ this.workflowsByName = new Map();
1769
+ /** Track which provider index owns each category (by category ID) */
1770
+ this.categoryOwnership = new Map(); // Maps categoryId → provider index
1771
+ /** Pending API requests to prevent duplicate calls */
1772
+ this.pendingCategoriesRequests = new Map();
1773
+ this.pendingWorkflowsRequests = new Map();
1774
+ this.pendingWorkflowRequests = new Map();
1812
1775
  }
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');
1776
+ //#endregion
1777
+ //#region ---- Public API: Categories ----
1778
+ /**
1779
+ * Get categories by parentId with aggressive caching
1780
+ *
1781
+ * Optimization: Returns cached result immediately if available,
1782
+ * preventing unnecessary API calls during navigation
1783
+ *
1784
+ * @param parentId - Parent category ID (undefined = root categories)
1785
+ * @returns Array of categories with count metadata (childrenCount, itemsCount)
1786
+ */
1787
+ async getCategories(parentId) {
1788
+ // ✅ Fast path: Return cached result
1789
+ if (this.categoriesByParentId.has(parentId)) {
1790
+ return this.categoriesByParentId.get(parentId);
1820
1791
  }
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
- };
1792
+ // Prevent duplicate requests: Return pending promise
1793
+ if (this.pendingCategoriesRequests.has(parentId)) {
1794
+ return this.pendingCategoriesRequests.get(parentId);
1835
1795
  }
1796
+ // ✅ Create single request and cache it
1797
+ const requestPromise = this.loadCategoriesFromProviders(parentId);
1798
+ this.pendingCategoriesRequests.set(parentId, requestPromise);
1799
+ return requestPromise;
1836
1800
  }
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' };
1801
+ /**
1802
+ * Get single category by ID with O(1) lookup
1803
+ *
1804
+ * Optimization: Uses Map for instant retrieval, falls back to
1805
+ * searching cache, then providers if not found
1806
+ */
1807
+ async getCategoryById(categoryId) {
1808
+ // ✅ Fast path: O(1) lookup in cache
1809
+ if (this.categoriesById.has(categoryId)) {
1810
+ return this.categoriesById.get(categoryId);
1811
+ }
1812
+ // Search in cached parent-child lists
1813
+ for (const categories of this.categoriesByParentId.values()) {
1814
+ const found = categories.find(cat => cat.id === categoryId);
1815
+ if (found) {
1816
+ this.categoriesById.set(categoryId, found);
1817
+ return found;
1869
1818
  }
1870
- else {
1871
- activities = elseActivities;
1872
- result = { branch: 'else' };
1873
- }
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
- }
1819
+ }
1820
+ // Load root categories if not loaded
1821
+ if (!this.categoriesByParentId.has(undefined)) {
1822
+ await this.getCategories();
1823
+ if (this.categoriesById.has(categoryId)) {
1824
+ return this.categoriesById.get(categoryId);
1889
1825
  }
1890
- return this.createResult(result, conditionResult ? 'Then' : 'Else');
1891
1826
  }
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
- };
1827
+ // Breadth-first search through hierarchy
1828
+ return this.searchCategoryInHierarchy(categoryId);
1829
+ }
1830
+ /**
1831
+ * Get category path from root to specified category
1832
+ *
1833
+ * Optimization: Builds path using cached categories only
1834
+ */
1835
+ async getCategoriesPathById(categoryId) {
1836
+ const path = [];
1837
+ let currentCategoryId = categoryId;
1838
+ while (currentCategoryId) {
1839
+ const category = await this.getCategoryById(currentCategoryId);
1840
+ if (!category) {
1841
+ throw new Error(`Category '${currentCategoryId}' not found`);
1842
+ }
1843
+ path.unshift(category);
1844
+ currentCategoryId = category.parentId;
1906
1845
  }
1846
+ return path;
1847
+ }
1848
+ //#endregion
1849
+ //#region ---- Public API: Workflow Definitions ----
1850
+ /**
1851
+ * Get workflow definitions for a category with smart caching
1852
+ *
1853
+ * Optimization: Checks itemsCount before querying
1854
+ * - If itemsCount = 0, returns empty array (no API call)
1855
+ * - If itemsCount > 0, loads and caches workflow definitions
1856
+ * - Returns cached result on subsequent calls
1857
+ *
1858
+ * @param categoryId - Category ID to get workflow definitions from
1859
+ * @returns Array of workflow definitions
1860
+ */
1861
+ async getWorkflowsByCategoryId(categoryId) {
1862
+ // ✅ Fast path: Return cached result
1863
+ if (this.workflowsByCategory.has(categoryId)) {
1864
+ return this.workflowsByCategory.get(categoryId);
1865
+ }
1866
+ // ✅ Smart optimization: Check itemsCount before querying
1867
+ const category = await this.getCategoryById(categoryId);
1868
+ if (category && category.itemsCount !== undefined && category.itemsCount === 0) {
1869
+ // Category has no workflows - cache empty array and skip API call
1870
+ const emptyArray = [];
1871
+ this.workflowsByCategory.set(categoryId, emptyArray);
1872
+ return emptyArray;
1873
+ }
1874
+ // ✅ Prevent duplicate requests
1875
+ if (this.pendingWorkflowsRequests.has(categoryId)) {
1876
+ return this.pendingWorkflowsRequests.get(categoryId);
1877
+ }
1878
+ // ✅ Load from providers
1879
+ const requestPromise = this.loadWorkflowsFromProviders(categoryId);
1880
+ this.pendingWorkflowsRequests.set(categoryId, requestPromise);
1881
+ return requestPromise;
1907
1882
  }
1908
- evaluateCondition(condition) {
1909
- if (typeof condition === 'boolean') {
1910
- return condition;
1883
+ /**
1884
+ * Get single workflow definition by name with O(1) lookup
1885
+ *
1886
+ * Optimization: Uses Map for instant retrieval
1887
+ *
1888
+ * @param name - Workflow name (unique identifier)
1889
+ * @returns Workflow definition or undefined if not found
1890
+ */
1891
+ async getWorkflowByName(name) {
1892
+ // ✅ Fast path: O(1) lookup in cache
1893
+ if (this.workflowsByName.has(name)) {
1894
+ return this.workflowsByName.get(name);
1911
1895
  }
1912
- if (typeof condition === 'string') {
1913
- // Simple evaluation - in real implementation, use proper expression evaluator
1914
- return condition === 'true' || condition === '1' || condition === 'yes';
1896
+ // Prevent duplicate requests
1897
+ if (this.pendingWorkflowRequests.has(name)) {
1898
+ return this.pendingWorkflowRequests.get(name);
1915
1899
  }
1916
- return false;
1900
+ // ✅ Load from providers
1901
+ const requestPromise = this.loadWorkflowFromProviders(name);
1902
+ this.pendingWorkflowRequests.set(name, requestPromise);
1903
+ return requestPromise;
1917
1904
  }
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');
1905
+ /**
1906
+ * Get category ID containing a specific workflow definition
1907
+ *
1908
+ * Optimization: Searches cache first, loads on-demand if needed
1909
+ */
1910
+ async getCategoryIdByWorkflowName(workflowName) {
1911
+ // ✅ Search in cached workflow definitions
1912
+ for (const [categoryId, definitions] of this.workflowsByCategory.entries()) {
1913
+ if (definitions.some(def => def.name === workflowName)) {
1914
+ return categoryId;
1915
+ }
1916
+ }
1917
+ // Try loading the workflow definition to find its category
1918
+ // Note: AXPWorkflowDefinition doesn't have category field
1919
+ // Category is managed separately through category providers
1920
+ // This method searches through cached categories
1921
+ const definition = await this.getWorkflowByName(workflowName);
1922
+ if (definition) {
1923
+ // Search through all categories to find which one contains this workflow
1924
+ const categories = await this.getCategories();
1925
+ for (const category of categories) {
1926
+ const workflows = await this.getWorkflowsByCategoryId(category.id);
1927
+ if (workflows.some(w => w.name === workflowName)) {
1928
+ return category.id;
1929
+ }
1930
+ }
1931
+ }
1932
+ return undefined;
1933
+ }
1934
+ /**
1935
+ * Get category path for a workflow
1936
+ */
1937
+ async getCategoriesPathByWorkflowName(workflowName) {
1938
+ const categoryId = await this.getCategoryIdByWorkflowName(workflowName);
1939
+ if (!categoryId) {
1940
+ throw new Error(`Workflow '${workflowName}' not found in any category`);
1941
+ }
1942
+ return this.getCategoriesPathById(categoryId);
1938
1943
  }
1939
- async execute(input) {
1940
- const { condition, activities = [], maxIterations = 1000 } = input;
1944
+ //#endregion
1945
+ //#region ---- Private: Data Loading ----
1946
+ /**
1947
+ * Load categories from providers and cache results
1948
+ *
1949
+ * Optimization: Tracks provider ownership to avoid unnecessary API calls
1950
+ * - For root (parentId = undefined): Query ALL providers
1951
+ * - For children: Only query the provider that owns the parent
1952
+ */
1953
+ async loadCategoriesFromProviders(parentId) {
1941
1954
  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
- };
1955
+ const resolvedProviders = await Promise.allSettled(this.categoryProviders);
1956
+ const categories = [];
1957
+ // Determine which provider(s) to query
1958
+ const providerIndicesToQuery = parentId
1959
+ ? this.getProviderIndexForCategory(parentId)
1960
+ : null; // Root: query all providers
1961
+ for (let i = 0; i < resolvedProviders.length; i++) {
1962
+ const p = resolvedProviders[i];
1963
+ // Skip if we have a specific provider index and this isn't it
1964
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
1965
+ continue;
1966
+ }
1967
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
1968
+ try {
1969
+ const cats = await p.value.getList(parentId);
1970
+ if (Array.isArray(cats) && cats.length > 0) {
1971
+ categories.push(...cats);
1972
+ // ✅ Track ownership: This provider INDEX owns these categories
1973
+ cats.forEach(cat => this.categoryOwnership.set(cat.id, i));
1974
+ }
1975
+ }
1976
+ catch {
1977
+ // Continue on error - try other providers
1962
1978
  }
1963
1979
  }
1964
- iteration++;
1965
- conditionResult = this.evaluateCondition(condition);
1966
1980
  }
1967
- const result = {
1968
- iterations: iteration,
1969
- completed: iteration < maxIterations
1970
- };
1971
- return this.createResult(result, 'Done');
1981
+ // Cache results for fast subsequent access
1982
+ this.categoriesByParentId.set(parentId, categories);
1983
+ categories.forEach(cat => this.categoriesById.set(cat.id, cat));
1984
+ return categories;
1972
1985
  }
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
- };
1986
+ finally {
1987
+ this.pendingCategoriesRequests.delete(parentId);
1987
1988
  }
1988
1989
  }
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';
1990
+ /**
1991
+ * Get the provider index that owns a specific category
1992
+ *
1993
+ * @returns Array with provider index, or null if ownership unknown (query all)
1994
+ */
1995
+ getProviderIndexForCategory(categoryId) {
1996
+ const ownerIndex = this.categoryOwnership.get(categoryId);
1997
+ if (ownerIndex !== undefined) {
1998
+ return [ownerIndex];
1996
1999
  }
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');
2000
+ // Ownership unknown - will query all providers (fallback)
2001
+ return null;
2021
2002
  }
2022
- async execute(input) {
2023
- const { items = [], activities = [], itemVariableName = 'currentItem', indexVariableName = 'currentIndex' } = input;
2003
+ /**
2004
+ * Load workflow definitions from providers and cache results
2005
+ *
2006
+ * Optimization: Only queries the provider that owns the category
2007
+ * Uses provider INDEX to match category provider with workflow provider
2008
+ */
2009
+ async loadWorkflowsFromProviders(categoryId) {
2024
2010
  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
- };
2011
+ const resolvedProviders = await Promise.allSettled(this.workflowProviders);
2012
+ const definitions = [];
2013
+ // Smart routing: Get provider INDEX that owns this category
2014
+ const ownerIndex = this.categoryOwnership.get(categoryId);
2015
+ const providerIndicesToQuery = ownerIndex !== undefined ? [ownerIndex] : null;
2016
+ for (let i = 0; i < resolvedProviders.length; i++) {
2017
+ const p = resolvedProviders[i];
2018
+ // Skip if we have a specific provider index and this isn't it
2019
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
2020
+ continue;
2021
+ }
2022
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
2023
+ try {
2024
+ const defs = await p.value.getList(categoryId);
2025
+ if (Array.isArray(defs)) {
2026
+ definitions.push(...defs);
2027
+ }
2028
+ }
2029
+ catch {
2030
+ // Continue on error - try other providers
2052
2031
  }
2053
2032
  }
2054
- results.push({
2055
- item: currentItem,
2056
- index,
2057
- processed: true
2058
- });
2059
2033
  }
2060
- const result = {
2061
- totalItems: items.length,
2062
- processedItems: results.length,
2063
- results
2064
- };
2065
- return this.createResult(result, 'Done');
2034
+ // Cache results for fast subsequent access
2035
+ this.workflowsByCategory.set(categoryId, definitions);
2036
+ definitions.forEach(def => {
2037
+ if (def.name) {
2038
+ this.workflowsByName.set(def.name, def);
2039
+ }
2040
+ });
2041
+ return definitions;
2066
2042
  }
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
- };
2043
+ finally {
2044
+ this.pendingWorkflowsRequests.delete(categoryId);
2081
2045
  }
2082
2046
  }
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;
2047
+ /**
2048
+ * Load single workflow definition from providers and cache result
2049
+ */
2050
+ async loadWorkflowFromProviders(name) {
2109
2051
  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
- };
2052
+ const resolvedProviders = await Promise.allSettled(this.workflowProviders);
2053
+ // Try providers first
2054
+ for (const p of resolvedProviders) {
2055
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getByName === 'function') {
2056
+ try {
2057
+ const definition = await p.value.getByName(name);
2058
+ if (definition) {
2059
+ this.workflowsByName.set(name, definition);
2060
+ return definition;
2061
+ }
2062
+ }
2063
+ catch {
2064
+ // Continue on error
2065
+ }
2066
+ }
2142
2067
  }
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
- };
2068
+ // Fallback: Search in cached workflow definitions
2069
+ for (const definitions of this.workflowsByCategory.values()) {
2070
+ const found = definitions.find(def => def.name === name);
2071
+ if (found) {
2072
+ this.workflowsByName.set(name, found);
2073
+ return found;
2074
+ }
2159
2075
  }
2160
- return this.createResult({
2161
- commandKey,
2162
- success: true,
2163
- output: result.data,
2164
- executedAt: new Date().toISOString(),
2165
- }, 'Done');
2076
+ return undefined;
2166
2077
  }
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
- };
2078
+ finally {
2079
+ this.pendingWorkflowRequests.delete(name);
2185
2080
  }
2186
2081
  }
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');
2082
+ /**
2083
+ * Breadth-first search through category hierarchy
2084
+ */
2085
+ async searchCategoryInHierarchy(categoryId) {
2086
+ const searchQueue = [undefined];
2087
+ const searched = new Set();
2088
+ while (searchQueue.length > 0) {
2089
+ const parentId = searchQueue.shift();
2090
+ if (searched.has(parentId))
2091
+ continue;
2092
+ searched.add(parentId);
2093
+ const categories = await this.getCategories(parentId);
2094
+ const found = categories.find(cat => cat.id === categoryId);
2095
+ if (found) {
2096
+ return found;
2097
+ }
2098
+ // Optimization: Only search children if childrenCount > 0
2099
+ for (const category of categories) {
2100
+ if (category.childrenCount > 0 && !searched.has(category.id)) {
2101
+ searchQueue.push(category.id);
2102
+ }
2227
2103
  }
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
2104
  }
2105
+ return undefined;
2257
2106
  }
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');
2107
+ //#endregion
2108
+ //#region ---- Cache Management ----
2109
+ /**
2110
+ * Check if category has children (uses cached count)
2111
+ */
2112
+ categoryHasChildren(categoryId) {
2113
+ const category = this.categoriesById.get(categoryId);
2114
+ return category ? category.childrenCount > 0 : false;
2280
2115
  }
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');
2116
+ /**
2117
+ * Check if category has workflows (uses cached count)
2118
+ */
2119
+ categoryHasWorkflows(categoryId) {
2120
+ const category = this.categoriesById.get(categoryId);
2121
+ return category ? (category.itemsCount ?? 0) > 0 : false;
2286
2122
  }
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');
2123
+ /**
2124
+ * Clear all caches
2125
+ */
2126
+ clearAllCache() {
2127
+ this.categoriesById.clear();
2128
+ this.categoriesByParentId.clear();
2129
+ this.workflowsByCategory.clear();
2130
+ this.workflowsByName.clear();
2131
+ this.categoryOwnership.clear();
2132
+ this.pendingCategoriesRequests.clear();
2133
+ this.pendingWorkflowsRequests.clear();
2134
+ this.pendingWorkflowRequests.clear();
2309
2135
  }
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');
2136
+ /**
2137
+ * Clear categories cache only
2138
+ */
2139
+ clearCategoriesCache() {
2140
+ this.categoriesById.clear();
2141
+ this.categoriesByParentId.clear();
2142
+ this.categoryOwnership.clear();
2143
+ this.pendingCategoriesRequests.clear();
2314
2144
  }
2145
+ /**
2146
+ * Clear workflows cache only
2147
+ */
2148
+ clearWorkflowsCache() {
2149
+ this.workflowsByCategory.clear();
2150
+ this.workflowsByName.clear();
2151
+ this.pendingWorkflowsRequests.clear();
2152
+ this.pendingWorkflowRequests.clear();
2153
+ }
2154
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2155
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowDefinitionService, providedIn: 'root' }); }
2315
2156
  }
2157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowDefinitionService, decorators: [{
2158
+ type: Injectable,
2159
+ args: [{
2160
+ providedIn: 'root',
2161
+ }]
2162
+ }] });
2316
2163
 
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
2399
-
2400
- // Workflow Definition Types (Storage/Database)
2164
+ // Workflow Instance Types (Storage/Database)
2401
2165
 
2402
2166
  class AXPWorkflowModule {
2403
2167
  static forRoot(config) {
2404
2168
  return {
2405
2169
  ngModule: AXPWorkflowModule,
2406
2170
  providers: [
2407
- provideWorkflowActivityCommands(),
2408
2171
  {
2409
2172
  provide: 'AXPWorkflowModuleFactory',
2410
2173
  useFactory: (registry) => () => {
@@ -2479,26 +2242,34 @@ class AXPWorkflowModule {
2479
2242
  * @ignore
2480
2243
  */
2481
2244
  constructor(instances) {
2482
- // Inject AXP_COMMAND_SETUP to trigger command registration factory
2483
- this._commandSetup = inject(AXP_COMMAND_SETUP, { optional: true });
2484
2245
  instances?.forEach((f) => {
2485
2246
  f();
2486
2247
  });
2487
2248
  }
2488
- 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
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule }); }
2490
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule, providers: [
2491
- provideWorkflowActivityCommands(),
2249
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowModule, deps: [{ token: 'AXPWorkflowModuleFactory', optional: true }], target: i0.ɵɵFactoryTarget.NgModule }); }
2250
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowModule }); }
2251
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowModule, providers: [
2252
+ AXPWorkflowLocalEngine,
2253
+ {
2254
+ provide: AXP_WORKFLOW_ENGINE,
2255
+ useExisting: AXPWorkflowLocalEngine,
2256
+ },
2257
+ AXPWorkflowManager,
2492
2258
  ] }); }
2493
2259
  }
2494
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWorkflowModule, decorators: [{
2260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPWorkflowModule, decorators: [{
2495
2261
  type: NgModule,
2496
2262
  args: [{
2497
2263
  imports: [],
2498
2264
  exports: [],
2499
2265
  declarations: [],
2500
2266
  providers: [
2501
- provideWorkflowActivityCommands(),
2267
+ AXPWorkflowLocalEngine,
2268
+ {
2269
+ provide: AXP_WORKFLOW_ENGINE,
2270
+ useExisting: AXPWorkflowLocalEngine,
2271
+ },
2272
+ AXPWorkflowManager,
2502
2273
  ],
2503
2274
  }]
2504
2275
  }], ctorParameters: () => [{ type: undefined, decorators: [{
@@ -2512,5 +2283,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
2512
2283
  * Generated bundle index. Do not edit.
2513
2284
  */
2514
2285
 
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 };
2286
+ 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_ENGINE, AXP_WORKFLOW_PROVIDER, ActivityExecutor, WorkflowExpressionScopeService, createWorkFlowEvent, ofType };
2516
2287
  //# sourceMappingURL=acorex-platform-workflow.mjs.map