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