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