@acorex-platform/framework-client 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +7 -0
  2. package/auth/README.md +3 -0
  3. package/common/README.md +3 -0
  4. package/core/README.md +4 -0
  5. package/fesm2022/acorex-platform-framework-client-auth.mjs +1383 -0
  6. package/fesm2022/acorex-platform-framework-client-auth.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-CbzOYCnI.mjs +190 -0
  8. package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-CbzOYCnI.mjs.map +1 -0
  9. package/fesm2022/acorex-platform-framework-client-common.mjs +9467 -0
  10. package/fesm2022/acorex-platform-framework-client-common.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-framework-client-core.mjs +4655 -0
  12. package/fesm2022/acorex-platform-framework-client-core.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-framework-client-domain.mjs +3189 -0
  14. package/fesm2022/acorex-platform-framework-client-domain.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-framework-client-layout-builder.mjs +3439 -0
  16. package/fesm2022/acorex-platform-framework-client-layout-builder.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-CWVU3wXL.mjs +134 -0
  18. package/fesm2022/acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-CWVU3wXL.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-framework-client-layout-components.mjs +9777 -0
  20. package/fesm2022/acorex-platform-framework-client-layout-components.mjs.map +1 -0
  21. package/fesm2022/acorex-platform-framework-client-layout-designer.mjs +2799 -0
  22. package/fesm2022/acorex-platform-framework-client-layout-designer.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-framework-client-layout-entity-attachments-page.component-CxBxxpmA.mjs +376 -0
  24. package/fesm2022/acorex-platform-framework-client-layout-entity-attachments-page.component-CxBxxpmA.mjs.map +1 -0
  25. package/fesm2022/acorex-platform-framework-client-layout-entity-file-list-popup.component-l0JAHKIH.mjs +100 -0
  26. package/fesm2022/acorex-platform-framework-client-layout-entity-file-list-popup.component-l0JAHKIH.mjs.map +1 -0
  27. package/fesm2022/acorex-platform-framework-client-layout-entity.mjs +26299 -0
  28. package/fesm2022/acorex-platform-framework-client-layout-entity.mjs.map +1 -0
  29. package/fesm2022/acorex-platform-framework-client-layout-views.mjs +2960 -0
  30. package/fesm2022/acorex-platform-framework-client-layout-views.mjs.map +1 -0
  31. package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs +4711 -0
  32. package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs.map +1 -0
  33. package/fesm2022/acorex-platform-framework-client-layout-widgets-button-widget-designer.component-CpkWwygb.mjs +72 -0
  34. package/fesm2022/acorex-platform-framework-client-layout-widgets-button-widget-designer.component-CpkWwygb.mjs.map +1 -0
  35. package/fesm2022/acorex-platform-framework-client-layout-widgets-image-preview.popup-C_EPAvCU.mjs +29 -0
  36. package/fesm2022/acorex-platform-framework-client-layout-widgets-image-preview.popup-C_EPAvCU.mjs.map +1 -0
  37. package/fesm2022/acorex-platform-framework-client-layout-widgets-page-widget-designer.component-CmtTq2Mt.mjs +172 -0
  38. package/fesm2022/acorex-platform-framework-client-layout-widgets-page-widget-designer.component-CmtTq2Mt.mjs.map +1 -0
  39. package/fesm2022/acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-DSx0E3NV.mjs +513 -0
  40. package/fesm2022/acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-DSx0E3NV.mjs.map +1 -0
  41. package/fesm2022/acorex-platform-framework-client-layout-widgets-tabular-data-edit-popup.component-DdfcGV1C.mjs +274 -0
  42. package/fesm2022/acorex-platform-framework-client-layout-widgets-tabular-data-edit-popup.component-DdfcGV1C.mjs.map +1 -0
  43. package/fesm2022/acorex-platform-framework-client-layout-widgets-tabular-data-view-popup.component-CntopmDS.mjs +64 -0
  44. package/fesm2022/acorex-platform-framework-client-layout-widgets-tabular-data-view-popup.component-CntopmDS.mjs.map +1 -0
  45. package/fesm2022/acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-B5kSWxGk.mjs +34 -0
  46. package/fesm2022/acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-B5kSWxGk.mjs.map +1 -0
  47. package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs +28553 -0
  48. package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs.map +1 -0
  49. package/fesm2022/acorex-platform-framework-client-native.mjs +155 -0
  50. package/fesm2022/acorex-platform-framework-client-native.mjs.map +1 -0
  51. package/fesm2022/acorex-platform-framework-client-runtime.mjs +911 -0
  52. package/fesm2022/acorex-platform-framework-client-runtime.mjs.map +1 -0
  53. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-DnTHwsDJ.mjs +160 -0
  54. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-DnTHwsDJ.mjs.map +1 -0
  55. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BpR9g6QS.mjs +121 -0
  56. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-BpR9g6QS.mjs.map +1 -0
  57. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-single-view.component-D1a7UI71.mjs +236 -0
  58. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-single-view.component-D1a7UI71.mjs.map +1 -0
  59. package/fesm2022/acorex-platform-framework-client-themes-default-error-401.component-D--_MIar.mjs +48 -0
  60. package/fesm2022/acorex-platform-framework-client-themes-default-error-401.component-D--_MIar.mjs.map +1 -0
  61. package/fesm2022/acorex-platform-framework-client-themes-default-error-404.component-Byyqk8pH.mjs +42 -0
  62. package/fesm2022/acorex-platform-framework-client-themes-default-error-404.component-Byyqk8pH.mjs.map +1 -0
  63. package/fesm2022/acorex-platform-framework-client-themes-default-error-offline.component-DR6G8gPC.mjs +19 -0
  64. package/fesm2022/acorex-platform-framework-client-themes-default-error-offline.component-DR6G8gPC.mjs.map +1 -0
  65. package/fesm2022/acorex-platform-framework-client-themes-default.mjs +4160 -0
  66. package/fesm2022/acorex-platform-framework-client-themes-default.mjs.map +1 -0
  67. package/fesm2022/acorex-platform-framework-client-themes-shared-icon-chooser-column.component-CXSGiTCK.mjs +55 -0
  68. package/fesm2022/acorex-platform-framework-client-themes-shared-icon-chooser-column.component-CXSGiTCK.mjs.map +1 -0
  69. package/fesm2022/acorex-platform-framework-client-themes-shared-icon-chooser-view.component-BY5clQ1J.mjs +57 -0
  70. package/fesm2022/acorex-platform-framework-client-themes-shared-icon-chooser-view.component-BY5clQ1J.mjs.map +1 -0
  71. package/fesm2022/acorex-platform-framework-client-themes-shared-settings.provider-DEL2YOTP.mjs +234 -0
  72. package/fesm2022/acorex-platform-framework-client-themes-shared-settings.provider-DEL2YOTP.mjs.map +1 -0
  73. package/fesm2022/acorex-platform-framework-client-themes-shared-theme-color-chooser-column.component-BXl8u4Yp.mjs +94 -0
  74. package/fesm2022/acorex-platform-framework-client-themes-shared-theme-color-chooser-column.component-BXl8u4Yp.mjs.map +1 -0
  75. package/fesm2022/acorex-platform-framework-client-themes-shared-theme-color-chooser-view.component-i2sOOkVj.mjs +86 -0
  76. package/fesm2022/acorex-platform-framework-client-themes-shared-theme-color-chooser-view.component-i2sOOkVj.mjs.map +1 -0
  77. package/fesm2022/acorex-platform-framework-client-themes-shared.mjs +2375 -0
  78. package/fesm2022/acorex-platform-framework-client-themes-shared.mjs.map +1 -0
  79. package/fesm2022/acorex-platform-framework-client-workflow.mjs +2836 -0
  80. package/fesm2022/acorex-platform-framework-client-workflow.mjs.map +1 -0
  81. package/fesm2022/acorex-platform-framework-client.mjs +6 -0
  82. package/fesm2022/acorex-platform-framework-client.mjs.map +1 -0
  83. package/layout/builder/README.md +1578 -0
  84. package/layout/components/README.md +3 -0
  85. package/layout/designer/README.md +4 -0
  86. package/layout/entity/README.md +4 -0
  87. package/layout/views/README.md +3 -0
  88. package/layout/widget-core/README.md +4 -0
  89. package/layout/widgets/README.md +3 -0
  90. package/native/README.md +4 -0
  91. package/package.json +98 -0
  92. package/runtime/README.md +3 -0
  93. package/themes/default/README.md +3 -0
  94. package/themes/shared/README.md +3 -0
  95. package/types/acorex-platform-framework-client-auth.d.ts +690 -0
  96. package/types/acorex-platform-framework-client-common.d.ts +3287 -0
  97. package/types/acorex-platform-framework-client-core.d.ts +1849 -0
  98. package/types/acorex-platform-framework-client-domain.d.ts +1968 -0
  99. package/types/acorex-platform-framework-client-layout-builder.d.ts +1058 -0
  100. package/types/acorex-platform-framework-client-layout-components.d.ts +3362 -0
  101. package/types/acorex-platform-framework-client-layout-designer.d.ts +501 -0
  102. package/types/acorex-platform-framework-client-layout-entity.d.ts +4374 -0
  103. package/types/acorex-platform-framework-client-layout-views.d.ts +975 -0
  104. package/types/acorex-platform-framework-client-layout-widget-core.d.ts +1080 -0
  105. package/types/acorex-platform-framework-client-layout-widgets.d.ts +5263 -0
  106. package/types/acorex-platform-framework-client-native.d.ts +28 -0
  107. package/types/acorex-platform-framework-client-runtime.d.ts +301 -0
  108. package/types/acorex-platform-framework-client-themes-default.d.ts +889 -0
  109. package/types/acorex-platform-framework-client-themes-shared.d.ts +202 -0
  110. package/types/acorex-platform-framework-client-workflow.d.ts +1345 -0
  111. package/types/acorex-platform-framework-client.d.ts +2 -0
  112. package/workflow/README.md +4 -0
