eva4j 1.0.17 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/DOMAIN_YAML_GUIDE.md +3 -1
- package/QUICK_REFERENCE.md +8 -4
- package/bin/eva4j.js +70 -2
- package/config/defaults.json +1 -0
- package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
- package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
- package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
- package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
- package/docs/commands/EVALUATE_SYSTEM.md +272 -8
- package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
- package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
- package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
- package/docs/commands/INDEX.md +27 -3
- package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
- package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
- package/docs/prototype/system/RISKS.md +277 -0
- package/docs/prototype/system/customers.yaml +133 -0
- package/docs/prototype/system/inventory.yaml +109 -0
- package/docs/prototype/system/notifications.yaml +131 -0
- package/docs/prototype/system/orders.yaml +241 -0
- package/docs/prototype/system/payments.yaml +256 -0
- package/docs/prototype/system/products.yaml +168 -0
- package/docs/prototype/system/system.yaml +269 -0
- package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
- package/examples/domain-read-models.yaml +2 -2
- package/examples/system/customer.yaml +89 -0
- package/examples/system/orders.yaml +119 -0
- package/examples/system/product.yaml +27 -0
- package/examples/system/system.yaml +80 -0
- package/package.json +1 -1
- package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
- package/src/agents/design-gap-analyst.agent.md +383 -0
- package/src/agents/design-reviewer-temporal.agent.md +412 -0
- package/src/agents/design-reviewer.agent.md +31 -5
- package/src/agents/implement-use-cases.prompt.md +179 -0
- package/src/agents/ux-gap-analyst.agent.md +412 -0
- package/src/commands/add-rabbitmq-client.js +261 -0
- package/src/commands/add-temporal-client.js +22 -2
- package/src/commands/build.js +267 -11
- package/src/commands/evaluate-system.js +700 -13
- package/src/commands/generate-entities.js +308 -16
- package/src/commands/generate-rabbitmq-event.js +665 -0
- package/src/commands/generate-rabbitmq-listener.js +205 -0
- package/src/commands/generate-temporal-activity.js +968 -34
- package/src/commands/generate-temporal-flow.js +95 -38
- package/src/commands/generate-temporal-system.js +708 -0
- package/src/skills/build-system-yaml/SKILL.md +222 -2
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +50 -4
- package/src/skills/build-system-yaml/references/module-spec.md +57 -8
- package/src/skills/build-temporal-system/SKILL.md +752 -0
- package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
- package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
- package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
- package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
- package/src/skills/implement-use-case/SKILL.md +350 -0
- package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
- package/src/skills/requirements-elicitation/SKILL.md +228 -0
- package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
- package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
- package/src/utils/bounded-context-diagram.js +844 -0
- package/src/utils/domain-validator.js +266 -4
- package/src/utils/naming.js +10 -0
- package/src/utils/system-validator.js +169 -11
- package/src/utils/system-yaml-parser.js +318 -0
- package/src/utils/temporal-validator.js +497 -0
- package/src/utils/yaml-to-entity.js +10 -7
- package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
- package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -2
- package/templates/aggregate/JpaEntity.java.ejs +2 -2
- package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
- package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
- package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
- package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
- package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
- package/templates/base/root/AGENTS.md.ejs +1 -1
- package/templates/crud/EndpointsController.java.ejs +1 -1
- package/templates/crud/ScaffoldCommand.java.ejs +5 -2
- package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
- package/templates/crud/ScaffoldQuery.java.ejs +5 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
- package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
- package/templates/evaluate/report.html.ejs +1447 -90
- package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
- package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
- package/templates/ports/PortAclMapper.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +7 -22
- package/templates/ports/PortFeignClient.java.ejs +4 -0
- package/templates/ports/PortResponseDto.java.ejs +1 -1
- package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
- package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
- package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
- package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
- package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
- package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
- package/templates/read-model/ReadModelJpa.java.ejs +1 -1
- package/templates/read-model/ReadModelJpaRepository.java.ejs +2 -0
- package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +9 -5
- package/templates/read-model/ReadModelSyncHandler.java.ejs +2 -0
- package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
- package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
- package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
- package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
- package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
- package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
- package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/NestedType.java.ejs +12 -0
- package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
- package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
- package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
- package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
- package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
- package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
- package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
- package/COMMAND_EVALUATION.md +0 -911
- package/test-c2010.js +0 -49
- package/test-update-compat.js +0 -109
- package/test-update-lifecycle.js +0 -121
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* system-yaml-parser.js
|
|
3
|
+
*
|
|
4
|
+
* Parses system.yaml and resolves cross-module workflow definitions by
|
|
5
|
+
* cross-referencing each workflow step's activity with its target module's
|
|
6
|
+
* domain.yaml. Produces a rich context tree for template generation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const yaml = require('js-yaml');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const { toPascalCase, toCamelCase, toScreamingSnakeCase } = require('./naming');
|
|
14
|
+
|
|
15
|
+
// ─── Java type → import mapping (shared with generate-temporal-activity) ────
|
|
16
|
+
const JAVA_TYPE_IMPORTS = {
|
|
17
|
+
BigDecimal: 'java.math.BigDecimal',
|
|
18
|
+
LocalDateTime: 'java.time.LocalDateTime',
|
|
19
|
+
LocalDate: 'java.time.LocalDate',
|
|
20
|
+
LocalTime: 'java.time.LocalTime',
|
|
21
|
+
Instant: 'java.time.Instant',
|
|
22
|
+
UUID: 'java.util.UUID',
|
|
23
|
+
List: 'java.util.List',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function resolveFieldImports(fields) {
|
|
27
|
+
const imports = new Set();
|
|
28
|
+
for (const field of fields) {
|
|
29
|
+
for (const [type, imp] of Object.entries(JAVA_TYPE_IMPORTS)) {
|
|
30
|
+
if (field.javaType && field.javaType.includes(type)) {
|
|
31
|
+
imports.add(`import ${imp};`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return Array.from(imports).sort();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function mapField(field) {
|
|
39
|
+
let javaType = field.type || 'String';
|
|
40
|
+
// Default bare collection types to generic <String>
|
|
41
|
+
if (javaType === 'List' || javaType === 'Set') {
|
|
42
|
+
javaType = javaType + '<String>';
|
|
43
|
+
}
|
|
44
|
+
return { name: field.name, javaType };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse a timeout string like "5s", "10m", "2h" into a Duration descriptor.
|
|
49
|
+
* @param {string|null} timeout
|
|
50
|
+
* @returns {{ value: number, unit: string }}
|
|
51
|
+
*/
|
|
52
|
+
function parseTimeout(timeout) {
|
|
53
|
+
if (!timeout) return { value: 30, unit: 'Seconds' };
|
|
54
|
+
const match = String(timeout).match(/^(\d+)(s|m|h)$/);
|
|
55
|
+
if (!match) return { value: 30, unit: 'Seconds' };
|
|
56
|
+
const units = { s: 'Seconds', m: 'Minutes', h: 'Hours' };
|
|
57
|
+
return { value: parseInt(match[1], 10), unit: units[match[2]] || 'Seconds' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Core parser ────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Load and parse system.yaml.
|
|
64
|
+
* @param {string} systemYamlPath - Absolute path to system.yaml
|
|
65
|
+
* @returns {object} Raw parsed YAML data
|
|
66
|
+
*/
|
|
67
|
+
async function loadSystemYaml(systemYamlPath) {
|
|
68
|
+
if (!(await fs.pathExists(systemYamlPath))) {
|
|
69
|
+
throw new Error(`system.yaml not found at ${systemYamlPath}`);
|
|
70
|
+
}
|
|
71
|
+
const content = await fs.readFile(systemYamlPath, 'utf-8');
|
|
72
|
+
return yaml.load(content);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Load the activities section from a module's domain.yaml.
|
|
77
|
+
* @param {string} domainYamlPath - Absolute path to domain.yaml
|
|
78
|
+
* @returns {Map<string, object>} Map of PascalCase activity name → raw activity definition
|
|
79
|
+
*/
|
|
80
|
+
async function loadModuleActivities(domainYamlPath) {
|
|
81
|
+
const activities = new Map();
|
|
82
|
+
if (!(await fs.pathExists(domainYamlPath))) return activities;
|
|
83
|
+
|
|
84
|
+
const content = await fs.readFile(domainYamlPath, 'utf-8');
|
|
85
|
+
const data = yaml.load(content);
|
|
86
|
+
if (!data || !Array.isArray(data.activities)) return activities;
|
|
87
|
+
|
|
88
|
+
for (const act of data.activities) {
|
|
89
|
+
activities.set(toPascalCase(act.name), act);
|
|
90
|
+
}
|
|
91
|
+
return activities;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Parse system.yaml and resolve all workflow steps against their target module
|
|
96
|
+
* domain.yaml files.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} systemDir - Directory containing system.yaml and domain yamls
|
|
99
|
+
* @returns {object} Parsed system context:
|
|
100
|
+
* {
|
|
101
|
+
* system: { name, groupId, javaVersion, springBootVersion, database },
|
|
102
|
+
* orchestration: { enabled, engine, temporal: { target, namespace } },
|
|
103
|
+
* modules: [{ name, description, exposes }],
|
|
104
|
+
* workflows: [ResolvedWorkflow],
|
|
105
|
+
* activityRegistry: Map<activityName, { module, definition }>,
|
|
106
|
+
* warnings: string[]
|
|
107
|
+
* }
|
|
108
|
+
*/
|
|
109
|
+
async function parseSystemYaml(systemDir) {
|
|
110
|
+
const systemYamlPath = path.join(systemDir, 'system.yaml');
|
|
111
|
+
const data = await loadSystemYaml(systemYamlPath);
|
|
112
|
+
|
|
113
|
+
const warnings = [];
|
|
114
|
+
|
|
115
|
+
// ── 1. Basic sections ──────────────────────────────────────────────────
|
|
116
|
+
const system = data.system || {};
|
|
117
|
+
const orchestration = data.orchestration || {};
|
|
118
|
+
const modules = Array.isArray(data.modules) ? data.modules : [];
|
|
119
|
+
const rawWorkflows = Array.isArray(data.workflows) ? data.workflows : [];
|
|
120
|
+
|
|
121
|
+
// ── 2. Load all module activities ──────────────────────────────────────
|
|
122
|
+
// Map: moduleName (camelCase) → Map<ActivityPascalCase, rawDefinition>
|
|
123
|
+
const moduleActivitiesMap = new Map();
|
|
124
|
+
|
|
125
|
+
for (const mod of modules) {
|
|
126
|
+
const modCamel = toCamelCase(mod.name);
|
|
127
|
+
// Domain YAML files can be in the system dir (prototype layout)
|
|
128
|
+
// or in the project's module directory. Try both.
|
|
129
|
+
const candidates = [
|
|
130
|
+
path.join(systemDir, `${mod.name}.yaml`),
|
|
131
|
+
path.join(systemDir, `${modCamel}.yaml`),
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
let activities = new Map();
|
|
135
|
+
for (const candidate of candidates) {
|
|
136
|
+
activities = await loadModuleActivities(candidate);
|
|
137
|
+
if (activities.size > 0) break;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
moduleActivitiesMap.set(modCamel, activities);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── 3. Build activity registry (flat, for easy lookup) ─────────────────
|
|
144
|
+
// Map: activityPascalCase → { module (camelCase), definition, inputFields, outputFields }
|
|
145
|
+
const activityRegistry = new Map();
|
|
146
|
+
|
|
147
|
+
for (const [modName, activities] of moduleActivitiesMap) {
|
|
148
|
+
for (const [actName, actDef] of activities) {
|
|
149
|
+
activityRegistry.set(actName, {
|
|
150
|
+
module: modName,
|
|
151
|
+
definition: actDef,
|
|
152
|
+
inputFields: Array.isArray(actDef.input) ? actDef.input.map(mapField) : [],
|
|
153
|
+
outputFields: Array.isArray(actDef.output) ? actDef.output.map(mapField) : [],
|
|
154
|
+
type: (actDef.type || 'light').toLowerCase(),
|
|
155
|
+
compensation: actDef.compensation ? toPascalCase(actDef.compensation) : null,
|
|
156
|
+
timeout: actDef.timeout || null,
|
|
157
|
+
description: actDef.description || '',
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── 4. Resolve workflows ───────────────────────────────────────────────
|
|
163
|
+
const workflows = rawWorkflows.map((wf) =>
|
|
164
|
+
resolveWorkflow(wf, activityRegistry, warnings)
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
system,
|
|
169
|
+
orchestration,
|
|
170
|
+
modules,
|
|
171
|
+
workflows,
|
|
172
|
+
activityRegistry,
|
|
173
|
+
warnings,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a single workflow definition: enrich each step with activity
|
|
179
|
+
* definitions, detect parallel groups, and derive the host module.
|
|
180
|
+
*
|
|
181
|
+
* @param {object} wf - Raw workflow from system.yaml
|
|
182
|
+
* @param {Map} activityRegistry - Global activity lookup
|
|
183
|
+
* @param {string[]} warnings - Mutable array for warnings
|
|
184
|
+
* @returns {object} Resolved workflow
|
|
185
|
+
*/
|
|
186
|
+
function resolveWorkflow(wf, activityRegistry, warnings) {
|
|
187
|
+
const name = wf.name;
|
|
188
|
+
const namePascal = toPascalCase(name);
|
|
189
|
+
const trigger = wf.trigger || {};
|
|
190
|
+
const hostModule = toCamelCase(trigger.module || '');
|
|
191
|
+
const taskQueue = wf.taskQueue || `${toScreamingSnakeCase(hostModule)}_WORKFLOW_QUEUE`;
|
|
192
|
+
const isSaga = wf.saga === true;
|
|
193
|
+
|
|
194
|
+
const steps = [];
|
|
195
|
+
const rawSteps = Array.isArray(wf.steps) ? wf.steps : [];
|
|
196
|
+
|
|
197
|
+
for (let i = 0; i < rawSteps.length; i++) {
|
|
198
|
+
const rawStep = rawSteps[i];
|
|
199
|
+
const activityName = toPascalCase(rawStep.activity);
|
|
200
|
+
const targetModule = toCamelCase(rawStep.target || hostModule);
|
|
201
|
+
const isLocal = targetModule === hostModule;
|
|
202
|
+
const isAsync = rawStep.type === 'async';
|
|
203
|
+
const isParallel = rawStep.parallel === true;
|
|
204
|
+
const isOptional = rawStep.optional === true;
|
|
205
|
+
|
|
206
|
+
// Resolve from registry
|
|
207
|
+
const registered = activityRegistry.get(activityName);
|
|
208
|
+
if (!registered) {
|
|
209
|
+
warnings.push(
|
|
210
|
+
`Workflow '${name}' step ${i + 1}: activity '${activityName}' not found in any module's domain.yaml`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const inputFields = registered ? registered.inputFields : (rawStep.input || []).map((n) => ({ name: n, javaType: 'String' }));
|
|
215
|
+
const outputFields = registered ? registered.outputFields : (rawStep.output || []).map((n) => ({ name: n, javaType: 'String' }));
|
|
216
|
+
const actType = registered ? registered.type : (rawStep.type === 'async' ? 'light' : 'light');
|
|
217
|
+
const timeout = rawStep.timeout || (registered ? registered.timeout : null);
|
|
218
|
+
|
|
219
|
+
// Resolve compensation
|
|
220
|
+
let compensation = null;
|
|
221
|
+
if (rawStep.compensation) {
|
|
222
|
+
const compName = toPascalCase(rawStep.compensation);
|
|
223
|
+
const compRegistered = activityRegistry.get(compName);
|
|
224
|
+
compensation = {
|
|
225
|
+
name: compName,
|
|
226
|
+
module: compRegistered ? compRegistered.module : targetModule,
|
|
227
|
+
inputFields: compRegistered ? compRegistered.inputFields : [],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Queue for this step's target module
|
|
232
|
+
const stepQueue = actType === 'heavy'
|
|
233
|
+
? `${toScreamingSnakeCase(targetModule)}_HEAVY_TASK_QUEUE`
|
|
234
|
+
: `${toScreamingSnakeCase(targetModule)}_LIGHT_TASK_QUEUE`;
|
|
235
|
+
|
|
236
|
+
// Raw field names from system.yaml (wiring-level names)
|
|
237
|
+
const rawInputNames = Array.isArray(rawStep.input) ? rawStep.input : [];
|
|
238
|
+
const rawOutputNames = Array.isArray(rawStep.output) ? rawStep.output : [];
|
|
239
|
+
|
|
240
|
+
steps.push({
|
|
241
|
+
index: i,
|
|
242
|
+
activityName,
|
|
243
|
+
activityCamel: activityName.charAt(0).toLowerCase() + activityName.slice(1),
|
|
244
|
+
targetModule,
|
|
245
|
+
targetModulePascal: toPascalCase(targetModule),
|
|
246
|
+
targetModuleScreamingSnake: toScreamingSnakeCase(targetModule),
|
|
247
|
+
stepQueue,
|
|
248
|
+
isLocal,
|
|
249
|
+
isAsync,
|
|
250
|
+
isParallel,
|
|
251
|
+
isOptional,
|
|
252
|
+
actType,
|
|
253
|
+
timeout,
|
|
254
|
+
inputFields,
|
|
255
|
+
outputFields,
|
|
256
|
+
rawInputNames,
|
|
257
|
+
rawOutputNames,
|
|
258
|
+
hasInput: inputFields.length > 0,
|
|
259
|
+
hasOutput: outputFields.length > 0,
|
|
260
|
+
compensation,
|
|
261
|
+
inputImports: resolveFieldImports(inputFields),
|
|
262
|
+
outputImports: resolveFieldImports(outputFields),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ── Detect parallel groups ─────────────────────────────────────────────
|
|
267
|
+
// Consecutive steps with parallel: true form a group
|
|
268
|
+
const parallelGroups = [];
|
|
269
|
+
let currentGroup = null;
|
|
270
|
+
for (const step of steps) {
|
|
271
|
+
if (step.isParallel) {
|
|
272
|
+
if (!currentGroup) {
|
|
273
|
+
currentGroup = { startIndex: step.index, steps: [] };
|
|
274
|
+
}
|
|
275
|
+
currentGroup.steps.push(step);
|
|
276
|
+
} else {
|
|
277
|
+
if (currentGroup) {
|
|
278
|
+
parallelGroups.push(currentGroup);
|
|
279
|
+
currentGroup = null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (currentGroup) parallelGroups.push(currentGroup);
|
|
284
|
+
|
|
285
|
+
// ── Collect unique target modules (for imports) ────────────────────────
|
|
286
|
+
const targetModules = [...new Set(steps.map((s) => s.targetModule))];
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
name,
|
|
290
|
+
namePascal,
|
|
291
|
+
nameScreamingSnake: toScreamingSnakeCase(name.replace(/Workflow$/, '')),
|
|
292
|
+
trigger,
|
|
293
|
+
hostModule,
|
|
294
|
+
hostModulePascal: toPascalCase(hostModule),
|
|
295
|
+
hostModuleScreamingSnake: toScreamingSnakeCase(hostModule),
|
|
296
|
+
taskQueue,
|
|
297
|
+
isSaga,
|
|
298
|
+
steps,
|
|
299
|
+
parallelGroups,
|
|
300
|
+
targetModules,
|
|
301
|
+
hasParallelSteps: parallelGroups.length > 0,
|
|
302
|
+
hasCompensations: steps.some((s) => s.compensation !== null),
|
|
303
|
+
hasAsyncSteps: steps.some((s) => s.isAsync),
|
|
304
|
+
hasOptionalSteps: steps.some((s) => s.isOptional),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ─── Exports ────────────────────────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
module.exports = {
|
|
311
|
+
parseSystemYaml,
|
|
312
|
+
loadSystemYaml,
|
|
313
|
+
loadModuleActivities,
|
|
314
|
+
resolveWorkflow,
|
|
315
|
+
resolveFieldImports,
|
|
316
|
+
mapField,
|
|
317
|
+
parseTimeout,
|
|
318
|
+
};
|