@@ -0,0 +1,2836 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, inject, InjectionToken, Optional, Inject, NgModule } from '@angular/core';
3
+ import { Subject, filter } from 'rxjs';
4
+ import { cloneDeep, get, set } from 'lodash-es';
5
+ import { setSmart } from '@acorex-platform/framework-shared/core';
6
+ import { AXPContextEvalFactory, AXPExpressionEvaluatorService, AXPDebugService, AXPDataGenerator } from '@acorex-platform/framework-client/core';
7
+ import { AXTranslationService } from '@acorex/core/translation';
8
+ import { AXPCommandService } from '@acorex-platform/framework-client/runtime';
9
+
10
+ class AXPWorkflowError extends Error {
11
+ constructor(message, inner = null) {
12
+ super(message);
13
+ this.inner = inner;
14
+ this.name = 'AXPWorkflowError';
15
+ }
16
+ }
17
+
18
+ class AXPWorkflowEventService {
19
+ constructor() {
20
+ this.eventSubject = new Subject();
21
+ }
22
+ dispatch(event) {
23
+ this.eventSubject.next(event);
24
+ }
25
+ get events$() {
26
+ return this.eventSubject.asObservable();
27
+ }
28
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowEventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
29
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowEventService, providedIn: 'root' }); }
30
+ }
31
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowEventService, decorators: [{
32
+ type: Injectable,
33
+ args: [{
34
+ providedIn: 'root',
35
+ }]
36
+ }] });
37
+
38
+ class AXPWorkflowContext {
39
+ constructor(initialData = {}) {
40
+ this.variables = {};
41
+ this.stepOutputs = new Map();
42
+ this.variables = cloneDeep(initialData);
43
+ }
44
+ setVariable(key, value) {
45
+ setSmart(this.variables, key, value);
46
+ }
47
+ setVariables(context = {}) {
48
+ this.variables = { ...this.variables, ...context };
49
+ }
50
+ getVariable(key) {
51
+ return !key ? this.variables : get(this.variables, key);
52
+ }
53
+ setOutput(key, output) {
54
+ this.stepOutputs.set(key, output);
55
+ }
56
+ setOutputs(values) {
57
+ this.stepOutputs = new Map([...this.stepOutputs, ...values]);
58
+ }
59
+ getOutput(key) {
60
+ return this.stepOutputs.get(key);
61
+ }
62
+ }
63
+ class AXPWorkflowAction {
64
+ constructor() {
65
+ this.eventService = inject(AXPWorkflowEventService);
66
+ }
67
+ dispatch(event) {
68
+ this.eventService.dispatch(event);
69
+ }
70
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowAction, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
71
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowAction }); }
72
+ }
73
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowAction, decorators: [{
74
+ type: Injectable
75
+ }] });
76
+ class AXPWorkflowFunction {
77
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowFunction, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
78
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowFunction }); }
79
+ }
80
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowFunction, decorators: [{
81
+ type: Injectable
82
+ }] });
83
+ function createWorkFlowEvent(type) {
84
+ const eventCreator = (payload) => ({ type, payload });
85
+ eventCreator.type = type;
86
+ return eventCreator;
87
+ }
88
+ function ofType(...allowedTypes) {
89
+ return filter((event) => allowedTypes.some((type) => event.type === type.type));
90
+ }
91
+
92
+ //#region ---- Contracts barrel ----
93
+ //#endregion
94
+
95
+ class AXPWorkflowRegistryService {
96
+ constructor() {
97
+ this.functionsMap = new Map();
98
+ this.actionsMap = new Map();
99
+ this.workflowsMap = new Map();
100
+ }
101
+ registerWorkflow(name, workflow) {
102
+ this.workflowsMap.set(name, workflow);
103
+ }
104
+ getWorkflow(name) {
105
+ return this.workflowsMap.get(name);
106
+ }
107
+ registerAction(name, action) {
108
+ this.actionsMap.set(name, action);
109
+ }
110
+ getAction(name) {
111
+ return this.actionsMap.get(name);
112
+ }
113
+ registerFunction(name, func) {
114
+ this.functionsMap.set(name, func);
115
+ }
116
+ getFunction(name) {
117
+ return this.functionsMap.get(name);
118
+ }
119
+ getFunctionNames() {
120
+ return Array.from(this.functionsMap.keys());
121
+ }
122
+ getStep(workflowName, stepName) {
123
+ const workflow = this.workflowsMap.get(workflowName);
124
+ if (workflow) {
125
+ const steps = workflow.steps;
126
+ return steps[stepName];
127
+ }
128
+ return undefined;
129
+ }
130
+ updateStepInWorkflow(workflowName, stepName, step) {
131
+ const workflow = this.workflowsMap.get(workflowName);
132
+ if (workflow) {
133
+ const steps = workflow.steps;
134
+ if (steps[stepName]) {
135
+ steps[stepName] = step;
136
+ }
137
+ else {
138
+ throw new Error(`Step with name ${stepName} does not exist in workflow ${workflowName}.`);
139
+ }
140
+ }
141
+ else {
142
+ throw new Error(`Workflow with name ${workflowName} does not exist.`);
143
+ }
144
+ }
145
+ addStepToWorkflow(workflowName, stepName, step) {
146
+ const workflow = this.workflowsMap.get(workflowName);
147
+ if (workflow) {
148
+ const steps = workflow.steps;
149
+ if (!steps[stepName]) {
150
+ steps[stepName] = step;
151
+ }
152
+ else {
153
+ throw new Error(`Step with name ${stepName} already exists in workflow ${workflowName}.`);
154
+ }
155
+ }
156
+ else {
157
+ throw new Error(`Workflow with name ${workflowName} does not exist.`);
158
+ }
159
+ }
160
+ addAfterStep(workflowId, stepId, newStepId, newStepAction, conditionExpression) {
161
+ const step = this.getStep(workflowId, stepId);
162
+ if (step != undefined) {
163
+ this.addStepToWorkflow(workflowId, newStepId, {
164
+ action: newStepAction,
165
+ nextSteps: step.nextSteps,
166
+ });
167
+ this.updateStepInWorkflow(workflowId, stepId, {
168
+ ...step,
169
+ nextSteps: [
170
+ {
171
+ conditions: [{ type: 'SINGLE', expression: conditionExpression }],
172
+ nextStepId: newStepId,
173
+ },
174
+ ],
175
+ });
176
+ }
177
+ }
178
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
179
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowRegistryService, providedIn: 'root' }); }
180
+ }
181
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowRegistryService, decorators: [{
182
+ type: Injectable,
183
+ args: [{
184
+ providedIn: 'root',
185
+ }]
186
+ }] });
187
+
188
+ class AXPWorkflowDecideAction extends AXPWorkflowAction {
189
+ async execute(context) {
190
+ // its a fake action
191
+ }
192
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowDecideAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
193
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowDecideAction, providedIn: 'root' }); }
194
+ }
195
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowDecideAction, decorators: [{
196
+ type: Injectable,
197
+ args: [{
198
+ providedIn: 'root',
199
+ }]
200
+ }] });
201
+
202
+ class AXPWorkflowService {
203
+ get events$() {
204
+ return this.eventService.events$;
205
+ }
206
+ constructor(registryService, injector) {
207
+ this.registryService = registryService;
208
+ this.injector = injector;
209
+ this.eventService = inject(AXPWorkflowEventService);
210
+ }
211
+ exists(name) {
212
+ return !!this.registryService.getWorkflow(name);
213
+ }
214
+ async execute(workflow, initialContext = null) {
215
+ // Instantiate or retrieve the workflow
216
+ const wk = typeof workflow === 'string' ? this.getWorkflowInstance(workflow) : workflow;
217
+ let currentStepId = wk.startStepId;
218
+ const context = initialContext instanceof AXPWorkflowContext
219
+ ? initialContext
220
+ : new AXPWorkflowContext(this.processData(initialContext) ?? {});
221
+ // Initialize context with initial variables
222
+ while (currentStepId) {
223
+ const currentStep = wk.steps[currentStepId];
224
+ if (!currentStep) {
225
+ throw new AXPWorkflowError(`Step '${currentStepId}' not found in the workflow steps.`);
226
+ }
227
+ // Instantiate or retrieve the action
228
+ const action = typeof currentStep.action === 'string' ? this.getActionInstance(currentStep.action) : currentStep.action;
229
+ //
230
+ if (currentStep.input) {
231
+ let inputs = {};
232
+ await this.processInputs(cloneDeep(currentStep.input), inputs, '', context);
233
+ await new Promise((resolve) => setTimeout(async () => {
234
+ Object.assign(action, inputs);
235
+ resolve(0);
236
+ }));
237
+ }
238
+ try {
239
+ await new Promise((resolve) => setTimeout(async () => {
240
+ await action.execute(context);
241
+ resolve(0);
242
+ }, 0));
243
+ }
244
+ catch (error) {
245
+ console.error(error);
246
+ throw new AXPWorkflowError('Workflow Error', error);
247
+ }
248
+ // Determine the next step based on the conditions
249
+ const nextStepInfo = await this.determineNextStep(currentStep, context);
250
+ if (nextStepInfo) {
251
+ currentStepId = nextStepInfo.nextStepId;
252
+ }
253
+ else {
254
+ break; // Exit the loop if no next step is determined
255
+ }
256
+ }
257
+ return context;
258
+ }
259
+ processData(initialContext) {
260
+ //TODO: update values with expressions and return new object
261
+ return initialContext;
262
+ }
263
+ getActionInstance(actionName) {
264
+ const actionType = this.registryService.getAction(actionName);
265
+ if (!actionType) {
266
+ throw new AXPWorkflowError(`Action type '${actionName}' not found in the registry.`);
267
+ }
268
+ return this.injector.get(actionType);
269
+ }
270
+ getWorkflowInstance(workflowName) {
271
+ const workflowType = this.registryService.getWorkflow(workflowName);
272
+ if (!workflowType) {
273
+ throw new AXPWorkflowError(`Workflow type '${workflowName}' not found in the registry.`);
274
+ }
275
+ return workflowType;
276
+ }
277
+ async determineNextStep(currentStep, context) {
278
+ if (!currentStep.nextSteps) {
279
+ return null;
280
+ }
281
+ for (const nextStep of currentStep.nextSteps) {
282
+ if (await this.evaluateCondition(nextStep.conditions, context)) {
283
+ return nextStep;
284
+ }
285
+ }
286
+ return null;
287
+ }
288
+ async evaluateCondition(conditions, context) {
289
+ if (!conditions || conditions.length === 0) {
290
+ return true; // If no conditions, the step proceeds
291
+ }
292
+ // Map each condition to a promise using evaluateSingleCondition
293
+ const conditionPromises = conditions.map((condition) => this.evaluateSingleCondition(condition, context));
294
+ // Wait for all promises to resolve
295
+ const results = await Promise.all(conditionPromises);
296
+ // Check if every condition is true
297
+ return results.every((result) => result);
298
+ }
299
+ async evaluateSingleCondition(condition, context) {
300
+ switch (condition.type) {
301
+ case 'SINGLE':
302
+ return (!condition.expression ? true : await this.evaluateExpression(condition.expression, context));
303
+ case 'AND':
304
+ // Evaluate all conditions with 'AND' logic
305
+ const andConditions = condition.conditions ?? [];
306
+ const andResults = await Promise.all(andConditions.map((cond) => this.evaluateSingleCondition(cond, context)));
307
+ return andResults.every((result) => result);
308
+ case 'OR':
309
+ // Evaluate all conditions with 'OR' logic
310
+ const orConditions = condition.conditions ?? [];
311
+ const orResults = await Promise.all(orConditions.map((cond) => this.evaluateSingleCondition(cond, context)));
312
+ return orResults.some((result) => result);
313
+ default:
314
+ throw new AXPWorkflowError(`Unsupported condition type: ${condition.type}`);
315
+ }
316
+ }
317
+ //TODO: return any and recursively update object, update scopes
318
+ async evaluateExpression(templateExpression, context) {
319
+ try {
320
+ let expression = '';
321
+ if (typeof templateExpression === 'string' && templateExpression.trim().includes('{{')) {
322
+ const expressionMatch = templateExpression.match(/\{\{\s*(.*?)\s*\}\}/);
323
+ if (!expressionMatch) {
324
+ throw Error(`No valid expression found in "${templateExpression}"`);
325
+ }
326
+ expression = expressionMatch[1];
327
+ }
328
+ else {
329
+ expression = templateExpression;
330
+ }
331
+ // Create a scope that includes context and registered functions
332
+ const scope = { context, ...this.createFunctionScope() };
333
+ // Evaluating the expression within the created scope
334
+ const sandbox = new Function('scope', `return (async function() { with (scope) { return ${expression}; } })();`);
335
+ const result = await sandbox(scope);
336
+ return result;
337
+ }
338
+ catch (error) {
339
+ console.error('Error evaluating expression:', error);
340
+ return false;
341
+ }
342
+ }
343
+ async processInputs(obj, inputs, pathPrefix = '', context = {}) {
344
+ if (!obj) {
345
+ return;
346
+ }
347
+ for await (const i of Object.entries(obj)) {
348
+ const key = i[0];
349
+ const value = i[1];
350
+ const currentPath = pathPrefix ? `${pathPrefix}.${key}` : key;
351
+ if (typeof value === 'string' && value.trim().includes('{{')) {
352
+ const expValue = await this.evaluateExpression(value, context);
353
+ set(inputs, currentPath, expValue);
354
+ }
355
+ else if (typeof value === 'object' &&
356
+ value !== null &&
357
+ (value.constructor === Object || Array.isArray(value))) {
358
+ // Recursively handle nested objects
359
+ this.processInputs(value, inputs, currentPath, context);
360
+ }
361
+ else {
362
+ // Apply static values directly
363
+ set(inputs, currentPath, value);
364
+ }
365
+ }
366
+ }
367
+ createFunctionScope() {
368
+ const scope = {};
369
+ this.registryService.getFunctionNames().forEach((name) => {
370
+ scope[name] = this.getFunctionInstance(name).execute;
371
+ });
372
+ return { methods: { scope } };
373
+ }
374
+ getFunctionInstance(functionName) {
375
+ const functionType = this.registryService.getFunction(functionName);
376
+ if (!functionType) {
377
+ throw new AXPWorkflowError(`Function type '${functionName}' not found in the registry.`);
378
+ }
379
+ return this.injector.get(functionType);
380
+ }
381
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowService, deps: [{ token: AXPWorkflowRegistryService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
382
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowService, providedIn: 'root' }); }
383
+ }
384
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowService, decorators: [{
385
+ type: Injectable,
386
+ args: [{
387
+ providedIn: 'root',
388
+ }]
389
+ }], ctorParameters: () => [{ type: AXPWorkflowRegistryService }, { type: i0.Injector }] });
390
+
391
+ class AXPStartWorkflowAction extends AXPWorkflowAction {
392
+ constructor() {
393
+ super(...arguments);
394
+ this.workflowService = inject(AXPWorkflowService);
395
+ }
396
+ async execute(context) {
397
+ //console.log("start WK current context", context);
398
+ //console.log("start WK input context", this.context);
399
+ if (this.context)
400
+ context.setVariables(this.context);
401
+ //console.log("start WK merged context", context);
402
+ try {
403
+ await this.workflowService.execute(this.workflow, context);
404
+ }
405
+ catch (e) {
406
+ throw e;
407
+ }
408
+ }
409
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPStartWorkflowAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
410
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPStartWorkflowAction, providedIn: 'root' }); }
411
+ }
412
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPStartWorkflowAction, decorators: [{
413
+ type: Injectable,
414
+ args: [{
415
+ providedIn: 'root',
416
+ }]
417
+ }] });
418
+
419
+ const AXP_ACTIVITY_PROVIDER = new InjectionToken('AXP_ACTIVITY_PROVIDER', {
420
+ factory: () => [],
421
+ });
422
+ const AXP_ACTIVITY_CATEGORY_PROVIDER = new InjectionToken('AXP_ACTIVITY_CATEGORY_PROVIDER', {
423
+ factory: () => [],
424
+ });
425
+
426
+ //#region ---- Imports ----
427
+ //#endregion
428
+ /**
429
+ * Optimized Activity Definition Service
430
+ *
431
+ * Manages activity definitions (metadata) for UI and tooling.
432
+ * Similar to AXPReportDefinitionService - only handles metadata, not execution.
433
+ *
434
+ * Performance optimizations:
435
+ * 1. Uses childrenCount to determine if category has children (no query needed)
436
+ * 2. Uses itemsCount to determine if category has activities (no query needed)
437
+ * 3. Aggressive caching prevents duplicate API calls
438
+ * 4. Single pending request per resource prevents race conditions
439
+ * 5. Lazy loading - only loads data when needed
440
+ */
441
+ class AXPActivityDefinitionService {
442
+ constructor() {
443
+ //#region ---- Providers & Caches ----
444
+ this.categoryProviders = inject(AXP_ACTIVITY_CATEGORY_PROVIDER, { optional: true }) || [];
445
+ this.activityProviders = inject(AXP_ACTIVITY_PROVIDER, { optional: true }) || [];
446
+ //#endregion
447
+ //#region ---- Cache Storage ----
448
+ /** Cache for categories by id - O(1) lookup */
449
+ this.categoriesById = new Map();
450
+ /** Cache for categories by parentId - O(1) lookup */
451
+ this.categoriesByParentId = new Map();
452
+ /** Cache for activity definitions by categoryId - O(1) lookup */
453
+ this.activitiesByCategory = new Map();
454
+ /** Cache for individual activity definitions by name - O(1) lookup */
455
+ this.activitiesByName = new Map();
456
+ /** Track which provider index owns each category (by category ID) */
457
+ this.categoryOwnership = new Map(); // Maps categoryId → provider index
458
+ /** Pending API requests to prevent duplicate calls */
459
+ this.pendingCategoriesRequests = new Map();
460
+ this.pendingActivitiesRequests = new Map();
461
+ this.pendingActivityRequests = new Map();
462
+ }
463
+ //#endregion
464
+ //#region ---- Initialization ----
465
+ //#endregion
466
+ //#region ---- Public API: Categories ----
467
+ /**
468
+ * Get categories by parentId with aggressive caching
469
+ *
470
+ * Optimization: Returns cached result immediately if available,
471
+ * preventing unnecessary API calls during navigation
472
+ *
473
+ * @param parentId - Parent category ID (undefined = root categories)
474
+ * @returns Array of categories with count metadata (childrenCount, itemsCount)
475
+ */
476
+ async getCategories(parentId) {
477
+ // ✅ Fast path: Return cached result
478
+ if (this.categoriesByParentId.has(parentId)) {
479
+ return this.categoriesByParentId.get(parentId);
480
+ }
481
+ // ✅ Prevent duplicate requests: Return pending promise
482
+ if (this.pendingCategoriesRequests.has(parentId)) {
483
+ return this.pendingCategoriesRequests.get(parentId);
484
+ }
485
+ // ✅ Create single request and cache it
486
+ const requestPromise = this.loadCategoriesFromProviders(parentId);
487
+ this.pendingCategoriesRequests.set(parentId, requestPromise);
488
+ return requestPromise;
489
+ }
490
+ /**
491
+ * Get single category by ID with O(1) lookup
492
+ *
493
+ * Optimization: Uses Map for instant retrieval, falls back to
494
+ * searching cache, then providers if not found
495
+ */
496
+ async getCategoryById(categoryId) {
497
+ // ✅ Fast path: O(1) lookup in cache
498
+ if (this.categoriesById.has(categoryId)) {
499
+ return this.categoriesById.get(categoryId);
500
+ }
501
+ // ✅ Search in cached parent-child lists
502
+ for (const categories of this.categoriesByParentId.values()) {
503
+ const found = categories.find(cat => cat.id === categoryId);
504
+ if (found) {
505
+ this.categoriesById.set(categoryId, found);
506
+ return found;
507
+ }
508
+ }
509
+ // ✅ Load root categories if not loaded
510
+ if (!this.categoriesByParentId.has(undefined)) {
511
+ await this.getCategories();
512
+ if (this.categoriesById.has(categoryId)) {
513
+ return this.categoriesById.get(categoryId);
514
+ }
515
+ }
516
+ // ✅ Breadth-first search through hierarchy
517
+ return this.searchCategoryInHierarchy(categoryId);
518
+ }
519
+ /**
520
+ * Get category path from root to specified category
521
+ *
522
+ * Optimization: Builds path using cached categories only
523
+ */
524
+ async getCategoriesPathById(categoryId) {
525
+ const path = [];
526
+ let currentCategoryId = categoryId;
527
+ while (currentCategoryId) {
528
+ const category = await this.getCategoryById(currentCategoryId);
529
+ if (!category) {
530
+ throw new Error(`Category '${currentCategoryId}' not found`);
531
+ }
532
+ path.unshift(category);
533
+ currentCategoryId = category.parentId;
534
+ }
535
+ return path;
536
+ }
537
+ //#endregion
538
+ //#region ---- Public API: Activity Definitions ----
539
+ /**
540
+ * Get activity definitions for a category with smart caching
541
+ *
542
+ * Optimization: Checks itemsCount before querying
543
+ * - If itemsCount = 0, returns empty array (no API call)
544
+ * - If itemsCount > 0, loads and caches activity definitions
545
+ * - Returns cached result on subsequent calls
546
+ *
547
+ * @param categoryId - Category ID to get activity definitions from
548
+ * @returns Array of activity definitions
549
+ */
550
+ async getActivitiesByCategoryId(categoryId) {
551
+ // ✅ Fast path: Return cached result
552
+ if (this.activitiesByCategory.has(categoryId)) {
553
+ return this.activitiesByCategory.get(categoryId);
554
+ }
555
+ // ✅ Smart optimization: Check itemsCount before querying
556
+ const category = await this.getCategoryById(categoryId);
557
+ if (category && category.itemsCount !== undefined && category.itemsCount === 0) {
558
+ // Category has no activities - cache empty array and skip API call
559
+ const emptyArray = [];
560
+ this.activitiesByCategory.set(categoryId, emptyArray);
561
+ return emptyArray;
562
+ }
563
+ // ✅ Prevent duplicate requests
564
+ if (this.pendingActivitiesRequests.has(categoryId)) {
565
+ return this.pendingActivitiesRequests.get(categoryId);
566
+ }
567
+ // ✅ Load from providers
568
+ const requestPromise = this.loadActivitiesFromProviders(categoryId);
569
+ this.pendingActivitiesRequests.set(categoryId, requestPromise);
570
+ return requestPromise;
571
+ }
572
+ /**
573
+ * Get single activity definition by name with O(1) lookup
574
+ *
575
+ * Optimization: Uses Map for instant retrieval
576
+ *
577
+ * @param name - Activity name (unique identifier and command key)
578
+ * @returns Activity definition or undefined if not found
579
+ */
580
+ async getActivityByName(name) {
581
+ // ✅ Fast path: O(1) lookup in cache
582
+ if (this.activitiesByName.has(name)) {
583
+ return this.activitiesByName.get(name);
584
+ }
585
+ // ✅ Prevent duplicate requests
586
+ if (this.pendingActivityRequests.has(name)) {
587
+ return this.pendingActivityRequests.get(name);
588
+ }
589
+ // ✅ Load from providers
590
+ const requestPromise = this.loadActivityFromProviders(name);
591
+ this.pendingActivityRequests.set(name, requestPromise);
592
+ return requestPromise;
593
+ }
594
+ /**
595
+ * Get all activity definitions (flat list) by loading root categories and their activities.
596
+ * Used by activity selector UIs (e.g. automation command configurator).
597
+ */
598
+ async getAllActivities() {
599
+ const categories = await this.getCategories(undefined);
600
+ const all = [];
601
+ for (const cat of categories) {
602
+ const activities = await this.getActivitiesByCategoryId(cat.id);
603
+ all.push(...activities);
604
+ }
605
+ return all;
606
+ }
607
+ /**
608
+ * Get category ID containing a specific activity definition
609
+ *
610
+ * Optimization: Searches cache first, loads on-demand if needed
611
+ */
612
+ async getCategoryIdByActivityName(activityName) {
613
+ // ✅ Search in cached activity definitions
614
+ for (const [categoryId, definitions] of this.activitiesByCategory.entries()) {
615
+ if (definitions.some(def => def.name === activityName)) {
616
+ return categoryId;
617
+ }
618
+ }
619
+ // ✅ Try loading the activity definition to find its category
620
+ const definition = await this.getActivityByName(activityName);
621
+ if (definition && definition.category) {
622
+ // Try to find category by name/id
623
+ const categories = await this.getCategories();
624
+ const found = categories.find((cat) => cat.id === definition.category?.id);
625
+ if (found) {
626
+ return found.id;
627
+ }
628
+ }
629
+ return undefined;
630
+ }
631
+ /**
632
+ * Get category path for an activity
633
+ */
634
+ async getCategoriesPathByActivityName(activityName) {
635
+ const categoryId = await this.getCategoryIdByActivityName(activityName);
636
+ if (!categoryId) {
637
+ throw new Error(`Activity '${activityName}' not found in any category`);
638
+ }
639
+ return this.getCategoriesPathById(categoryId);
640
+ }
641
+ //#endregion
642
+ //#region ---- Private: Data Loading ----
643
+ /**
644
+ * Load categories from providers and cache results
645
+ *
646
+ * Optimization: Tracks provider ownership to avoid unnecessary API calls
647
+ * - For root (parentId = undefined): Query ALL providers
648
+ * - For children: Only query the provider that owns the parent
649
+ */
650
+ async loadCategoriesFromProviders(parentId) {
651
+ try {
652
+ const resolvedProviders = await Promise.allSettled(this.categoryProviders);
653
+ const categories = [];
654
+ // Determine which provider(s) to query
655
+ const providerIndicesToQuery = parentId
656
+ ? this.getProviderIndexForCategory(parentId)
657
+ : null; // Root: query all providers
658
+ for (let i = 0; i < resolvedProviders.length; i++) {
659
+ const p = resolvedProviders[i];
660
+ // Skip if we have a specific provider index and this isn't it
661
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
662
+ continue;
663
+ }
664
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
665
+ try {
666
+ const cats = await p.value.getList(parentId);
667
+ if (Array.isArray(cats) && cats.length > 0) {
668
+ categories.push(...cats);
669
+ // ✅ Track ownership: This provider INDEX owns these categories
670
+ cats.forEach(cat => this.categoryOwnership.set(cat.id, i));
671
+ }
672
+ }
673
+ catch {
674
+ // Continue on error - try other providers
675
+ }
676
+ }
677
+ }
678
+ // ✅ Cache results for fast subsequent access
679
+ this.categoriesByParentId.set(parentId, categories);
680
+ categories.forEach(cat => this.categoriesById.set(cat.id, cat));
681
+ return categories;
682
+ }
683
+ finally {
684
+ this.pendingCategoriesRequests.delete(parentId);
685
+ }
686
+ }
687
+ /**
688
+ * Get the provider index that owns a specific category
689
+ *
690
+ * @returns Array with provider index, or null if ownership unknown (query all)
691
+ */
692
+ getProviderIndexForCategory(categoryId) {
693
+ const ownerIndex = this.categoryOwnership.get(categoryId);
694
+ if (ownerIndex !== undefined) {
695
+ return [ownerIndex];
696
+ }
697
+ // Ownership unknown - will query all providers (fallback)
698
+ return null;
699
+ }
700
+ /**
701
+ * Load activity definitions from providers and cache results
702
+ *
703
+ * Optimization: Only queries the provider that owns the category
704
+ * Uses provider INDEX to match category provider with activity provider
705
+ */
706
+ async loadActivitiesFromProviders(categoryId) {
707
+ try {
708
+ const resolvedProviders = await Promise.allSettled(this.activityProviders);
709
+ const definitions = [];
710
+ // ✅ Smart routing: Get provider INDEX that owns this category
711
+ const ownerIndex = this.categoryOwnership.get(categoryId);
712
+ const providerIndicesToQuery = ownerIndex !== undefined ? [ownerIndex] : null;
713
+ for (let i = 0; i < resolvedProviders.length; i++) {
714
+ const p = resolvedProviders[i];
715
+ // Skip if we have a specific provider index and this isn't it
716
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
717
+ continue;
718
+ }
719
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
720
+ try {
721
+ const defs = await p.value.getList(categoryId);
722
+ if (Array.isArray(defs)) {
723
+ definitions.push(...defs);
724
+ }
725
+ }
726
+ catch {
727
+ // Continue on error - try other providers
728
+ }
729
+ }
730
+ }
731
+ // ✅ Cache results for fast subsequent access
732
+ this.activitiesByCategory.set(categoryId, definitions);
733
+ definitions.forEach(def => {
734
+ if (def.name) {
735
+ this.activitiesByName.set(def.name, def);
736
+ }
737
+ });
738
+ return definitions;
739
+ }
740
+ finally {
741
+ this.pendingActivitiesRequests.delete(categoryId);
742
+ }
743
+ }
744
+ /**
745
+ * Load single activity definition from providers and cache result
746
+ */
747
+ async loadActivityFromProviders(name) {
748
+ try {
749
+ const resolvedProviders = await Promise.allSettled(this.activityProviders);
750
+ // Try providers first
751
+ for (const p of resolvedProviders) {
752
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getById === 'function') {
753
+ try {
754
+ const result = await p.value.getById(name);
755
+ if (result) {
756
+ this.activitiesByName.set(name, result);
757
+ return result;
758
+ }
759
+ }
760
+ catch {
761
+ // Continue on error
762
+ }
763
+ }
764
+ }
765
+ // Fallback: Search in cached activity definitions
766
+ for (const definitions of this.activitiesByCategory.values()) {
767
+ const found = definitions.find(def => def.name === name);
768
+ if (found) {
769
+ this.activitiesByName.set(name, found);
770
+ return found;
771
+ }
772
+ }
773
+ return undefined;
774
+ }
775
+ finally {
776
+ this.pendingActivityRequests.delete(name);
777
+ }
778
+ }
779
+ /**
780
+ * Breadth-first search through category hierarchy
781
+ */
782
+ async searchCategoryInHierarchy(categoryId) {
783
+ const searchQueue = [undefined];
784
+ const searched = new Set();
785
+ while (searchQueue.length > 0) {
786
+ const parentId = searchQueue.shift();
787
+ if (searched.has(parentId))
788
+ continue;
789
+ searched.add(parentId);
790
+ const categories = await this.getCategories(parentId);
791
+ const found = categories.find(cat => cat.id === categoryId);
792
+ if (found) {
793
+ return found;
794
+ }
795
+ // ✅ Optimization: Only search children if childrenCount > 0
796
+ for (const category of categories) {
797
+ if (category.childrenCount > 0 && !searched.has(category.id)) {
798
+ searchQueue.push(category.id);
799
+ }
800
+ }
801
+ }
802
+ return undefined;
803
+ }
804
+ //#endregion
805
+ //#region ---- Cache Management ----
806
+ /**
807
+ * Check if category has children (uses cached count)
808
+ */
809
+ categoryHasChildren(categoryId) {
810
+ const category = this.categoriesById.get(categoryId);
811
+ return category ? category.childrenCount > 0 : false;
812
+ }
813
+ /**
814
+ * Check if category has activities (uses cached count)
815
+ */
816
+ categoryHasActivities(categoryId) {
817
+ const category = this.categoriesById.get(categoryId);
818
+ return category ? (category.itemsCount ?? 0) > 0 : false;
819
+ }
820
+ /**
821
+ * Clear all caches
822
+ */
823
+ clearAllCache() {
824
+ this.categoriesById.clear();
825
+ this.categoriesByParentId.clear();
826
+ this.activitiesByCategory.clear();
827
+ this.activitiesByName.clear();
828
+ this.categoryOwnership.clear();
829
+ this.pendingCategoriesRequests.clear();
830
+ this.pendingActivitiesRequests.clear();
831
+ this.pendingActivityRequests.clear();
832
+ }
833
+ /**
834
+ * Clear categories cache only
835
+ */
836
+ clearCategoriesCache() {
837
+ this.categoriesById.clear();
838
+ this.categoriesByParentId.clear();
839
+ this.categoryOwnership.clear();
840
+ this.pendingCategoriesRequests.clear();
841
+ }
842
+ /**
843
+ * Clear activities cache only
844
+ */
845
+ clearActivitiesCache() {
846
+ this.activitiesByCategory.clear();
847
+ this.activitiesByName.clear();
848
+ this.pendingActivitiesRequests.clear();
849
+ this.pendingActivityRequests.clear();
850
+ }
851
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPActivityDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
852
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPActivityDefinitionService, providedIn: 'root' }); }
853
+ }
854
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPActivityDefinitionService, decorators: [{
855
+ type: Injectable,
856
+ args: [{
857
+ providedIn: 'root',
858
+ }]
859
+ }] });
860
+
861
+ /**
862
+ * Injection token for workflow engine.
863
+ * Default implementation is AXPWorkflowLocalEngine.
864
+ */
865
+ const AXP_WORKFLOW_ENGINE = new InjectionToken('AXP_WORKFLOW_ENGINE');
866
+
867
+ //#region ---- Error Codes ----
868
+ /**
869
+ * Stable workflow engine error codes for UI branching (toasts, take-over flow, etc.).
870
+ */
871
+ const AXP_WORKFLOW_ERROR_CODES = {
872
+ TASK_NOT_ASSIGNEE: 'WORKFLOW_TASK_NOT_ASSIGNEE',
873
+ CLAIM_REQUIRED: 'WORKFLOW_CLAIM_REQUIRED',
874
+ REASSIGN_NOT_ALLOWED: 'WORKFLOW_REASSIGN_NOT_ALLOWED',
875
+ };
876
+ //#endregion
877
+ //#region ---- Engine Error ----
878
+ /**
879
+ * Business-rule failure from {@link AXPWorkflowEngine} (expected, user-facing).
880
+ */
881
+ class AXPWorkflowEngineError extends Error {
882
+ constructor(message, code, details) {
883
+ super(message);
884
+ this.code = code;
885
+ this.details = details;
886
+ this.name = 'AXPWorkflowEngineError';
887
+ }
888
+ }
889
+ /**
890
+ * Resolves a stable error code from an unknown thrown value.
891
+ */
892
+ function getWorkflowEngineErrorCode(error) {
893
+ if (error instanceof AXPWorkflowEngineError) {
894
+ return error.code;
895
+ }
896
+ return undefined;
897
+ }
898
+ /**
899
+ * Resolves a user-facing message and optional code from an unknown thrown value.
900
+ */
901
+ function getWorkflowEngineErrorInfo(error) {
902
+ if (error instanceof AXPWorkflowEngineError) {
903
+ return { message: error.message, code: error.code };
904
+ }
905
+ if (error instanceof Error) {
906
+ return { message: error.message };
907
+ }
908
+ return { message: 'An unexpected workflow error occurred' };
909
+ }
910
+ //#endregion
911
+
912
+ /**
913
+ * Activity types that use the task board (suspend until user acts via inbox), not inline interactive execution.
914
+ */
915
+ const AXP_WORKFLOW_TASK_BOARD_ACTIVITY_TYPES = [
916
+ 'workflow-activity:human-task',
917
+ 'workflow-activity:cartable',
918
+ ];
919
+ function axpIsWorkflowTaskBoardActivityType(activityType) {
920
+ return !!activityType && AXP_WORKFLOW_TASK_BOARD_ACTIVITY_TYPES.includes(activityType);
921
+ }
922
+
923
+ //#region ---- Imports ----
924
+ //#endregion
925
+ //#region ---- Constants ----
926
+ /** Trace namespace for platform workflow runtime diagnostics (gated by tenant Debug Mode). */
927
+ const WORKFLOW_RUNTIME_TRACE_NS = 'platform-workflow';
928
+ //#endregion
929
+ //#region ---- Helpers ----
930
+ function traceWorkflowRuntime(debug, area, message, data) {
931
+ debug?.trace(WORKFLOW_RUNTIME_TRACE_NS, area, message, data);
932
+ }
933
+ function warnWorkflowRuntime(debug, area, message, data) {
934
+ debug?.warn(WORKFLOW_RUNTIME_TRACE_NS, area, message, data);
935
+ }
936
+ function errorWorkflowRuntime(debug, area, message, data) {
937
+ debug?.error(WORKFLOW_RUNTIME_TRACE_NS, area, message, data);
938
+ }
939
+ //#endregion
940
+
941
+ //#endregion
942
+ /**
943
+ * Workflow Expression Scope Service
944
+ *
945
+ * Shared service for building expression evaluation scope from workflow data.
946
+ * Used by both Local Engine and Mock Runtime to manage workflow data (inputs, variables, outputs).
947
+ *
948
+ * Responsibilities:
949
+ * - Build expression evaluation scope from workflow state
950
+ * - Provide context.eval() function for accessing workflow data
951
+ * - Manage workflow data structure (inputs, variables, outputs)
952
+ *
953
+ * This service does NOT:
954
+ * - Execute activities
955
+ * - Evaluate expressions (delegates to AXPExpressionEvaluatorService)
956
+ * - Manage workflow state (handled by runtime)
957
+ *
958
+ * @example
959
+ * ```typescript
960
+ * const scopeService = inject(WorkflowExpressionScopeService);
961
+ *
962
+ * // Build scope from workflow state
963
+ * const scope = scopeService.buildScope({
964
+ * inputs: state.input || {},
965
+ * variables: state.variables || {},
966
+ * outputs: activityOutputs
967
+ * });
968
+ *
969
+ * // Or build from state directly
970
+ * const scope = scopeService.buildScopeFromState(state, activityOutputs);
971
+ * ```
972
+ */
973
+ class WorkflowExpressionScopeService {
974
+ constructor() {
975
+ this.contextEvalFactory = inject(AXPContextEvalFactory);
976
+ }
977
+ //#region ---- Private Helpers (dot-notation input normalization) ----
978
+ /**
979
+ * Collect dot-notation key-value pairs from nested objects so that e.g.
980
+ * { metadata: { "metadata.questionnaire.id": "x" } } becomes { "metadata.questionnaire.id": "x" } at root.
981
+ * Top-level non-dot keys are kept as-is.
982
+ */
983
+ flattenDotKeysFromTree(obj) {
984
+ if (obj === null || typeof obj !== 'object')
985
+ return obj;
986
+ const result = {};
987
+ for (const [key, value] of Object.entries(obj)) {
988
+ if (key.includes('.')) {
989
+ result[key] = value;
990
+ }
991
+ else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
992
+ for (const [k, v] of Object.entries(value)) {
993
+ if (k.includes('.')) {
994
+ result[k] = v;
995
+ }
996
+ else {
997
+ result[key + '.' + k] = v;
998
+ }
999
+ }
1000
+ }
1001
+ else {
1002
+ result[key] = value;
1003
+ }
1004
+ }
1005
+ return result;
1006
+ }
1007
+ /**
1008
+ * Expand flat keys with dots into nested objects.
1009
+ * e.g. { "metadata.questionnaire.id": "x" } -> { metadata: { questionnaire: { id: "x" } } }.
1010
+ */
1011
+ expandDotKeys(obj) {
1012
+ if (obj === null || typeof obj !== 'object')
1013
+ return obj;
1014
+ const result = {};
1015
+ const dotKeys = [];
1016
+ const simpleKeys = [];
1017
+ for (const [key, value] of Object.entries(obj)) {
1018
+ if (key.includes('.')) {
1019
+ dotKeys.push([key, value]);
1020
+ }
1021
+ else {
1022
+ simpleKeys.push([key, value]);
1023
+ }
1024
+ }
1025
+ for (const [key, value] of dotKeys) {
1026
+ const parts = key.split('.');
1027
+ let current = result;
1028
+ for (let i = 0; i < parts.length - 1; i++) {
1029
+ const part = parts[i];
1030
+ if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
1031
+ current[part] = {};
1032
+ }
1033
+ current = current[part];
1034
+ }
1035
+ current[parts[parts.length - 1]] = value;
1036
+ }
1037
+ for (const [key, value] of simpleKeys) {
1038
+ result[key] = value;
1039
+ }
1040
+ return result;
1041
+ }
1042
+ /**
1043
+ * Normalize workflow input so that flat dot-notation keys (e.g. from form schema
1044
+ * "metadata.questionnaire.id") become nested for expression access (inputs.metadata.questionnaire.id).
1045
+ */
1046
+ normalizeInputs(input) {
1047
+ if (!input || typeof input !== 'object')
1048
+ return {};
1049
+ const flattened = this.flattenDotKeysFromTree(input);
1050
+ return this.expandDotKeys(flattened);
1051
+ }
1052
+ //#endregion
1053
+ //#region ---- Public Methods ----
1054
+ /**
1055
+ * Build expression evaluation scope for workflow activities.
1056
+ *
1057
+ * Provides workflow-specific data (inputs, variables, outputs) and context.eval() function.
1058
+ * Other data (session, current user, etc.) are provided by expression evaluator scope providers.
1059
+ *
1060
+ * Scope includes:
1061
+ * - inputs: Workflow input values (accessible as inputs.propertyName)
1062
+ * - variables: Workflow state variables (accessible as variables.propertyName or vars.propertyName)
1063
+ * - outputs: Previous activity outputs (accessible as outputs.activityId)
1064
+ * - context.eval(path): Function to access nested properties from workflow data
1065
+ *
1066
+ * Expressions can use:
1067
+ * - {{inputs.userName}} - Direct access to workflow input
1068
+ * - {{variables.someVar}} or {{vars.someVar}} - Direct access to workflow variable
1069
+ * - {{outputs.activityId.property}} - Direct access to previous activity output
1070
+ * - {{context.eval("inputs.userName")}} - Access via context.eval (supports nested paths)
1071
+ * - {{context.eval("variables.someVar")}} - Access variables via context.eval
1072
+ * - {{context.eval("outputs.activityId.property")}} - Access outputs via context.eval
1073
+ * - {{session.currentUser().name}} - Access current user via expression evaluator scope providers
1074
+ *
1075
+ * The context.eval() function provides a unified way to access all workflow data,
1076
+ * similar to how entity-detail-list uses context.eval() to access parent data.
1077
+ *
1078
+ * @param context - Workflow expression context containing inputs, variables, and outputs
1079
+ * @returns Expression evaluator scope with workflow data and context.eval() function
1080
+ */
1081
+ buildScope(context) {
1082
+ // Normalize inputs so flat dot-notation keys (e.g. metadata.questionnaire.id from forms) become nested for expressions
1083
+ const inputs = this.normalizeInputs(context.inputs || {});
1084
+ // Build merged workflow data object for context.eval()
1085
+ // This allows expressions like: context.eval("inputs.userName") or context.eval("variables.count")
1086
+ const workflowData = {
1087
+ inputs,
1088
+ variables: context.variables || {},
1089
+ vars: context.variables || {}, // Alias for convenience
1090
+ outputs: context.outputs || {},
1091
+ };
1092
+ // Build scope object with workflow-specific data and context.eval()
1093
+ // Note: AXPExpressionEvaluatorScope type expects { [namespace]: { [name]: Function } }
1094
+ // but evaluate() method actually accepts flat objects with values too
1095
+ // We'll use 'any' to allow both values and functions
1096
+ const scope = {
1097
+ // Direct access to workflow data
1098
+ inputs: workflowData.inputs,
1099
+ variables: workflowData.variables,
1100
+ vars: workflowData.vars,
1101
+ outputs: workflowData.outputs,
1102
+ // Context object with eval function (similar to entity-detail-list pattern)
1103
+ context: this.contextEvalFactory.createScope(workflowData),
1104
+ };
1105
+ return scope;
1106
+ }
1107
+ /**
1108
+ * Build expression evaluation scope from workflow instance state.
1109
+ *
1110
+ * Convenience method that extracts data from AXPWorkflowInstanceState.
1111
+ *
1112
+ * @param state - Workflow instance state
1113
+ * @param activityOutputs - Map of activity outputs (activityId -> output)
1114
+ * @returns Expression evaluator scope with workflow data and context.eval() function
1115
+ */
1116
+ buildScopeFromState(state, activityOutputs) {
1117
+ // Convert activity outputs to record format
1118
+ const outputs = {};
1119
+ if (activityOutputs) {
1120
+ if (activityOutputs instanceof Map) {
1121
+ activityOutputs.forEach((output, activityId) => {
1122
+ outputs[activityId] = output;
1123
+ });
1124
+ }
1125
+ else {
1126
+ Object.assign(outputs, activityOutputs);
1127
+ }
1128
+ }
1129
+ // Convenience alias for the last activity output (if available).
1130
+ // This allows expressions like: {{outputs.last.someField}}
1131
+ if (outputs['last'] === undefined && state.lastActivityOutput !== undefined) {
1132
+ outputs['last'] = state.lastActivityOutput;
1133
+ }
1134
+ return this.buildScope({
1135
+ inputs: this.normalizeInputs((state.input || {})),
1136
+ variables: state.variables || {},
1137
+ outputs: outputs,
1138
+ });
1139
+ }
1140
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WorkflowExpressionScopeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1141
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WorkflowExpressionScopeService, providedIn: 'root' }); }
1142
+ }
1143
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WorkflowExpressionScopeService, decorators: [{
1144
+ type: Injectable,
1145
+ args: [{
1146
+ providedIn: 'root'
1147
+ }]
1148
+ }] });
1149
+
1150
+ //#region ---- Constants ----
1151
+ /**
1152
+ * Activity types handled internally by the workflow engine (e.g. mock backend).
1153
+ * When such an activity is not registered as Command on the client, we skip execution
1154
+ * without warning — the engine will run it when executing the workflow.
1155
+ */
1156
+ const ENGINE_BUILTIN_ACTIVITY_TYPES = new Set([
1157
+ 'workflow-activity:set-variable',
1158
+ 'workflow-activity:http-request',
1159
+ 'workflow-activity:resolve-employee-user-id',
1160
+ ]);
1161
+ //#endregion
1162
+ /**
1163
+ * Activity Executor Service
1164
+ *
1165
+ * Service for executing workflow activities via CommandBus.
1166
+ * Automatically evaluates expressions in activity inputs before execution.
1167
+ *
1168
+ * @example
1169
+ * ```typescript
1170
+ * const executor = inject(ActivityExecutor);
1171
+ *
1172
+ * // Execute activity with task and workflow state (expressions will be evaluated)
1173
+ * const result = await executor.execute(task, workflowState, activityOutputs);
1174
+ * ```
1175
+ */
1176
+ class ActivityExecutor {
1177
+ constructor() {
1178
+ //#region ---- Services & Dependencies ----
1179
+ this.commandService = inject(AXPCommandService);
1180
+ this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
1181
+ this.expressionScopeService = inject(WorkflowExpressionScopeService);
1182
+ this.translateService = inject(AXTranslationService);
1183
+ this.debug = inject(AXPDebugService);
1184
+ }
1185
+ //#endregion
1186
+ //#region ---- Public Methods ----
1187
+ /**
1188
+ * Execute a workflow activity with expression evaluation.
1189
+ *
1190
+ * Evaluates expressions in activity inputs using workflow state,
1191
+ * then executes the activity via CommandBus.
1192
+ *
1193
+ * @param task - Workflow task containing activity information
1194
+ * @param workflowState - Current workflow instance state (for expression evaluation)
1195
+ * @param activityOutputs - Map of previous activity outputs (for expression evaluation)
1196
+ * @returns Execution result with output and outcome
1197
+ */
1198
+ async execute(task, workflowState, activityOutputs) {
1199
+ try {
1200
+ const activityName = task.activityType;
1201
+ // Merge config + input, then evaluate expressions when workflow state is available.
1202
+ const baseInputs = { ...(task.config || {}), ...(task.input || {}) };
1203
+ let evaluatedInputs = baseInputs;
1204
+ if (workflowState) {
1205
+ const outputsForScope = activityOutputs ??
1206
+ workflowState.activityOutputs ??
1207
+ undefined;
1208
+ const scope = this.expressionScopeService.buildScopeFromState(workflowState, outputsForScope);
1209
+ evaluatedInputs = await this.expressionEvaluator.evaluate(baseInputs, scope);
1210
+ }
1211
+ // Check if command exists
1212
+ const commandExists = this.commandService.exists(activityName);
1213
+ if (!commandExists) {
1214
+ if (!ENGINE_BUILTIN_ACTIVITY_TYPES.has(activityName)) {
1215
+ warnWorkflowRuntime(this.debug, 'executor', 'Activity is not registered as command; skipping execution', () => ({
1216
+ activityName,
1217
+ }));
1218
+ }
1219
+ return {
1220
+ output: null,
1221
+ outcome: 'Done'
1222
+ };
1223
+ }
1224
+ // Flatten properties if nested (designer-style nested payload)
1225
+ let commandInput = evaluatedInputs;
1226
+ if (commandInput['properties'] && typeof commandInput['properties'] === 'object') {
1227
+ // Flatten: {properties: {text: "..."}} -> {text: "..."}
1228
+ commandInput = { ...commandInput['properties'] };
1229
+ }
1230
+ // Execute activity via CommandBus
1231
+ // Activities (AXPActivity) return {output, outcome}; legacy may return {output, outcomes}
1232
+ const result = await this.commandService.execute(activityName, commandInput);
1233
+ if (!result) {
1234
+ return {
1235
+ output: null,
1236
+ outcome: 'Failed',
1237
+ };
1238
+ }
1239
+ if (!result.success) {
1240
+ return {
1241
+ output: {
1242
+ error: await this.resolveCommandMessageTextForError(result.message?.text),
1243
+ },
1244
+ outcome: 'Failed',
1245
+ };
1246
+ }
1247
+ const commandResult = result.data;
1248
+ // Prefer unified outcome in result.metadata; fall back to legacy data.outcome/outcomes.
1249
+ const metadataOutcome = result?.metadata?.['outcome'];
1250
+ let outcome = 'Done';
1251
+ if (typeof metadataOutcome === 'string' && metadataOutcome.length > 0) {
1252
+ outcome = metadataOutcome;
1253
+ }
1254
+ else if (typeof commandResult?.outcome === 'string' && commandResult.outcome.length > 0) {
1255
+ outcome = commandResult.outcome;
1256
+ }
1257
+ else {
1258
+ const outcomes = (commandResult?.outcomes ?? {});
1259
+ if (outcomes && typeof outcomes === 'object' && Object.keys(outcomes).length > 0) {
1260
+ outcome = outcomes['Done'] ? 'Done' : Object.keys(outcomes)[0] || 'Done';
1261
+ }
1262
+ }
1263
+ if (activityName.includes('AssessmentCase') && activityName.endsWith(':Fill')) {
1264
+ if (!result.success) {
1265
+ warnWorkflowRuntime(this.debug, 'executor', 'Assessment fill activity failed', () => ({
1266
+ activityName,
1267
+ metadataOutcome: metadataOutcome ?? null,
1268
+ resolvedOutcome: outcome,
1269
+ caseId: commandInput?.['caseId'] ?? null,
1270
+ sessionId: commandInput?.['sessionId'] ?? null,
1271
+ error: result.message?.text ?? null,
1272
+ }));
1273
+ }
1274
+ }
1275
+ // Prefer output wrapper when present; otherwise treat data itself as output.
1276
+ const output = commandResult && typeof commandResult === 'object' && 'output' in commandResult
1277
+ ? commandResult.output
1278
+ : commandResult;
1279
+ return {
1280
+ output: output ?? null,
1281
+ outcome,
1282
+ };
1283
+ }
1284
+ catch (error) {
1285
+ errorWorkflowRuntime(this.debug, 'executor', 'Error evaluating expressions or executing activity', () => ({
1286
+ activityName: task.activityType,
1287
+ error: error instanceof Error ? error.message : String(error),
1288
+ }));
1289
+ return {
1290
+ output: {
1291
+ error: error instanceof Error ? error.message : 'Unknown error',
1292
+ },
1293
+ outcome: 'Failed',
1294
+ };
1295
+ }
1296
+ }
1297
+ /**
1298
+ * Resolves command failure message text for workflow output: `@` keys via translate, MLS maps via resolve.
1299
+ */
1300
+ async resolveCommandMessageTextForError(value) {
1301
+ if (value == null) {
1302
+ return '';
1303
+ }
1304
+ if (typeof value === 'string') {
1305
+ return value.startsWith('@') ? await this.translateService.translateAsync(value) : value;
1306
+ }
1307
+ return this.translateService.resolve(value);
1308
+ }
1309
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ActivityExecutor, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1310
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ActivityExecutor, providedIn: 'root' }); }
1311
+ }
1312
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ActivityExecutor, decorators: [{
1313
+ type: Injectable,
1314
+ args: [{
1315
+ providedIn: 'root'
1316
+ }]
1317
+ }] });
1318
+
1319
+ const AXP_WORKFLOW_CONTINUATION_HOOK = new InjectionToken('AXP_WORKFLOW_CONTINUATION_HOOK');
1320
+ /** When true on resume `userInput`, skips the post-step continuation dialog (auto-advance). */
1321
+ const AXP_WORKFLOW_SUPPRESS_CONTINUATION_INPUT_KEY = '_suppressWorkflowContinuation';
1322
+ function isWorkflowContinuationSuppressed(userInput) {
1323
+ if (userInput == null || typeof userInput !== 'object') {
1324
+ return false;
1325
+ }
1326
+ return userInput[AXP_WORKFLOW_SUPPRESS_CONTINUATION_INPUT_KEY] === true;
1327
+ }
1328
+ //#endregion
1329
+
1330
+ //#region ---- Inline frontend classification ----
1331
+ /**
1332
+ * Frontend step executed inline (dialog/popup), not suspended on the task board.
1333
+ * Derived from execution mode and task-board activity types — no per-activity hardcoding.
1334
+ */
1335
+ function axpIsWorkflowInlineFrontendTask(task) {
1336
+ if (!task) {
1337
+ return false;
1338
+ }
1339
+ if (axpIsWorkflowTaskBoardActivityType(task.activityType)) {
1340
+ return false;
1341
+ }
1342
+ return task.executionMode === 'frontend' || task.executionMode === 'both';
1343
+ }
1344
+ /** @deprecated Use {@link axpIsWorkflowInlineFrontendTask}. */
1345
+ function axpIsWorkflowInteractiveFrontendTask(task) {
1346
+ return axpIsWorkflowInlineFrontendTask(task);
1347
+ }
1348
+ //#endregion
1349
+ //#region ---- Continuation metadata on tasks ----
1350
+ /** Resolved before-interactive behavior from task metadata (set by engine from activity definition). */
1351
+ function axpResolveWorkflowContinuationBeforeInteractive(task) {
1352
+ return task?.continuation?.beforeInteractive ?? 'default';
1353
+ }
1354
+ /**
1355
+ * Whether to skip the pre-interactive "continue?" prompt based on activity metadata.
1356
+ * Assignee-specific rules are applied in workflow-management (needs session).
1357
+ */
1358
+ function axpShouldSkipBeforeInteractiveFromMetadata(behavior, completedWasHumanTask) {
1359
+ return behavior === 'skip-when-chained-from-human-task' && completedWasHumanTask;
1360
+ }
1361
+ /**
1362
+ * Whether to show a "continue?" prompt before running inline frontend activities.
1363
+ * Skips when the user already resumed a task-board step (primary action / resume).
1364
+ */
1365
+ function axpShouldOfferBeforeInteractiveFlow(context) {
1366
+ return !context.completedActivityId;
1367
+ }
1368
+ //#endregion
1369
+ //#region ---- Step advancement gate ----
1370
+ /** Resume outcomes that do not advance the workflow graph (draft save, cancel, loop back). */
1371
+ const NON_ADVANCING_RESUME_OUTCOMES = new Set(['saved', 'cancelled', 'cancel', 'save', 'draft']);
1372
+ /**
1373
+ * Whether the workflow advanced enough to consider a continuation prompt.
1374
+ * Includes pending interactive steps when inbox bookmarks are already consumed.
1375
+ */
1376
+ function axpShouldOfferWorkflowContinuationAfterStep(context, activeBookmarkActivityIds) {
1377
+ const outcome = context.resumeOutcome?.trim().toLowerCase();
1378
+ if (outcome && NON_ADVANCING_RESUME_OUTCOMES.has(outcome)) {
1379
+ return false;
1380
+ }
1381
+ const pendingActivityId = context.pendingNextTask?.activityId;
1382
+ const completedActivityId = context.completedActivityId;
1383
+ if (pendingActivityId && completedActivityId && pendingActivityId === completedActivityId) {
1384
+ return false;
1385
+ }
1386
+ if (activeBookmarkActivityIds.length === 0) {
1387
+ const pending = context.pendingNextTask;
1388
+ if (completedActivityId && pending && pending.activityId !== completedActivityId) {
1389
+ if (axpIsWorkflowInlineFrontendTask(pending) ||
1390
+ axpIsWorkflowTaskBoardActivityType(pending.activityType)) {
1391
+ return true;
1392
+ }
1393
+ }
1394
+ return false;
1395
+ }
1396
+ /** No continuation on bare workflow start (bookmarks exist before any step completes). */
1397
+ if (!completedActivityId) {
1398
+ return false;
1399
+ }
1400
+ return activeBookmarkActivityIds.some((id) => id !== completedActivityId);
1401
+ }
1402
+ //#endregion
1403
+
1404
+ //#endregion
1405
+ /**
1406
+ * Workflow Manager - Facade for workflow lifecycle orchestration.
1407
+ *
1408
+ * This service is the ONLY interface the frontend uses to interact with workflows.
1409
+ * It follows Clean Architecture principles and does NOT contain execution or business logic.
1410
+ *
1411
+ * Responsibilities:
1412
+ * - Orchestrate workflow lifecycle (start, execute, complete, resume)
1413
+ * - Delegate execution to ActivityExecutor
1414
+ * - Cache workflow state in memory
1415
+ * - Expose a stable API for UI
1416
+ *
1417
+ * Rules:
1418
+ * - No HTTP calls (delegates to AXPWorkflowEngine)
1419
+ * - No CommandBus / Command execution (delegates to ActivityExecutor)
1420
+ * - No workflow branching logic (backend decides)
1421
+ * - No business validation (backend validates)
1422
+ * - No backend assumptions (uses abstract runtime service)
1423
+ */
1424
+ class AXPWorkflowManager {
1425
+ constructor() {
1426
+ //#region ---- Services & Dependencies ----
1427
+ this.workflowEngine = inject(AXP_WORKFLOW_ENGINE);
1428
+ this.activityExecutor = inject(ActivityExecutor);
1429
+ this.debug = inject(AXPDebugService);
1430
+ this.continuationHook = inject(AXP_WORKFLOW_CONTINUATION_HOOK, { optional: true });
1431
+ //#endregion
1432
+ //#region ---- State Cache ----
1433
+ /**
1434
+ * Cache workflow states in memory for quick access.
1435
+ * Key: instanceId
1436
+ * Value: AXPWorkflowInstanceState
1437
+ */
1438
+ this.stateCache = new Map();
1439
+ /**
1440
+ * Cache TTL in milliseconds (5 minutes).
1441
+ */
1442
+ this.CACHE_TTL = 5 * 60 * 1000;
1443
+ }
1444
+ //#endregion
1445
+ //#region ---- Public Methods ----
1446
+ /**
1447
+ * Execute frontend activities interactively until reaching workflow-activity:human-task or completion.
1448
+ *
1449
+ * Interactive = show form/popup immediately (user sees and acts). Only workflow-activity:human-task
1450
+ * is not interactive (goes to task board). Other frontend activities (show-layout-popup, show-toast, etc.)
1451
+ * even with taskType human-task in definition are executed here.
1452
+ *
1453
+ * @param instanceId - Workflow instance ID
1454
+ * @param task - Current task to execute
1455
+ * @param state - Current workflow state
1456
+ * @param lastActivityOutput - Last activity output (for expression evaluation)
1457
+ * @returns Final result with nextTask (if workflow-activity:human-task) or completion status
1458
+ */
1459
+ async executeInteractiveFlow(instanceId, task, state, activityOutputs) {
1460
+ let currentTask = task;
1461
+ let currentState = state;
1462
+ let currentActivityOutputs = {
1463
+ ...(currentState.activityOutputs || {}),
1464
+ ...(activityOutputs || {}),
1465
+ };
1466
+ const maxIterations = 100; // Prevent infinite loops
1467
+ let iterationCount = 0;
1468
+ while (currentTask && iterationCount < maxIterations) {
1469
+ iterationCount++;
1470
+ // Interactive = frontend executionMode and NOT task-board activities (human-task, cartable)
1471
+ const isInteractive = (currentTask.executionMode === 'frontend' || currentTask.executionMode === 'both') &&
1472
+ !axpIsWorkflowTaskBoardActivityType(currentTask.activityType);
1473
+ if (isInteractive) {
1474
+ // Execute frontend activity
1475
+ const execResult = await this.activityExecutor.execute(currentTask, currentState, currentActivityOutputs);
1476
+ // Track outputs locally (backend should also persist and return them)
1477
+ currentActivityOutputs = {
1478
+ ...currentActivityOutputs,
1479
+ [currentTask.activityId]: execResult.output,
1480
+ };
1481
+ // Send result to backend
1482
+ const completedTask = currentTask;
1483
+ const completeResponse = await this.workflowEngine.frontActivtyComplete({
1484
+ instanceId,
1485
+ activityNode: completedTask.activityId,
1486
+ output: execResult.output || {},
1487
+ outcome: execResult.outcome,
1488
+ });
1489
+ // Update state cache
1490
+ if (completeResponse.state) {
1491
+ const normalizedState = { ...completeResponse.state };
1492
+ if (normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
1493
+ normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
1494
+ }
1495
+ currentState = normalizedState;
1496
+ // Prefer outputs returned by backend; fallback to local cache
1497
+ currentActivityOutputs = {
1498
+ ...currentActivityOutputs,
1499
+ ...(normalizedState.activityOutputs || {}),
1500
+ };
1501
+ this.stateCache.set(instanceId, normalizedState);
1502
+ }
1503
+ // Backend decides: if no nextTask, workflow is completed
1504
+ if (!completeResponse.nextTask) {
1505
+ return {
1506
+ nextTask: null,
1507
+ state: currentState,
1508
+ output: completeResponse.output,
1509
+ };
1510
+ }
1511
+ // Backend decides: if nextTask is task-board activity or not frontend, return it (task board or done)
1512
+ const nextInteractive = (completeResponse.nextTask.executionMode === 'frontend' || completeResponse.nextTask.executionMode === 'both') &&
1513
+ !axpIsWorkflowTaskBoardActivityType(completeResponse.nextTask.activityType);
1514
+ if (!nextInteractive) {
1515
+ const humanTaskNext = completeResponse.nextTask;
1516
+ let boundaryContinuationOffer;
1517
+ if (humanTaskNext &&
1518
+ axpIsWorkflowTaskBoardActivityType(humanTaskNext.activityType)) {
1519
+ boundaryContinuationOffer =
1520
+ (await this.continuationHook?.offerAfterWorkflowStep?.({
1521
+ instanceId,
1522
+ completedActivityId: currentTask.activityId,
1523
+ resumeOutcome: execResult.outcome,
1524
+ pendingNextTask: humanTaskNext,
1525
+ })) ?? 'not-offered';
1526
+ }
1527
+ return {
1528
+ nextTask: humanTaskNext,
1529
+ state: currentState,
1530
+ boundaryContinuationOffer,
1531
+ };
1532
+ }
1533
+ // Continue with next interactive frontend task
1534
+ currentTask = completeResponse.nextTask;
1535
+ }
1536
+ else {
1537
+ // Not interactive (e.g. human-task / cartable) - return as-is for task board
1538
+ return {
1539
+ nextTask: currentTask,
1540
+ state: currentState,
1541
+ };
1542
+ }
1543
+ }
1544
+ // Max iterations reached
1545
+ if (iterationCount >= maxIterations) {
1546
+ warnWorkflowRuntime(this.debug, 'manager', 'Maximum interactive flow iterations reached', () => ({
1547
+ maxIterations,
1548
+ instanceId,
1549
+ }));
1550
+ }
1551
+ return {
1552
+ nextTask: currentTask,
1553
+ state: currentState,
1554
+ };
1555
+ }
1556
+ /**
1557
+ * Runs inline interactive activities when allowed, with an optional pre-flow continuation prompt.
1558
+ */
1559
+ async runInteractiveFlowWithContinuation(instanceId, pendingTask, state, continuationContext, suppressContinuation, activityOutputs) {
1560
+ let continuationOffer = 'not-offered';
1561
+ if (!suppressContinuation &&
1562
+ axpShouldOfferBeforeInteractiveFlow(continuationContext) &&
1563
+ axpIsWorkflowInlineFrontendTask(pendingTask) &&
1564
+ this.continuationHook?.offerBeforeInteractiveFlow) {
1565
+ continuationOffer =
1566
+ (await this.continuationHook.offerBeforeInteractiveFlow({
1567
+ ...continuationContext,
1568
+ instanceId,
1569
+ pendingNextTask: pendingTask,
1570
+ })) ?? 'not-offered';
1571
+ if (continuationOffer === 'declined') {
1572
+ await this.revertResumeAfterDeclinedInteractiveContinuation(instanceId, continuationContext, pendingTask);
1573
+ const revertedState = this.stateCache.get(instanceId) ?? state;
1574
+ return { nextTask: null, state: revertedState, continuationOffer };
1575
+ }
1576
+ }
1577
+ const interactiveResult = await this.executeInteractiveFlow(instanceId, pendingTask, state, activityOutputs);
1578
+ const boundaryOffer = interactiveResult.boundaryContinuationOffer;
1579
+ if (boundaryOffer && boundaryOffer !== 'not-offered') {
1580
+ continuationOffer = boundaryOffer;
1581
+ }
1582
+ return { ...interactiveResult, continuationOffer };
1583
+ }
1584
+ async invokeWorkflowContinuationAfterStep(context, priorOffer, suppressContinuation = false) {
1585
+ if (suppressContinuation || priorOffer === 'declined') {
1586
+ return;
1587
+ }
1588
+ if (priorOffer && priorOffer !== 'not-offered') {
1589
+ return;
1590
+ }
1591
+ await this.continuationHook?.offerAfterWorkflowStep?.(context);
1592
+ }
1593
+ /**
1594
+ * Restores the suspended task-board step when the user declines a pre-interactive continuation prompt.
1595
+ */
1596
+ async revertResumeAfterDeclinedInteractiveContinuation(instanceId, context, abortedTask) {
1597
+ const suspendedStepId = context.completedActivityId;
1598
+ if (!suspendedStepId) {
1599
+ return;
1600
+ }
1601
+ const revert = this.workflowEngine.revertResumeAfterDeclinedInteractiveContinuation?.bind(this.workflowEngine);
1602
+ if (!revert) {
1603
+ return;
1604
+ }
1605
+ const result = await revert({
1606
+ instanceId,
1607
+ suspendedStepId,
1608
+ abortedStepId: abortedTask.activityId,
1609
+ bookmarkId: context.completedBookmarkId,
1610
+ });
1611
+ if (result.success && result.state) {
1612
+ let normalizedState = { ...result.state };
1613
+ if (normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
1614
+ normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
1615
+ }
1616
+ this.stateCache.set(instanceId, normalizedState);
1617
+ }
1618
+ }
1619
+ /**
1620
+ * Start a new workflow instance.
1621
+ *
1622
+ * Creates a new workflow instance in backend and returns instance ID.
1623
+ * Backend decides what to do: returns pendingTask or indicates completion.
1624
+ *
1625
+ * @param workflowId - Workflow ID to start
1626
+ * @param input - Initial input data (optional)
1627
+ * @returns Start result with instanceId, state, and nextTask
1628
+ *
1629
+ * @example
1630
+ * ```typescript
1631
+ * const result = await workflowManager.start('my-workflow', { userId: '123' });
1632
+ *
1633
+ * if (result.success && result.nextTask) {
1634
+ * // Execute task if frontend
1635
+ * if (result.nextTask.executionMode === 'frontend') {
1636
+ * const execResult = await workflowManager.execute(result.nextTask);
1637
+ * await workflowManager.complete(result.instanceId!, result.nextTask, execResult.outcome, execResult.output);
1638
+ * }
1639
+ * }
1640
+ * ```
1641
+ */
1642
+ async start(workflowId, input = {}) {
1643
+ try {
1644
+ const response = await this.workflowEngine.start({
1645
+ workflowId,
1646
+ input,
1647
+ });
1648
+ // Cache state (normalize Date)
1649
+ let startNormalizedState = { ...response.state };
1650
+ if (startNormalizedState.lastUpdated && !(startNormalizedState.lastUpdated instanceof Date)) {
1651
+ startNormalizedState.lastUpdated = new Date(startNormalizedState.lastUpdated);
1652
+ }
1653
+ this.stateCache.set(response.instanceId, startNormalizedState);
1654
+ // 🎯 Interactive flow: Execute frontend activities that are NOT task-board types (those go to task board)
1655
+ let finalNextTask = response.pendingTask || null;
1656
+ let finalOutput = startNormalizedState.output;
1657
+ const pendingTask = response.pendingTask;
1658
+ if (pendingTask &&
1659
+ (pendingTask.executionMode === 'frontend' || pendingTask.executionMode === 'both') &&
1660
+ !axpIsWorkflowTaskBoardActivityType(pendingTask.activityType)) {
1661
+ const interactiveResult = await this.runInteractiveFlowWithContinuation(response.instanceId, pendingTask, startNormalizedState, { instanceId: response.instanceId, pendingNextTask: pendingTask }, isWorkflowContinuationSuppressed(input), (response.activityOutputs ?? response.state.activityOutputs));
1662
+ finalNextTask = interactiveResult.nextTask;
1663
+ startNormalizedState = interactiveResult.state;
1664
+ if (interactiveResult.output != null) {
1665
+ finalOutput = interactiveResult.output;
1666
+ }
1667
+ this.stateCache.set(response.instanceId, startNormalizedState);
1668
+ }
1669
+ // If backend returned null or non-executable task, return it as-is
1670
+ // Backend already decided workflow status (suspended, completed, etc.)
1671
+ return {
1672
+ success: true,
1673
+ instanceId: response.instanceId,
1674
+ state: startNormalizedState,
1675
+ nextTask: finalNextTask,
1676
+ output: finalOutput,
1677
+ };
1678
+ }
1679
+ catch (error) {
1680
+ errorWorkflowRuntime(this.debug, 'manager', 'Error starting workflow', () => ({
1681
+ error: error instanceof Error ? error.message : String(error),
1682
+ }));
1683
+ return {
1684
+ success: false,
1685
+ error: error instanceof Error ? error.message : 'Failed to start workflow',
1686
+ };
1687
+ }
1688
+ }
1689
+ /**
1690
+ * Resume a suspended workflow (e.g., after user interaction).
1691
+ *
1692
+ * Backend determines nextStep based on outcome and outcomeConnections.
1693
+ * Client only provides instanceId, stepId, outcome, and optional userInput.
1694
+ *
1695
+ * @param instanceId - Workflow instance ID
1696
+ * @param stepId - Step ID that was waiting for user input
1697
+ * @param outcome - User action outcome (e.g., 'Confirmed', 'Cancelled', 'Submitted')
1698
+ * @param userInput - Optional user input data
1699
+ * @param taskToken - Secure task token (required for secure resumption)
1700
+ * @returns Resume result with next task (if any)
1701
+ */
1702
+ async resume(instanceId, stepId, outcome, userInput, taskToken) {
1703
+ try {
1704
+ // Ensure taskToken is provided for secure resumption
1705
+ if (!taskToken) {
1706
+ throw new Error('Missing taskToken for resume operation');
1707
+ }
1708
+ // Backend handles everything: checks outcomeConnections and determines nextStep
1709
+ const response = await this.workflowEngine.resume({
1710
+ instanceId,
1711
+ stepId,
1712
+ taskToken,
1713
+ outcome,
1714
+ userInput,
1715
+ });
1716
+ // Update cache with state from backend
1717
+ let normalizedState = response.state ? { ...response.state } : undefined;
1718
+ if (normalizedState && normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
1719
+ normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
1720
+ }
1721
+ if (normalizedState) {
1722
+ this.stateCache.set(instanceId, normalizedState);
1723
+ }
1724
+ // 🎯 Interactive flow: Execute frontend activities that are NOT task-board types (those go to task board)
1725
+ let finalNextTask = response.nextTask || null;
1726
+ let finalOutput = response.output;
1727
+ const nextTask = response.nextTask;
1728
+ let resumeContinuationOffer;
1729
+ if (nextTask &&
1730
+ (nextTask.executionMode === 'frontend' || nextTask.executionMode === 'both') &&
1731
+ !axpIsWorkflowTaskBoardActivityType(nextTask.activityType)) {
1732
+ const completedBookmarkId = typeof userInput === 'object' && userInput != null && 'bookmarkId' in userInput
1733
+ ? String(userInput.bookmarkId ?? '')
1734
+ : undefined;
1735
+ const interactiveResult = await this.runInteractiveFlowWithContinuation(instanceId, nextTask, normalizedState, {
1736
+ instanceId,
1737
+ completedActivityId: stepId,
1738
+ completedBookmarkId: completedBookmarkId || undefined,
1739
+ resumeOutcome: outcome,
1740
+ pendingNextTask: nextTask,
1741
+ }, isWorkflowContinuationSuppressed(userInput), normalizedState?.activityOutputs);
1742
+ finalNextTask = interactiveResult.nextTask;
1743
+ normalizedState = interactiveResult.state;
1744
+ resumeContinuationOffer = interactiveResult.continuationOffer;
1745
+ if (interactiveResult.output != null) {
1746
+ finalOutput = interactiveResult.output;
1747
+ }
1748
+ this.stateCache.set(instanceId, normalizedState);
1749
+ }
1750
+ // If backend returned null or non-executable task, return it as-is
1751
+ // Backend already decided workflow status (suspended, completed, etc.)
1752
+ const resumeResult = {
1753
+ success: true,
1754
+ instanceId,
1755
+ state: normalizedState || response.state,
1756
+ nextTask: finalNextTask,
1757
+ output: finalOutput,
1758
+ };
1759
+ const completedBookmarkIdForContinuation = typeof userInput === 'object' && userInput != null && 'bookmarkId' in userInput
1760
+ ? String(userInput.bookmarkId ?? '')
1761
+ : undefined;
1762
+ await this.invokeWorkflowContinuationAfterStep({
1763
+ instanceId,
1764
+ completedActivityId: stepId,
1765
+ completedBookmarkId: completedBookmarkIdForContinuation || undefined,
1766
+ resumeOutcome: outcome,
1767
+ pendingNextTask: finalNextTask,
1768
+ }, resumeContinuationOffer, isWorkflowContinuationSuppressed(userInput));
1769
+ return resumeResult;
1770
+ }
1771
+ catch (error) {
1772
+ const { message, code } = getWorkflowEngineErrorInfo(error);
1773
+ return {
1774
+ success: false,
1775
+ instanceId,
1776
+ error: message,
1777
+ errorCode: code,
1778
+ };
1779
+ }
1780
+ }
1781
+ /**
1782
+ * Get workflow instance state.
1783
+ *
1784
+ * Retrieves state from cache (if valid) or from backend.
1785
+ *
1786
+ * @param instanceId - Workflow instance ID
1787
+ * @returns Workflow instance state or null if not found
1788
+ */
1789
+ async getState(instanceId) {
1790
+ // Check cache first
1791
+ const cached = this.stateCache.get(instanceId);
1792
+ if (cached) {
1793
+ // Normalize lastUpdated to Date (for cache safety)
1794
+ const normalizedCached = { ...cached };
1795
+ if (normalizedCached.lastUpdated && !(normalizedCached.lastUpdated instanceof Date)) {
1796
+ normalizedCached.lastUpdated = new Date(normalizedCached.lastUpdated);
1797
+ }
1798
+ // Validate cache age
1799
+ const cacheAge = Date.now() - normalizedCached.lastUpdated.getTime();
1800
+ if (cacheAge < this.CACHE_TTL) {
1801
+ return normalizedCached;
1802
+ }
1803
+ }
1804
+ // Fetch from backend
1805
+ try {
1806
+ const state = await this.workflowEngine.getState({
1807
+ instanceId,
1808
+ });
1809
+ // Normalize lastUpdated to Date (for cache safety)
1810
+ const normalizedState = { ...state };
1811
+ if (normalizedState.lastUpdated && !(normalizedState.lastUpdated instanceof Date)) {
1812
+ normalizedState.lastUpdated = new Date(normalizedState.lastUpdated);
1813
+ }
1814
+ // Update cache
1815
+ this.stateCache.set(instanceId, normalizedState);
1816
+ return normalizedState;
1817
+ }
1818
+ catch (error) {
1819
+ errorWorkflowRuntime(this.debug, 'manager', 'Error getting workflow state', () => ({
1820
+ error: error instanceof Error ? error.message : String(error),
1821
+ }));
1822
+ return null;
1823
+ }
1824
+ }
1825
+ /**
1826
+ * Claim a pooled workflow task (assign current user on the bookmark without advancing the workflow).
1827
+ * Supported only when the injected workflow engine implements {@link AXPWorkflowEngine.claimTask}.
1828
+ */
1829
+ async claimTask(instanceId, bookmarkId, stepId) {
1830
+ const claim = this.workflowEngine.claimTask?.bind(this.workflowEngine);
1831
+ if (!claim) {
1832
+ return {
1833
+ success: false,
1834
+ instanceId,
1835
+ error: 'Claim task is not supported by this workflow engine',
1836
+ };
1837
+ }
1838
+ try {
1839
+ const result = await claim({ instanceId, bookmarkId, stepId });
1840
+ return {
1841
+ success: result.success,
1842
+ instanceId,
1843
+ error: result.error,
1844
+ errorCode: result.errorCode,
1845
+ };
1846
+ }
1847
+ catch (error) {
1848
+ const { message, code } = getWorkflowEngineErrorInfo(error);
1849
+ return {
1850
+ success: false,
1851
+ instanceId,
1852
+ error: message,
1853
+ errorCode: code,
1854
+ };
1855
+ }
1856
+ }
1857
+ /**
1858
+ * Reassign a human-task bookmark to the current user without advancing the workflow.
1859
+ * Supported only when the injected workflow engine implements {@link AXPWorkflowEngine.reassignTaskToSelf}.
1860
+ */
1861
+ async reassignTaskToSelf(instanceId, bookmarkId, stepId) {
1862
+ const reassign = this.workflowEngine.reassignTaskToSelf?.bind(this.workflowEngine);
1863
+ if (!reassign) {
1864
+ return {
1865
+ success: false,
1866
+ instanceId,
1867
+ error: 'Reassign task is not supported by this workflow engine',
1868
+ };
1869
+ }
1870
+ try {
1871
+ const result = await reassign({ instanceId, bookmarkId, stepId });
1872
+ return {
1873
+ success: result.success,
1874
+ instanceId,
1875
+ error: result.error,
1876
+ errorCode: result.errorCode,
1877
+ };
1878
+ }
1879
+ catch (error) {
1880
+ const { message, code } = getWorkflowEngineErrorInfo(error);
1881
+ return {
1882
+ success: false,
1883
+ instanceId,
1884
+ error: message,
1885
+ errorCode: code,
1886
+ };
1887
+ }
1888
+ }
1889
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1890
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowManager, providedIn: 'root' }); }
1891
+ }
1892
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowManager, decorators: [{
1893
+ type: Injectable,
1894
+ args: [{
1895
+ providedIn: 'root',
1896
+ }]
1897
+ }] });
1898
+
1899
+ const AXP_WORKFLOW_PROVIDER = new InjectionToken('AXP_WORKFLOW_PROVIDER', {
1900
+ factory: () => [],
1901
+ });
1902
+ const AXP_WORKFLOW_CATEGORY_PROVIDER = new InjectionToken('AXP_WORKFLOW_CATEGORY_PROVIDER', {
1903
+ factory: () => [],
1904
+ });
1905
+
1906
+ //#endregion
1907
+ /**
1908
+ * Local engine implementation that manages workflow progression and state.
1909
+ *
1910
+ * This engine:
1911
+ * - Returns frontend/both activities as pendingTask (does NOT execute them)
1912
+ * - Skips backend activities (does not error, continues execution)
1913
+ * - Maintains workflow state in memory
1914
+ * - Does not require backend API calls
1915
+ *
1916
+ * Execution of frontend tasks is handled by AXPWorkflowManager via ActivityExecutor.
1917
+ * This engine only manages workflow progression and state storage.
1918
+ *
1919
+ * This is the DEFAULT engine provider. Applications can override it with
1920
+ * an API-based engine implementation.
1921
+ */
1922
+ class AXPWorkflowLocalEngine {
1923
+ constructor() {
1924
+ //#region ---- Services & Dependencies ----
1925
+ this.activityDefinitionService = inject(AXPActivityDefinitionService);
1926
+ this.workflowProviders = inject(AXP_WORKFLOW_PROVIDER, { optional: true }) || [];
1927
+ this.multiLanguageResolver = inject(AXTranslationService);
1928
+ this.debug = inject(AXPDebugService);
1929
+ //#endregion
1930
+ //#region ---- Instance Storage ----
1931
+ /**
1932
+ * In-memory storage for workflow instances.
1933
+ * Key: instanceId
1934
+ * Value: LocalWorkflowState
1935
+ */
1936
+ this.instances = new Map();
1937
+ /**
1938
+ * Task token storage for secure resume operations.
1939
+ * Key: taskToken
1940
+ * Value: { instanceId, activityId }
1941
+ */
1942
+ this.taskTokens = new Map();
1943
+ }
1944
+ //#endregion
1945
+ //#region ---- Public Methods (AXPWorkflowEngine) ----
1946
+ /**
1947
+ * Start a new workflow instance.
1948
+ *
1949
+ * Creates an in-memory workflow instance and progresses it.
1950
+ * Frontend/both activities are returned as pendingTask for external execution.
1951
+ * Backend activities are skipped.
1952
+ */
1953
+ async start(request) {
1954
+ // Generate instance ID
1955
+ const instanceId = AXPDataGenerator.uuid();
1956
+ const now = new Date();
1957
+ // Load workflow definition
1958
+ const definition = await this.getDefinition(request.workflowId);
1959
+ if (!definition) {
1960
+ errorWorkflowRuntime(this.debug, 'local-engine', 'Workflow definition not found', () => ({
1961
+ workflowId: request.workflowId,
1962
+ }));
1963
+ throw new Error(`Workflow definition not found: ${request.workflowId}`);
1964
+ }
1965
+ // Initialize workflow state
1966
+ const state = {
1967
+ instanceId,
1968
+ workflowId: request.workflowId,
1969
+ status: 'running',
1970
+ variables: {},
1971
+ activityOutputs: {},
1972
+ lastActivityOutput: undefined,
1973
+ input: request.input || {},
1974
+ output: undefined,
1975
+ lastUpdated: now,
1976
+ };
1977
+ // Create local state
1978
+ const localState = {
1979
+ instanceId,
1980
+ workflowId: request.workflowId,
1981
+ definition,
1982
+ state,
1983
+ completedActivities: new Set(),
1984
+ activityResults: new Map(),
1985
+ };
1986
+ // Store instance
1987
+ this.instances.set(instanceId, localState);
1988
+ // Execute workflow steps
1989
+ const pendingTask = await this.executeWorkflowSteps(localState);
1990
+ // Update state
1991
+ localState.state.lastUpdated = new Date();
1992
+ return {
1993
+ instanceId,
1994
+ state: localState.state,
1995
+ pendingTask: pendingTask || null,
1996
+ activityOutputs: localState.state.activityOutputs,
1997
+ lastActivityOutput: localState.state.lastActivityOutput,
1998
+ };
1999
+ }
2000
+ /**
2001
+ * Resume a suspended workflow instance.
2002
+ *
2003
+ * Validates task token, applies externally executed result,
2004
+ * and continues progressing workflow steps.
2005
+ */
2006
+ async resume(request) {
2007
+ // Validate task token
2008
+ const tokenInfo = this.taskTokens.get(request.taskToken);
2009
+ if (!tokenInfo || tokenInfo.instanceId !== request.instanceId || tokenInfo.activityId !== request.stepId) {
2010
+ throw new Error('Invalid task token');
2011
+ }
2012
+ // Get instance
2013
+ const localState = this.instances.get(request.instanceId);
2014
+ if (!localState) {
2015
+ throw new Error(`Workflow instance not found: ${request.instanceId}`);
2016
+ }
2017
+ // Store activity result (from external execution)
2018
+ const outcome = request.outcome ?? 'Done';
2019
+ localState.activityResults.set(request.stepId, {
2020
+ output: request.userInput || {},
2021
+ outcome,
2022
+ });
2023
+ localState.completedActivities.add(request.stepId);
2024
+ // Store activity output for expression evaluation
2025
+ localState.state.activityOutputs = {
2026
+ ...(localState.state.activityOutputs || {}),
2027
+ [request.stepId]: request.userInput || {},
2028
+ };
2029
+ localState.state.lastActivityOutput = request.userInput || {};
2030
+ // Merge output/userInput into state variables
2031
+ if (request.userInput) {
2032
+ localState.state.variables = {
2033
+ ...localState.state.variables,
2034
+ ...(request.userInput || {}),
2035
+ };
2036
+ }
2037
+ localState.state.variables = {
2038
+ ...localState.state.variables,
2039
+ [`${request.stepId}_outcome`]: outcome,
2040
+ [`${request.stepId}_activityOutput`]: request.userInput || {},
2041
+ };
2042
+ // Mark activity as completed and continue progression
2043
+ // Continue progressing workflow steps (skipping backend activities)
2044
+ const nextTask = await this.executeWorkflowSteps(localState);
2045
+ // Update state
2046
+ localState.state.lastUpdated = new Date();
2047
+ // Determine final status
2048
+ if (!nextTask && localState.state.status === 'running') {
2049
+ localState.state.status = 'completed';
2050
+ localState.state.output = localState.state.variables;
2051
+ }
2052
+ return {
2053
+ output: request.userInput || {},
2054
+ outcomes: { [outcome]: true },
2055
+ state: localState.state,
2056
+ nextTask: nextTask || null,
2057
+ };
2058
+ }
2059
+ /**
2060
+ * Get current workflow instance state.
2061
+ */
2062
+ async getState(request) {
2063
+ const localState = this.instances.get(request.instanceId);
2064
+ if (!localState) {
2065
+ throw new Error(`Workflow instance not found: ${request.instanceId}`);
2066
+ }
2067
+ // Normalize lastUpdated to Date (for cache safety)
2068
+ const state = { ...localState.state };
2069
+ if (state.lastUpdated && !(state.lastUpdated instanceof Date)) {
2070
+ state.lastUpdated = new Date(state.lastUpdated);
2071
+ }
2072
+ return state;
2073
+ }
2074
+ //#endregion
2075
+ //#region ---- Private Methods ----
2076
+ /**
2077
+ * Get workflow definition from available providers.
2078
+ */
2079
+ async getDefinition(workflowId) {
2080
+ // Try all providers in order
2081
+ const resolvedProviders = await Promise.allSettled(this.workflowProviders);
2082
+ for (const p of resolvedProviders) {
2083
+ if (p.status === 'fulfilled' && p.value) {
2084
+ const provider = p.value;
2085
+ // Check if provider has getByName method
2086
+ if (typeof provider.getByName === 'function') {
2087
+ try {
2088
+ const definition = await provider.getByName(workflowId);
2089
+ if (definition) {
2090
+ return definition;
2091
+ }
2092
+ }
2093
+ catch {
2094
+ // Continue on error - try other providers
2095
+ }
2096
+ }
2097
+ }
2098
+ }
2099
+ return null;
2100
+ }
2101
+ /**
2102
+ * Progress workflow steps starting from the current position.
2103
+ *
2104
+ * For frontend/both activities: returns task immediately (suspends workflow).
2105
+ * For backend activities: skips and continues.
2106
+ *
2107
+ * Returns the next pending task (if frontend activity found) or null (if completed).
2108
+ */
2109
+ async executeWorkflowSteps(localState) {
2110
+ const graph = localState.definition.graph;
2111
+ const activities = graph.activities || [];
2112
+ const connections = graph.connections || [];
2113
+ // Build activity map
2114
+ const activityMap = new Map();
2115
+ activities.forEach((activity) => {
2116
+ activityMap.set(activity.id, activity);
2117
+ });
2118
+ // Build connection graph
2119
+ const outgoingConnections = new Map();
2120
+ const incomingConnections = new Map();
2121
+ connections.forEach((conn) => {
2122
+ const sourceId = conn.source.activtyName;
2123
+ const targetId = conn.target.activtyName;
2124
+ if (!outgoingConnections.has(sourceId)) {
2125
+ outgoingConnections.set(sourceId, []);
2126
+ }
2127
+ outgoingConnections.get(sourceId).push(conn);
2128
+ if (!incomingConnections.has(targetId)) {
2129
+ incomingConnections.set(targetId, []);
2130
+ }
2131
+ incomingConnections.get(targetId).push(conn);
2132
+ });
2133
+ // Find starting activity (use startActivityId or no incoming connections, or first activity)
2134
+ let currentActivityId = localState.currentActivityId;
2135
+ if (!currentActivityId) {
2136
+ // Use startActivityId if available
2137
+ if (graph.startActivityId) {
2138
+ currentActivityId = graph.startActivityId;
2139
+ }
2140
+ else {
2141
+ // Find root activity (no incoming connections)
2142
+ for (const activity of activities) {
2143
+ if (!incomingConnections.has(activity.id)) {
2144
+ currentActivityId = activity.id;
2145
+ break;
2146
+ }
2147
+ }
2148
+ // If no root found, use first activity
2149
+ if (!currentActivityId && activities.length > 0) {
2150
+ currentActivityId = activities[0].id;
2151
+ }
2152
+ }
2153
+ }
2154
+ // Execute workflow steps
2155
+ while (currentActivityId) {
2156
+ const activity = activityMap.get(currentActivityId);
2157
+ if (!activity) {
2158
+ break;
2159
+ }
2160
+ // Skip if already completed
2161
+ if (localState.completedActivities.has(currentActivityId)) {
2162
+ // Move to next activity
2163
+ const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
2164
+ currentActivityId = nextId ?? undefined;
2165
+ continue;
2166
+ }
2167
+ // Get activity definition to retrieve executionMode and title
2168
+ const activityDefinition = await this.activityDefinitionService.getActivityByName(activity.name);
2169
+ const executionMode = activityDefinition?.executionMode || 'frontend';
2170
+ const activityTitle = this.multiLanguageResolver.resolve(activityDefinition?.title) || activityDefinition?.name;
2171
+ // Handle backend activities: skip
2172
+ if (executionMode === 'backend') {
2173
+ localState.completedActivities.add(currentActivityId);
2174
+ localState.activityResults.set(currentActivityId, {
2175
+ output: null,
2176
+ outcome: 'Done',
2177
+ });
2178
+ // Move to next activity
2179
+ const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
2180
+ currentActivityId = nextId ?? undefined;
2181
+ continue;
2182
+ }
2183
+ // Handle frontend/both activities: return as pendingTask (do NOT execute)
2184
+ if (executionMode === 'frontend' || executionMode === 'both') {
2185
+ // Create task for external execution
2186
+ // Note: Expression evaluation will be done by ActivityExecutor
2187
+ const task = {
2188
+ taskToken: AXPDataGenerator.uuid(),
2189
+ activityId: activity.id,
2190
+ activityType: activity.name,
2191
+ activityName: activityTitle || undefined,
2192
+ executionMode: executionMode,
2193
+ input: activity.inputs || {}, // Pass raw inputs - executor will evaluate
2194
+ };
2195
+ // Store task token for secure resume
2196
+ this.taskTokens.set(task.taskToken, {
2197
+ instanceId: localState.instanceId,
2198
+ activityId: activity.id,
2199
+ });
2200
+ // Update state to indicate suspension
2201
+ localState.currentActivityId = currentActivityId;
2202
+ localState.state.status = 'suspended';
2203
+ localState.state.currentStepId = currentActivityId;
2204
+ // Return task immediately - AXPWorkflowManager will execute it
2205
+ return task;
2206
+ }
2207
+ // Move to next activity
2208
+ const nextId = this.getNextActivityId(currentActivityId, outgoingConnections, localState.activityResults);
2209
+ currentActivityId = nextId ?? undefined;
2210
+ }
2211
+ // Workflow completed
2212
+ localState.state.status = 'completed';
2213
+ localState.state.output = localState.state.variables;
2214
+ localState.currentActivityId = undefined;
2215
+ return null;
2216
+ }
2217
+ /**
2218
+ * Get next activity ID based on connections and outcomes.
2219
+ */
2220
+ getNextActivityId(currentActivityId, outgoingConnections, activityResults) {
2221
+ const connections = outgoingConnections.get(currentActivityId) || [];
2222
+ if (connections.length === 0) {
2223
+ return null; // No outgoing connections - workflow ends
2224
+ }
2225
+ // Get current activity result
2226
+ const result = activityResults.get(currentActivityId);
2227
+ const outcome = result?.outcome || 'Done';
2228
+ // Find connection matching outcome
2229
+ // Outcome matching is typically done via port name (e.g., "Done", "Failed")
2230
+ for (const conn of connections) {
2231
+ const sourcePort = conn.source.port || 'Done';
2232
+ if (sourcePort === outcome) {
2233
+ return conn.target.activtyName;
2234
+ }
2235
+ }
2236
+ // If no matching outcome, use first connection (default path)
2237
+ if (connections.length > 0) {
2238
+ return connections[0].target.activtyName;
2239
+ }
2240
+ return null;
2241
+ }
2242
+ async frontActivtyComplete(request) {
2243
+ const localState = this.instances.get(request.instanceId);
2244
+ if (!localState) {
2245
+ throw new Error(`Workflow instance not found: ${request.instanceId}`);
2246
+ }
2247
+ // Store activity result
2248
+ const outcome = request.outcome ?? 'Done';
2249
+ localState.activityResults.set(request.activityNode, {
2250
+ output: request.output || {},
2251
+ outcome,
2252
+ });
2253
+ localState.completedActivities.add(request.activityNode);
2254
+ // Store outputs for expression evaluation
2255
+ localState.state.activityOutputs = {
2256
+ ...(localState.state.activityOutputs || {}),
2257
+ [request.activityNode]: request.output || {},
2258
+ };
2259
+ localState.state.lastActivityOutput = request.output || {};
2260
+ // Merge output into workflow variables
2261
+ localState.state.variables = {
2262
+ ...localState.state.variables,
2263
+ ...(request.output || {}),
2264
+ };
2265
+ // Continue workflow progression
2266
+ const nextTask = await this.executeWorkflowSteps(localState);
2267
+ // Update state timestamp
2268
+ localState.state.lastUpdated = new Date();
2269
+ // Determine final status
2270
+ if (!nextTask && localState.state.status === 'running') {
2271
+ localState.state.status = 'completed';
2272
+ localState.state.output = localState.state.variables;
2273
+ }
2274
+ return {
2275
+ output: request.output || {},
2276
+ outcomes: { [outcome]: true },
2277
+ nextTask: nextTask || null,
2278
+ state: localState.state,
2279
+ };
2280
+ }
2281
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowLocalEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2282
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowLocalEngine }); }
2283
+ }
2284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowLocalEngine, decorators: [{
2285
+ type: Injectable
2286
+ }] });
2287
+
2288
+ // Workflow Runtime Services
2289
+
2290
+ //#region ---- Imports ----
2291
+ //#endregion
2292
+ /**
2293
+ * Optimized Workflow Definition Service
2294
+ *
2295
+ * Manages workflow definitions (metadata) for UI and tooling.
2296
+ * Similar to AXPActivityDefinitionService - only handles metadata, not execution.
2297
+ *
2298
+ * Performance optimizations:
2299
+ * 1. Uses childrenCount to determine if category has children (no query needed)
2300
+ * 2. Uses itemsCount to determine if category has workflows (no query needed)
2301
+ * 3. Aggressive caching prevents duplicate API calls
2302
+ * 4. Single pending request per resource prevents race conditions
2303
+ * 5. Lazy loading - only loads data when needed
2304
+ */
2305
+ class AXPWorkflowDefinitionService {
2306
+ constructor() {
2307
+ //#region ---- Providers & Caches ----
2308
+ this.categoryProviders = inject(AXP_WORKFLOW_CATEGORY_PROVIDER, { optional: true }) || [];
2309
+ this.workflowProviders = inject(AXP_WORKFLOW_PROVIDER, { optional: true }) || [];
2310
+ //#endregion
2311
+ //#region ---- Cache Storage ----
2312
+ /** Cache for categories by id - O(1) lookup */
2313
+ this.categoriesById = new Map();
2314
+ /** Cache for categories by parentId - O(1) lookup */
2315
+ this.categoriesByParentId = new Map();
2316
+ /** Cache for workflow definitions by categoryId - O(1) lookup */
2317
+ this.workflowsByCategory = new Map();
2318
+ /** Cache for individual workflow definitions by name - O(1) lookup */
2319
+ this.workflowsByName = new Map();
2320
+ /** Track which provider index owns each category (by category ID) */
2321
+ this.categoryOwnership = new Map(); // Maps categoryId → provider index
2322
+ /** Pending API requests to prevent duplicate calls */
2323
+ this.pendingCategoriesRequests = new Map();
2324
+ this.pendingWorkflowsRequests = new Map();
2325
+ this.pendingWorkflowRequests = new Map();
2326
+ }
2327
+ //#endregion
2328
+ //#region ---- Public API: Categories ----
2329
+ /**
2330
+ * Get categories by parentId with aggressive caching
2331
+ *
2332
+ * Optimization: Returns cached result immediately if available,
2333
+ * preventing unnecessary API calls during navigation
2334
+ *
2335
+ * @param parentId - Parent category ID (undefined = root categories)
2336
+ * @returns Array of categories with count metadata (childrenCount, itemsCount)
2337
+ */
2338
+ async getCategories(parentId) {
2339
+ // ✅ Fast path: Return cached result
2340
+ if (this.categoriesByParentId.has(parentId)) {
2341
+ return this.categoriesByParentId.get(parentId);
2342
+ }
2343
+ // ✅ Prevent duplicate requests: Return pending promise
2344
+ if (this.pendingCategoriesRequests.has(parentId)) {
2345
+ return this.pendingCategoriesRequests.get(parentId);
2346
+ }
2347
+ // ✅ Create single request and cache it
2348
+ const requestPromise = this.loadCategoriesFromProviders(parentId);
2349
+ this.pendingCategoriesRequests.set(parentId, requestPromise);
2350
+ return requestPromise;
2351
+ }
2352
+ /**
2353
+ * Get single category by ID with O(1) lookup
2354
+ *
2355
+ * Optimization: Uses Map for instant retrieval, falls back to
2356
+ * searching cache, then providers if not found
2357
+ */
2358
+ async getCategoryById(categoryId) {
2359
+ // ✅ Fast path: O(1) lookup in cache
2360
+ if (this.categoriesById.has(categoryId)) {
2361
+ return this.categoriesById.get(categoryId);
2362
+ }
2363
+ // ✅ Search in cached parent-child lists
2364
+ for (const categories of this.categoriesByParentId.values()) {
2365
+ const found = categories.find(cat => cat.id === categoryId);
2366
+ if (found) {
2367
+ this.categoriesById.set(categoryId, found);
2368
+ return found;
2369
+ }
2370
+ }
2371
+ // ✅ Load root categories if not loaded
2372
+ if (!this.categoriesByParentId.has(undefined)) {
2373
+ await this.getCategories();
2374
+ if (this.categoriesById.has(categoryId)) {
2375
+ return this.categoriesById.get(categoryId);
2376
+ }
2377
+ }
2378
+ // ✅ Breadth-first search through hierarchy
2379
+ return this.searchCategoryInHierarchy(categoryId);
2380
+ }
2381
+ /**
2382
+ * Get category path from root to specified category
2383
+ *
2384
+ * Optimization: Builds path using cached categories only
2385
+ */
2386
+ async getCategoriesPathById(categoryId) {
2387
+ const path = [];
2388
+ let currentCategoryId = categoryId;
2389
+ while (currentCategoryId) {
2390
+ const category = await this.getCategoryById(currentCategoryId);
2391
+ if (!category) {
2392
+ throw new Error(`Category '${currentCategoryId}' not found`);
2393
+ }
2394
+ path.unshift(category);
2395
+ currentCategoryId = category.parentId;
2396
+ }
2397
+ return path;
2398
+ }
2399
+ //#endregion
2400
+ //#region ---- Public API: Workflow Definitions ----
2401
+ /**
2402
+ * Get workflow definitions for a category with smart caching
2403
+ *
2404
+ * Optimization: Checks itemsCount before querying
2405
+ * - If itemsCount = 0, returns empty array (no API call)
2406
+ * - If itemsCount > 0, loads and caches workflow definitions
2407
+ * - Returns cached result on subsequent calls
2408
+ *
2409
+ * @param categoryId - Category ID to get workflow definitions from
2410
+ * @returns Array of workflow definitions
2411
+ */
2412
+ async getWorkflowsByCategoryId(categoryId) {
2413
+ // ✅ Fast path: Return cached result
2414
+ if (this.workflowsByCategory.has(categoryId)) {
2415
+ return this.workflowsByCategory.get(categoryId);
2416
+ }
2417
+ // ✅ Smart optimization: Check itemsCount before querying
2418
+ const category = await this.getCategoryById(categoryId);
2419
+ if (category && category.itemsCount !== undefined && category.itemsCount === 0) {
2420
+ // Category has no workflows - cache empty array and skip API call
2421
+ const emptyArray = [];
2422
+ this.workflowsByCategory.set(categoryId, emptyArray);
2423
+ return emptyArray;
2424
+ }
2425
+ // ✅ Prevent duplicate requests
2426
+ if (this.pendingWorkflowsRequests.has(categoryId)) {
2427
+ return this.pendingWorkflowsRequests.get(categoryId);
2428
+ }
2429
+ // ✅ Load from providers
2430
+ const requestPromise = this.loadWorkflowsFromProviders(categoryId);
2431
+ this.pendingWorkflowsRequests.set(categoryId, requestPromise);
2432
+ return requestPromise;
2433
+ }
2434
+ /**
2435
+ * Get single workflow definition by name with O(1) lookup
2436
+ *
2437
+ * Optimization: Uses Map for instant retrieval
2438
+ *
2439
+ * @param name - Workflow name (unique identifier)
2440
+ * @returns Workflow definition or undefined if not found
2441
+ */
2442
+ async getWorkflowByName(name) {
2443
+ // ✅ Fast path: O(1) lookup in cache
2444
+ if (this.workflowsByName.has(name)) {
2445
+ return this.workflowsByName.get(name);
2446
+ }
2447
+ // ✅ Prevent duplicate requests
2448
+ if (this.pendingWorkflowRequests.has(name)) {
2449
+ return this.pendingWorkflowRequests.get(name);
2450
+ }
2451
+ // ✅ Load from providers
2452
+ const requestPromise = this.loadWorkflowFromProviders(name);
2453
+ this.pendingWorkflowRequests.set(name, requestPromise);
2454
+ return requestPromise;
2455
+ }
2456
+ /**
2457
+ * Get category ID containing a specific workflow definition
2458
+ *
2459
+ * Optimization: Searches cache first, loads on-demand if needed
2460
+ */
2461
+ async getCategoryIdByWorkflowName(workflowName) {
2462
+ // ✅ Search in cached workflow definitions
2463
+ for (const [categoryId, definitions] of this.workflowsByCategory.entries()) {
2464
+ if (definitions.some(def => def.name === workflowName)) {
2465
+ return categoryId;
2466
+ }
2467
+ }
2468
+ // ✅ Try loading the workflow definition to find its category
2469
+ // Note: AXPWorkflowDefinition doesn't have category field
2470
+ // Category is managed separately through category providers
2471
+ // This method searches through cached categories
2472
+ const definition = await this.getWorkflowByName(workflowName);
2473
+ if (definition) {
2474
+ // Search through all categories to find which one contains this workflow
2475
+ const categories = await this.getCategories();
2476
+ for (const category of categories) {
2477
+ const workflows = await this.getWorkflowsByCategoryId(category.id);
2478
+ if (workflows.some(w => w.name === workflowName)) {
2479
+ return category.id;
2480
+ }
2481
+ }
2482
+ }
2483
+ return undefined;
2484
+ }
2485
+ /**
2486
+ * Get category path for a workflow
2487
+ */
2488
+ async getCategoriesPathByWorkflowName(workflowName) {
2489
+ const categoryId = await this.getCategoryIdByWorkflowName(workflowName);
2490
+ if (!categoryId) {
2491
+ throw new Error(`Workflow '${workflowName}' not found in any category`);
2492
+ }
2493
+ return this.getCategoriesPathById(categoryId);
2494
+ }
2495
+ //#endregion
2496
+ //#region ---- Private: Data Loading ----
2497
+ /**
2498
+ * Load categories from providers and cache results
2499
+ *
2500
+ * Optimization: Tracks provider ownership to avoid unnecessary API calls
2501
+ * - For root (parentId = undefined): Query ALL providers
2502
+ * - For children: Only query the provider that owns the parent
2503
+ */
2504
+ async loadCategoriesFromProviders(parentId) {
2505
+ try {
2506
+ const resolvedProviders = await Promise.allSettled(this.categoryProviders);
2507
+ const categories = [];
2508
+ // Determine which provider(s) to query
2509
+ const providerIndicesToQuery = parentId
2510
+ ? this.getProviderIndexForCategory(parentId)
2511
+ : null; // Root: query all providers
2512
+ for (let i = 0; i < resolvedProviders.length; i++) {
2513
+ const p = resolvedProviders[i];
2514
+ // Skip if we have a specific provider index and this isn't it
2515
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
2516
+ continue;
2517
+ }
2518
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
2519
+ try {
2520
+ const cats = await p.value.getList(parentId);
2521
+ if (Array.isArray(cats) && cats.length > 0) {
2522
+ categories.push(...cats);
2523
+ // ✅ Track ownership: This provider INDEX owns these categories
2524
+ cats.forEach(cat => this.categoryOwnership.set(cat.id, i));
2525
+ }
2526
+ }
2527
+ catch {
2528
+ // Continue on error - try other providers
2529
+ }
2530
+ }
2531
+ }
2532
+ // ✅ Cache results for fast subsequent access
2533
+ this.categoriesByParentId.set(parentId, categories);
2534
+ categories.forEach(cat => this.categoriesById.set(cat.id, cat));
2535
+ return categories;
2536
+ }
2537
+ finally {
2538
+ this.pendingCategoriesRequests.delete(parentId);
2539
+ }
2540
+ }
2541
+ /**
2542
+ * Get the provider index that owns a specific category
2543
+ *
2544
+ * @returns Array with provider index, or null if ownership unknown (query all)
2545
+ */
2546
+ getProviderIndexForCategory(categoryId) {
2547
+ const ownerIndex = this.categoryOwnership.get(categoryId);
2548
+ if (ownerIndex !== undefined) {
2549
+ return [ownerIndex];
2550
+ }
2551
+ // Ownership unknown - will query all providers (fallback)
2552
+ return null;
2553
+ }
2554
+ /**
2555
+ * Load workflow definitions from providers and cache results
2556
+ *
2557
+ * Optimization: Only queries the provider that owns the category
2558
+ * Uses provider INDEX to match category provider with workflow provider
2559
+ */
2560
+ async loadWorkflowsFromProviders(categoryId) {
2561
+ try {
2562
+ const resolvedProviders = await Promise.allSettled(this.workflowProviders);
2563
+ const definitions = [];
2564
+ // ✅ Smart routing: Get provider INDEX that owns this category
2565
+ const ownerIndex = this.categoryOwnership.get(categoryId);
2566
+ const providerIndicesToQuery = ownerIndex !== undefined ? [ownerIndex] : null;
2567
+ for (let i = 0; i < resolvedProviders.length; i++) {
2568
+ const p = resolvedProviders[i];
2569
+ // Skip if we have a specific provider index and this isn't it
2570
+ if (providerIndicesToQuery !== null && !providerIndicesToQuery.includes(i)) {
2571
+ continue;
2572
+ }
2573
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getList === 'function') {
2574
+ try {
2575
+ const defs = await p.value.getList(categoryId);
2576
+ if (Array.isArray(defs)) {
2577
+ definitions.push(...defs);
2578
+ }
2579
+ }
2580
+ catch {
2581
+ // Continue on error - try other providers
2582
+ }
2583
+ }
2584
+ }
2585
+ // ✅ Cache results for fast subsequent access
2586
+ this.workflowsByCategory.set(categoryId, definitions);
2587
+ definitions.forEach(def => {
2588
+ if (def.name) {
2589
+ this.workflowsByName.set(def.name, def);
2590
+ }
2591
+ });
2592
+ return definitions;
2593
+ }
2594
+ finally {
2595
+ this.pendingWorkflowsRequests.delete(categoryId);
2596
+ }
2597
+ }
2598
+ /**
2599
+ * Load single workflow definition from providers and cache result
2600
+ */
2601
+ async loadWorkflowFromProviders(name) {
2602
+ try {
2603
+ const resolvedProviders = await Promise.allSettled(this.workflowProviders);
2604
+ // Try providers first
2605
+ for (const p of resolvedProviders) {
2606
+ if (p.status === 'fulfilled' && p.value && typeof p.value.getByName === 'function') {
2607
+ try {
2608
+ const definition = await p.value.getByName(name);
2609
+ if (definition) {
2610
+ this.workflowsByName.set(name, definition);
2611
+ return definition;
2612
+ }
2613
+ }
2614
+ catch {
2615
+ // Continue on error
2616
+ }
2617
+ }
2618
+ }
2619
+ // Fallback: Search in cached workflow definitions
2620
+ for (const definitions of this.workflowsByCategory.values()) {
2621
+ const found = definitions.find(def => def.name === name);
2622
+ if (found) {
2623
+ this.workflowsByName.set(name, found);
2624
+ return found;
2625
+ }
2626
+ }
2627
+ return undefined;
2628
+ }
2629
+ finally {
2630
+ this.pendingWorkflowRequests.delete(name);
2631
+ }
2632
+ }
2633
+ /**
2634
+ * Breadth-first search through category hierarchy
2635
+ */
2636
+ async searchCategoryInHierarchy(categoryId) {
2637
+ const searchQueue = [undefined];
2638
+ const searched = new Set();
2639
+ while (searchQueue.length > 0) {
2640
+ const parentId = searchQueue.shift();
2641
+ if (searched.has(parentId))
2642
+ continue;
2643
+ searched.add(parentId);
2644
+ const categories = await this.getCategories(parentId);
2645
+ const found = categories.find(cat => cat.id === categoryId);
2646
+ if (found) {
2647
+ return found;
2648
+ }
2649
+ // ✅ Optimization: Only search children if childrenCount > 0
2650
+ for (const category of categories) {
2651
+ if (category.childrenCount > 0 && !searched.has(category.id)) {
2652
+ searchQueue.push(category.id);
2653
+ }
2654
+ }
2655
+ }
2656
+ return undefined;
2657
+ }
2658
+ //#endregion
2659
+ //#region ---- Cache Management ----
2660
+ /**
2661
+ * Check if category has children (uses cached count)
2662
+ */
2663
+ categoryHasChildren(categoryId) {
2664
+ const category = this.categoriesById.get(categoryId);
2665
+ return category ? category.childrenCount > 0 : false;
2666
+ }
2667
+ /**
2668
+ * Check if category has workflows (uses cached count)
2669
+ */
2670
+ categoryHasWorkflows(categoryId) {
2671
+ const category = this.categoriesById.get(categoryId);
2672
+ return category ? (category.itemsCount ?? 0) > 0 : false;
2673
+ }
2674
+ /**
2675
+ * Clear all caches
2676
+ */
2677
+ clearAllCache() {
2678
+ this.categoriesById.clear();
2679
+ this.categoriesByParentId.clear();
2680
+ this.workflowsByCategory.clear();
2681
+ this.workflowsByName.clear();
2682
+ this.categoryOwnership.clear();
2683
+ this.pendingCategoriesRequests.clear();
2684
+ this.pendingWorkflowsRequests.clear();
2685
+ this.pendingWorkflowRequests.clear();
2686
+ }
2687
+ /**
2688
+ * Clear categories cache only
2689
+ */
2690
+ clearCategoriesCache() {
2691
+ this.categoriesById.clear();
2692
+ this.categoriesByParentId.clear();
2693
+ this.categoryOwnership.clear();
2694
+ this.pendingCategoriesRequests.clear();
2695
+ }
2696
+ /**
2697
+ * Clear workflows cache only
2698
+ */
2699
+ clearWorkflowsCache() {
2700
+ this.workflowsByCategory.clear();
2701
+ this.workflowsByName.clear();
2702
+ this.pendingWorkflowsRequests.clear();
2703
+ this.pendingWorkflowRequests.clear();
2704
+ }
2705
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2706
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowDefinitionService, providedIn: 'root' }); }
2707
+ }
2708
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowDefinitionService, decorators: [{
2709
+ type: Injectable,
2710
+ args: [{
2711
+ providedIn: 'root',
2712
+ }]
2713
+ }] });
2714
+
2715
+ class AXPWorkflowModule {
2716
+ static forRoot(config) {
2717
+ return {
2718
+ ngModule: AXPWorkflowModule,
2719
+ providers: [
2720
+ {
2721
+ provide: 'AXPWorkflowModuleFactory',
2722
+ useFactory: (registry) => () => {
2723
+ registry.registerAction('start-workflow', AXPStartWorkflowAction);
2724
+ registry.registerAction('decide', AXPWorkflowDecideAction);
2725
+ //
2726
+ if (config?.functions) {
2727
+ for (const [key, type] of Object.entries(config.functions)) {
2728
+ registry.registerFunction(key, type);
2729
+ }
2730
+ }
2731
+ //
2732
+ if (config?.actions) {
2733
+ for (const [key, type] of Object.entries(config.actions)) {
2734
+ registry.registerAction(key, type);
2735
+ }
2736
+ }
2737
+ //
2738
+ if (config?.workflows) {
2739
+ for (const [key, type] of Object.entries(config.workflows)) {
2740
+ registry.registerWorkflow(key, type);
2741
+ }
2742
+ }
2743
+ },
2744
+ deps: [AXPWorkflowRegistryService],
2745
+ multi: true,
2746
+ },
2747
+ ...Object.values(config?.actions ?? { AXPStartWorkflowAction }),
2748
+ ...Object.values(config?.functions ?? {}),
2749
+ ],
2750
+ };
2751
+ }
2752
+ static forChild(config) {
2753
+ return {
2754
+ ngModule: AXPWorkflowModule,
2755
+ providers: [
2756
+ // Built-in activities are already registered in forRoot via @NgModule providers
2757
+ // No need to register again in forChild
2758
+ {
2759
+ provide: 'AXPWorkflowModuleFactory',
2760
+ useFactory: (registry) => () => {
2761
+ registry.registerAction('start-workflow', AXPStartWorkflowAction);
2762
+ registry.registerAction('decide', AXPWorkflowDecideAction);
2763
+ //
2764
+ if (config?.functions) {
2765
+ for (const [key, type] of Object.entries(config.functions)) {
2766
+ registry.registerFunction(key, type);
2767
+ }
2768
+ }
2769
+ //
2770
+ if (config?.actions) {
2771
+ for (const [key, type] of Object.entries(config.actions)) {
2772
+ registry.registerAction(key, type);
2773
+ }
2774
+ }
2775
+ //
2776
+ if (config?.workflows) {
2777
+ for (const [key, type] of Object.entries(config.workflows)) {
2778
+ registry.registerWorkflow(key, type);
2779
+ }
2780
+ }
2781
+ },
2782
+ deps: [AXPWorkflowRegistryService],
2783
+ multi: true,
2784
+ },
2785
+ ...Object.values(config?.actions ?? {}),
2786
+ ...Object.values(config?.functions ?? {}),
2787
+ ],
2788
+ };
2789
+ }
2790
+ /**
2791
+ * @ignore
2792
+ */
2793
+ constructor(instances) {
2794
+ instances?.forEach((f) => {
2795
+ f();
2796
+ });
2797
+ }
2798
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowModule, deps: [{ token: 'AXPWorkflowModuleFactory', optional: true }], target: i0.ɵɵFactoryTarget.NgModule }); }
2799
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowModule }); }
2800
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowModule, providers: [
2801
+ AXPWorkflowLocalEngine,
2802
+ {
2803
+ provide: AXP_WORKFLOW_ENGINE,
2804
+ useExisting: AXPWorkflowLocalEngine,
2805
+ },
2806
+ AXPWorkflowManager,
2807
+ ] }); }
2808
+ }
2809
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowModule, decorators: [{
2810
+ type: NgModule,
2811
+ args: [{
2812
+ imports: [],
2813
+ exports: [],
2814
+ declarations: [],
2815
+ providers: [
2816
+ AXPWorkflowLocalEngine,
2817
+ {
2818
+ provide: AXP_WORKFLOW_ENGINE,
2819
+ useExisting: AXPWorkflowLocalEngine,
2820
+ },
2821
+ AXPWorkflowManager,
2822
+ ],
2823
+ }]
2824
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
2825
+ type: Optional
2826
+ }, {
2827
+ type: Inject,
2828
+ args: ['AXPWorkflowModuleFactory']
2829
+ }] }] });
2830
+
2831
+ /**
2832
+ * Generated bundle index. Do not edit.
2833
+ */
2834
+
2835
+ export { AXPActivityDefinitionService, AXPWorkflowAction, AXPWorkflowContext, AXPWorkflowDefinitionService, AXPWorkflowEngineError, AXPWorkflowError, AXPWorkflowEventService, AXPWorkflowFunction, AXPWorkflowLocalEngine, AXPWorkflowManager, AXPWorkflowModule, AXPWorkflowRegistryService, AXPWorkflowService, AXP_ACTIVITY_CATEGORY_PROVIDER, AXP_ACTIVITY_PROVIDER, AXP_WORKFLOW_CATEGORY_PROVIDER, AXP_WORKFLOW_CONTINUATION_HOOK, AXP_WORKFLOW_ENGINE, AXP_WORKFLOW_ERROR_CODES, AXP_WORKFLOW_PROVIDER, AXP_WORKFLOW_SUPPRESS_CONTINUATION_INPUT_KEY, AXP_WORKFLOW_TASK_BOARD_ACTIVITY_TYPES, ActivityExecutor, WorkflowExpressionScopeService, axpIsWorkflowInlineFrontendTask, axpIsWorkflowInteractiveFrontendTask, axpIsWorkflowTaskBoardActivityType, axpResolveWorkflowContinuationBeforeInteractive, axpShouldOfferBeforeInteractiveFlow, axpShouldOfferWorkflowContinuationAfterStep, axpShouldSkipBeforeInteractiveFromMetadata, createWorkFlowEvent, getWorkflowEngineErrorCode, getWorkflowEngineErrorInfo, isWorkflowContinuationSuppressed, ofType };
2836
+ //# sourceMappingURL=acorex-platform-framework-client-workflow.mjs.map