eva4j 1.0.13 → 1.0.15
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 +314 -10
- package/COMMAND_EVALUATION.md +15 -16
- package/DOMAIN_YAML_GUIDE.md +576 -10
- package/FUTURE_FEATURES.md +1627 -1168
- package/README.md +318 -13
- package/bin/eva4j.js +34 -0
- package/config/defaults.json +1 -0
- package/design-system.md +797 -0
- package/docs/commands/EVALUATE_SYSTEM.md +994 -0
- package/docs/commands/GENERATE_ENTITIES.md +795 -6
- package/docs/commands/INDEX.md +10 -1
- package/examples/domain-endpoints-relations.yaml +353 -0
- package/examples/domain-endpoints-versioned.yaml +144 -0
- package/examples/domain-endpoints.yaml +135 -0
- package/examples/domain-events.yaml +166 -20
- package/examples/domain-listeners.yaml +212 -0
- package/examples/domain-one-to-many.yaml +1 -0
- package/examples/domain-one-to-one.yaml +1 -0
- package/examples/domain-ports.yaml +414 -0
- package/examples/domain-soft-delete.yaml +47 -44
- package/examples/system/notification.yaml +147 -0
- package/examples/system/product.yaml +185 -0
- package/examples/system/system.yaml +112 -0
- package/examples/system-report.html +971 -0
- package/examples/system.yaml +332 -0
- package/package.json +2 -1
- package/src/commands/build.js +714 -0
- package/src/commands/create.js +7 -3
- package/src/commands/detach.js +1 -0
- package/src/commands/evaluate-system.js +610 -0
- package/src/commands/generate-entities.js +1331 -49
- package/src/commands/generate-http-exchange.js +2 -0
- package/src/commands/generate-kafka-event.js +98 -11
- package/src/generators/base-generator.js +8 -1
- package/src/generators/postman-generator.js +188 -0
- package/src/generators/shared-generator.js +10 -0
- package/src/utils/config-manager.js +54 -0
- package/src/utils/context-builder.js +1 -0
- package/src/utils/domain-diagram.js +192 -0
- package/src/utils/domain-validator.js +970 -0
- package/src/utils/fake-data.js +376 -0
- package/src/utils/naming.js +3 -2
- package/src/utils/system-validator.js +434 -0
- package/src/utils/yaml-to-entity.js +302 -8
- package/templates/aggregate/AggregateMapper.java.ejs +3 -2
- package/templates/aggregate/AggregateRepository.java.ejs +8 -2
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +13 -3
- package/templates/aggregate/AggregateRoot.java.ejs +60 -2
- package/templates/aggregate/DomainEventHandler.java.ejs +27 -20
- package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
- package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
- package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
- package/templates/aggregate/JpaRepository.java.ejs +5 -0
- package/templates/base/gradle/build.gradle.ejs +3 -2
- package/templates/base/root/AGENTS.md.ejs +306 -45
- package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1663 -0
- package/templates/base/root/skill-build-system-yaml.ejs +1446 -0
- package/templates/base/root/system.yaml.ejs +97 -0
- package/templates/crud/ApplicationMapper.java.ejs +4 -0
- package/templates/crud/Controller.java.ejs +4 -4
- package/templates/crud/CreateCommand.java.ejs +4 -0
- package/templates/crud/CreateItemDto.java.ejs +4 -0
- package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
- package/templates/crud/DeleteCommandHandler.java.ejs +10 -2
- package/templates/crud/EndpointsController.java.ejs +178 -0
- package/templates/crud/FindByQuery.java.ejs +17 -0
- package/templates/crud/FindByQueryHandler.java.ejs +57 -0
- package/templates/crud/ListQuery.java.ejs +1 -1
- package/templates/crud/ListQueryHandler.java.ejs +8 -8
- package/templates/crud/ScaffoldCommand.java.ejs +12 -0
- package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
- package/templates/crud/ScaffoldQuery.java.ejs +13 -0
- package/templates/crud/ScaffoldQueryHandler.java.ejs +41 -0
- package/templates/crud/SubEntityAddCommand.java.ejs +21 -0
- package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
- package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
- package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
- package/templates/crud/TransitionCommand.java.ejs +9 -0
- package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
- package/templates/crud/UpdateCommand.java.ejs +4 -0
- package/templates/evaluate/report.html.ejs +1363 -0
- package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
- package/templates/kafka-event/Event.java.ejs +16 -0
- package/templates/kafka-listener/KafkaController.java.ejs +1 -1
- package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
- package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
- package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
- package/templates/kafka-listener/ListenerCommandHandler.java.ejs +23 -0
- package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
- package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
- package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
- package/templates/mock/MockEvent.java.ejs +10 -0
- package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
- package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
- package/templates/mock/SpringEventListener.java.ejs +61 -0
- package/templates/ports/PortDomainModel.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +67 -0
- package/templates/ports/PortFeignClient.java.ejs +45 -0
- package/templates/ports/PortFeignConfig.java.ejs +24 -0
- package/templates/ports/PortInterface.java.ejs +45 -0
- package/templates/ports/PortNestedType.java.ejs +28 -0
- package/templates/ports/PortRequestDto.java.ejs +30 -0
- package/templates/ports/PortResponseDto.java.ejs +28 -0
- package/templates/postman/Collection.json.ejs +1 -1
- package/templates/postman/UnifiedCollection.json.ejs +185 -0
- package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { faker } = require('@faker-js/faker');
|
|
4
|
+
|
|
5
|
+
// Set locale to English (US)
|
|
6
|
+
faker.locale = 'en_US';
|
|
7
|
+
|
|
8
|
+
// Deterministic seed for reproducible output
|
|
9
|
+
const DEFAULT_SEED = 42;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initialize faker with a seed for reproducibility.
|
|
13
|
+
* @param {number} seed
|
|
14
|
+
*/
|
|
15
|
+
function initSeed(seed = DEFAULT_SEED) {
|
|
16
|
+
faker.seed(seed);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ── Field-name heuristics (order matters — first match wins) ────────────────
|
|
20
|
+
|
|
21
|
+
const NAME_HEURISTICS = [
|
|
22
|
+
// Identity / person
|
|
23
|
+
{ pattern: /^(first[-_]?name)$/i, gen: () => faker.person.firstName() },
|
|
24
|
+
{ pattern: /^(last[-_]?name|surname)$/i, gen: () => faker.person.lastName() },
|
|
25
|
+
{ pattern: /^(full[-_]?name|customer[-_]?name|user[-_]?name|recipient[-_]?name|author[-_]?name|owner[-_]?name)$/i, gen: () => faker.person.fullName() },
|
|
26
|
+
{ pattern: /^(username|login|nick[-_]?name)$/i, gen: () => faker.internet.username() },
|
|
27
|
+
{ pattern: /^(name)$/i, gen: () => faker.commerce.productName() },
|
|
28
|
+
|
|
29
|
+
// Contact
|
|
30
|
+
{ pattern: /email/i, gen: () => faker.internet.email() },
|
|
31
|
+
{ pattern: /^(phone|telephone|mobile|cell|fax)/i, gen: () => faker.phone.number({ style: 'international' }) },
|
|
32
|
+
|
|
33
|
+
// Location / address
|
|
34
|
+
{ pattern: /^(street|street[-_]?address)$/i, gen: () => faker.location.streetAddress() },
|
|
35
|
+
{ pattern: /^(city|town)$/i, gen: () => faker.location.city() },
|
|
36
|
+
{ pattern: /^(state|province|region)$/i, gen: () => faker.location.state() },
|
|
37
|
+
{ pattern: /^(zip[-_]?code|postal[-_]?code)$/i, gen: () => faker.location.zipCode() },
|
|
38
|
+
{ pattern: /^(country)$/i, gen: () => faker.location.country() },
|
|
39
|
+
{ pattern: /^(address)$/i, gen: () => faker.location.streetAddress(true) },
|
|
40
|
+
{ pattern: /^(latitude|lat)$/i, gen: () => faker.location.latitude() },
|
|
41
|
+
{ pattern: /^(longitude|lng|lon)$/i, gen: () => faker.location.longitude() },
|
|
42
|
+
|
|
43
|
+
// Financial / commerce
|
|
44
|
+
{ pattern: /^(price|amount|total|subtotal|tax|discount|balance|cost|fee|salary|income)$/i, gen: () => faker.commerce.price({ min: 5, max: 500 }) },
|
|
45
|
+
{ pattern: /^(currency)$/i, gen: () => faker.finance.currencyCode() },
|
|
46
|
+
{ pattern: /^(iban)$/i, gen: () => faker.finance.iban() },
|
|
47
|
+
{ pattern: /^(credit[-_]?card|card[-_]?number)$/i, gen: () => faker.finance.creditCardNumber() },
|
|
48
|
+
|
|
49
|
+
// Text / content
|
|
50
|
+
{ pattern: /^(title|subject|headline)$/i, gen: () => faker.commerce.productName() + ' ' + faker.word.noun() },
|
|
51
|
+
{ pattern: /^(description|summary|bio|about|notes|comment|message|body|content)$/i, gen: () => faker.commerce.productDescription() },
|
|
52
|
+
{ pattern: /^(slug)$/i, gen: () => faker.helpers.slugify(faker.commerce.productName()).toLowerCase() },
|
|
53
|
+
|
|
54
|
+
// Web
|
|
55
|
+
{ pattern: /^(url|website|homepage|link|image[-_]?url|avatar[-_]?url|photo[-_]?url|logo[-_]?url)$/i, gen: () => faker.internet.url() },
|
|
56
|
+
{ pattern: /^(ip|ip[-_]?address)$/i, gen: () => faker.internet.ip() },
|
|
57
|
+
{ pattern: /^(domain|host)$/i, gen: () => faker.internet.domainName() },
|
|
58
|
+
|
|
59
|
+
// Identifiers / codes
|
|
60
|
+
{ pattern: /^(order[-_]?number|invoice[-_]?number|reference[-_]?number|tracking[-_]?number)$/i, gen: () => `ORD-${faker.string.alphanumeric(6).toUpperCase()}` },
|
|
61
|
+
{ pattern: /^(sku|product[-_]?code|item[-_]?code|barcode)$/i, gen: () => faker.string.alphanumeric(8).toUpperCase() },
|
|
62
|
+
{ pattern: /^(code|token|key)$/i, gen: () => faker.string.alphanumeric(10).toUpperCase() },
|
|
63
|
+
|
|
64
|
+
// Company
|
|
65
|
+
{ pattern: /^(company|company[-_]?name|organization|brand)$/i, gen: () => faker.company.name() },
|
|
66
|
+
{ pattern: /^(department)$/i, gen: () => faker.commerce.department() },
|
|
67
|
+
{ pattern: /^(job[-_]?title|position|role)$/i, gen: () => faker.person.jobTitle() },
|
|
68
|
+
|
|
69
|
+
// Dates (as strings for JSON)
|
|
70
|
+
{ pattern: /^(birth[-_]?date|date[-_]?of[-_]?birth|dob)$/i, gen: () => faker.date.birthdate({ min: 18, max: 65, mode: 'age' }).toISOString().split('T')[0] },
|
|
71
|
+
|
|
72
|
+
// Quantity / counts
|
|
73
|
+
{ pattern: /^(quantity|qty|count|stock|units|capacity|size|weight|height|width|length|duration|age|rating|score|priority|order|rank|level|version)$/i, gen: () => faker.number.int({ min: 1, max: 100 }) },
|
|
74
|
+
|
|
75
|
+
// Boolean semantic
|
|
76
|
+
{ pattern: /^(is[-_]?active|active|enabled|verified|published|available|visible|approved|completed|deleted|archived)$/i, gen: () => faker.datatype.boolean() },
|
|
77
|
+
|
|
78
|
+
// Color
|
|
79
|
+
{ pattern: /^(color|colour)$/i, gen: () => faker.color.human() },
|
|
80
|
+
|
|
81
|
+
// Catch-all for *Id fields that reference another aggregate
|
|
82
|
+
{ pattern: /Id$/, gen: () => faker.string.uuid() },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// ── Type-based fallbacks ────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
const TYPE_GENERATORS = {
|
|
88
|
+
'String': () => faker.word.words(3),
|
|
89
|
+
'Integer': () => faker.number.int({ min: 1, max: 1000 }),
|
|
90
|
+
'int': () => faker.number.int({ min: 1, max: 1000 }),
|
|
91
|
+
'Long': () => faker.number.int({ min: 10000, max: 99999 }),
|
|
92
|
+
'long': () => faker.number.int({ min: 10000, max: 99999 }),
|
|
93
|
+
'Double': () => parseFloat(faker.finance.amount({ min: 1, max: 999, dec: 2 })),
|
|
94
|
+
'double': () => parseFloat(faker.finance.amount({ min: 1, max: 999, dec: 2 })),
|
|
95
|
+
'Float': () => parseFloat(faker.finance.amount({ min: 1, max: 999, dec: 2 })),
|
|
96
|
+
'float': () => parseFloat(faker.finance.amount({ min: 1, max: 999, dec: 2 })),
|
|
97
|
+
'BigDecimal': () => faker.commerce.price({ min: 5, max: 500 }),
|
|
98
|
+
'Boolean': () => faker.datatype.boolean(),
|
|
99
|
+
'boolean': () => faker.datatype.boolean(),
|
|
100
|
+
'LocalDate': () => faker.date.recent({ days: 30 }).toISOString().split('T')[0],
|
|
101
|
+
'LocalDateTime': () => faker.date.recent({ days: 30 }).toISOString().replace('Z', ''),
|
|
102
|
+
'LocalTime': () => faker.date.recent().toISOString().split('T')[1].substring(0, 8),
|
|
103
|
+
'Instant': () => faker.date.recent({ days: 30 }).toISOString(),
|
|
104
|
+
'UUID': () => faker.string.uuid(),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// ── Validation-aware overrides ──────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
function applyValidationOverrides(field, baseValue) {
|
|
110
|
+
const annotations = field.validationAnnotations || [];
|
|
111
|
+
if (annotations.length === 0) return baseValue;
|
|
112
|
+
|
|
113
|
+
let value = baseValue;
|
|
114
|
+
|
|
115
|
+
for (const ann of annotations) {
|
|
116
|
+
// @Email — always override to guarantee valid email format
|
|
117
|
+
if (/@Email/i.test(ann)) return faker.internet.email();
|
|
118
|
+
|
|
119
|
+
// @Future / @Past — always override to guarantee temporal constraint
|
|
120
|
+
if (/@Future/i.test(ann)) return faker.date.future({ years: 1 }).toISOString().replace('Z', '');
|
|
121
|
+
if (/@Past/i.test(ann)) return faker.date.past({ years: 2 }).toISOString().replace('Z', '');
|
|
122
|
+
|
|
123
|
+
// @Pattern — always override (heuristic can't know regex)
|
|
124
|
+
const patternMatch = ann.match(/@Pattern\s*\(.*?regexp\s*=\s*"([^"]+)"/);
|
|
125
|
+
if (patternMatch) {
|
|
126
|
+
const re = patternMatch[1];
|
|
127
|
+
if (/^\^\[0-9\]|^\^\\d/.test(re)) return faker.string.numeric(6);
|
|
128
|
+
if (/^\^\[A-Z\]/.test(re)) return faker.string.alpha({ length: 8, casing: 'upper' });
|
|
129
|
+
return faker.string.alphanumeric(8);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// @NotBlank / @NotNull / @NotEmpty — ensure non-empty; keep base if already non-empty
|
|
133
|
+
if (/@NotBlank|@NotEmpty|@NotNull/i.test(ann)) {
|
|
134
|
+
if (value === null || value === undefined || value === '') {
|
|
135
|
+
value = `example_${field.name}`;
|
|
136
|
+
}
|
|
137
|
+
continue; // don't return — check other annotations too
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// @Positive / @PositiveOrZero / @Negative — constrain if base is numeric, else override
|
|
141
|
+
if (/@PositiveOrZero/i.test(ann)) {
|
|
142
|
+
if (typeof value === 'number') { value = Math.abs(value); }
|
|
143
|
+
else { value = faker.number.int({ min: 0, max: 500 }); }
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (/@Positive/i.test(ann)) {
|
|
147
|
+
if (typeof value === 'number') { value = Math.max(1, Math.abs(value)); }
|
|
148
|
+
else if (typeof value === 'string' && !isNaN(parseFloat(value))) {
|
|
149
|
+
const n = Math.max(1, Math.abs(parseFloat(value)));
|
|
150
|
+
value = String(n);
|
|
151
|
+
} else { value = faker.number.int({ min: 1, max: 500 }); }
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (/@Negative/i.test(ann)) {
|
|
155
|
+
if (typeof value === 'number') { value = -Math.abs(value || 1); }
|
|
156
|
+
else { value = faker.number.int({ min: -500, max: -1 }); }
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// @Min(n) — if base is numeric and below min, adjust; otherwise generate
|
|
161
|
+
const minMatch = ann.match(/@Min\s*\(\s*(?:value\s*=\s*)?(\d+)/);
|
|
162
|
+
if (minMatch) {
|
|
163
|
+
const min = parseInt(minMatch[1], 10);
|
|
164
|
+
if (typeof value === 'number' && value < min) { value = faker.number.int({ min, max: min + 100 }); }
|
|
165
|
+
else if (typeof value !== 'number') { value = faker.number.int({ min, max: min + 100 }); }
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// @Max(n) — if base is numeric and above max, adjust; otherwise generate
|
|
170
|
+
const maxMatch = ann.match(/@Max\s*\(\s*(?:value\s*=\s*)?(\d+)/);
|
|
171
|
+
if (maxMatch) {
|
|
172
|
+
const max = parseInt(maxMatch[1], 10);
|
|
173
|
+
if (typeof value === 'number' && value > max) { value = faker.number.int({ min: 0, max }); }
|
|
174
|
+
else if (typeof value !== 'number') { value = faker.number.int({ min: 0, max }); }
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// @Size(min, max) — constrain string length; keep base if fits, else truncate/pad
|
|
179
|
+
const sizeMatch = ann.match(/@Size\s*\(.*?min\s*=\s*(\d+)(?:.*?max\s*=\s*(\d+))?/);
|
|
180
|
+
if (sizeMatch) {
|
|
181
|
+
const min = parseInt(sizeMatch[1], 10);
|
|
182
|
+
const max = sizeMatch[2] ? parseInt(sizeMatch[2], 10) : 255;
|
|
183
|
+
if (typeof value === 'string') {
|
|
184
|
+
if (value.length < min) {
|
|
185
|
+
// Pad to min length
|
|
186
|
+
value = value + faker.string.alpha(min - value.length);
|
|
187
|
+
} else if (value.length > max) {
|
|
188
|
+
// Truncate to max length
|
|
189
|
+
value = value.substring(0, max);
|
|
190
|
+
}
|
|
191
|
+
// else: value length is within range — keep it
|
|
192
|
+
} else {
|
|
193
|
+
value = faker.string.alpha({ length: { min: Math.max(min, 1), max: Math.min(max, 50) } });
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// @Digits(integer, fraction) — format as decimal with given precision
|
|
199
|
+
const digitsMatch = ann.match(/@Digits\s*\(\s*integer\s*=\s*(\d+)(?:.*?fraction\s*=\s*(\d+))?/);
|
|
200
|
+
if (digitsMatch) {
|
|
201
|
+
const intPart = parseInt(digitsMatch[1], 10);
|
|
202
|
+
const fracPart = digitsMatch[2] ? parseInt(digitsMatch[2], 10) : 0;
|
|
203
|
+
const maxInt = Math.pow(10, Math.min(intPart, 6)) - 1;
|
|
204
|
+
const num = faker.number.float({ min: 1, max: maxInt, fractionDigits: fracPart });
|
|
205
|
+
value = fracPart > 0 ? num.toFixed(fracPart) : String(Math.floor(num));
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Core generation ─────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Generate a realistic fake value for a domain field.
|
|
217
|
+
*
|
|
218
|
+
* @param {Object} field - Parsed field object from yaml-to-entity
|
|
219
|
+
* @param {Array} allEnums - All enum definitions available in the module
|
|
220
|
+
* @param {Array} valueObjects - All value object definitions in the aggregate
|
|
221
|
+
* @param {number} depth - Current recursion depth (for nested VOs)
|
|
222
|
+
* @returns {*} A JSON-compatible fake value
|
|
223
|
+
*/
|
|
224
|
+
function generateFakeValue(field, allEnums = [], valueObjects = [], depth = 0) {
|
|
225
|
+
const MAX_DEPTH = 3;
|
|
226
|
+
if (depth >= MAX_DEPTH) return null;
|
|
227
|
+
|
|
228
|
+
// 1. Enum → random pick from values
|
|
229
|
+
if (field.isEnum) {
|
|
230
|
+
const enumDef = allEnums.find(e => e.name === field.javaType);
|
|
231
|
+
if (enumDef && enumDef.values && enumDef.values.length > 0) {
|
|
232
|
+
return faker.helpers.arrayElement(enumDef.values);
|
|
233
|
+
}
|
|
234
|
+
return 'EXAMPLE_VALUE';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 2. Value Objects → recursively generate nested fields
|
|
238
|
+
if (field.isValueObject) {
|
|
239
|
+
const voDef = valueObjects.find(vo => vo.name === field.javaType);
|
|
240
|
+
if (voDef && voDef.fields) {
|
|
241
|
+
const obj = {};
|
|
242
|
+
voDef.fields.forEach(voField => {
|
|
243
|
+
obj[voField.name] = generateFakeValue(voField, allEnums, valueObjects, depth + 1);
|
|
244
|
+
});
|
|
245
|
+
return obj;
|
|
246
|
+
}
|
|
247
|
+
// Fallback: heuristic by VO name
|
|
248
|
+
return generateValueObjectFallback(field.javaType, field.name);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 3. Collections
|
|
252
|
+
if (field.isCollection) {
|
|
253
|
+
return [faker.word.noun(), faker.word.noun()];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 4. Field-name heuristic
|
|
257
|
+
for (const rule of NAME_HEURISTICS) {
|
|
258
|
+
if (rule.pattern.test(field.name)) {
|
|
259
|
+
const val = rule.gen();
|
|
260
|
+
return applyValidationOverrides(field, val);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 5. Type-based fallback
|
|
265
|
+
const typeGen = TYPE_GENERATORS[field.javaType];
|
|
266
|
+
if (typeGen) {
|
|
267
|
+
return applyValidationOverrides(field, typeGen());
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 6. Absolute fallback
|
|
271
|
+
return applyValidationOverrides(field, `example_${field.name}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Fallback for value objects when no definition is found.
|
|
276
|
+
*/
|
|
277
|
+
function generateValueObjectFallback(typeName, fieldName) {
|
|
278
|
+
const name = typeName.toLowerCase();
|
|
279
|
+
if (name.includes('money') || name.includes('price') || name.includes('amount')) {
|
|
280
|
+
return { amount: faker.commerce.price({ min: 5, max: 500 }), currency: faker.finance.currencyCode() };
|
|
281
|
+
}
|
|
282
|
+
if (name.includes('address')) {
|
|
283
|
+
return { street: faker.location.streetAddress(), city: faker.location.city(), state: faker.location.state(), zipCode: faker.location.zipCode(), country: faker.location.country() };
|
|
284
|
+
}
|
|
285
|
+
if (name.includes('email')) {
|
|
286
|
+
return { value: faker.internet.email() };
|
|
287
|
+
}
|
|
288
|
+
if (name.includes('phone')) {
|
|
289
|
+
return { value: faker.phone.number({ style: 'international' }) };
|
|
290
|
+
}
|
|
291
|
+
return { value: faker.word.words(2) };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Generate a full request body for an aggregate's command fields.
|
|
296
|
+
*
|
|
297
|
+
* @param {Array} commandFields - Writable fields (exclude id, audit, readOnly)
|
|
298
|
+
* @param {Array} oneToManyRelationships - Enriched relationships from enrichRelationshipsRecursively
|
|
299
|
+
* @param {Array} allEnums - Module enums
|
|
300
|
+
* @param {Array} valueObjects - Aggregate value objects
|
|
301
|
+
* @returns {Object} A JSON-serialisable body object
|
|
302
|
+
*/
|
|
303
|
+
function generateFakeBody(commandFields, oneToManyRelationships = [], allEnums = [], valueObjects = []) {
|
|
304
|
+
const body = {};
|
|
305
|
+
|
|
306
|
+
commandFields.forEach(field => {
|
|
307
|
+
body[field.name] = generateFakeValue(field, allEnums, valueObjects);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (oneToManyRelationships && oneToManyRelationships.length > 0) {
|
|
311
|
+
oneToManyRelationships.forEach(rel => {
|
|
312
|
+
const obj = generateFakeNestedObject(rel, allEnums, valueObjects, 0);
|
|
313
|
+
if (obj) {
|
|
314
|
+
body[rel.fieldName] = [obj];
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return body;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Generate a fake object for a nested relationship.
|
|
324
|
+
*/
|
|
325
|
+
function generateFakeNestedObject(rel, allEnums, valueObjects, depth) {
|
|
326
|
+
const MAX_DEPTH = 3;
|
|
327
|
+
if (depth >= MAX_DEPTH) return null;
|
|
328
|
+
|
|
329
|
+
const obj = {};
|
|
330
|
+
|
|
331
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
332
|
+
rel.fields.filter(f => !f.readOnly && f.name !== 'id').forEach(field => {
|
|
333
|
+
obj[field.name] = generateFakeValue(field, allEnums, valueObjects, depth + 1);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Recurse into nested OneToMany
|
|
338
|
+
if (rel.hasNestedRelationships && rel.nestedRelationships) {
|
|
339
|
+
rel.nestedRelationships.forEach(nestedRel => {
|
|
340
|
+
const nestedObj = generateFakeNestedObject(nestedRel, allEnums, valueObjects, depth + 1);
|
|
341
|
+
if (nestedObj) {
|
|
342
|
+
obj[nestedRel.fieldName] = [nestedObj];
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Nested OneToOne
|
|
348
|
+
if (rel.nestedOneToOneRelationships && rel.nestedOneToOneRelationships.length > 0) {
|
|
349
|
+
rel.nestedOneToOneRelationships.forEach(otoRel => {
|
|
350
|
+
const otoObj = {};
|
|
351
|
+
(otoRel.fields || []).filter(f => !f.readOnly && f.name !== 'id').forEach(field => {
|
|
352
|
+
otoObj[field.name] = generateFakeValue(field, allEnums, valueObjects, depth + 1);
|
|
353
|
+
});
|
|
354
|
+
obj[otoRel.fieldName] = otoObj;
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return obj;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Generate example ID based on the id-field type.
|
|
363
|
+
*/
|
|
364
|
+
function generateFakeId(idType) {
|
|
365
|
+
if (idType === 'UUID') return faker.string.uuid();
|
|
366
|
+
if (idType === 'Long' || idType === 'Integer') return String(faker.number.int({ min: 1, max: 999 }));
|
|
367
|
+
return faker.string.uuid();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
module.exports = {
|
|
371
|
+
initSeed,
|
|
372
|
+
generateFakeValue,
|
|
373
|
+
generateFakeBody,
|
|
374
|
+
generateFakeNestedObject,
|
|
375
|
+
generateFakeId,
|
|
376
|
+
};
|
package/src/utils/naming.js
CHANGED
|
@@ -84,12 +84,13 @@ function getBaseEntity(hasSoftDelete, hasAudit) {
|
|
|
84
84
|
/**
|
|
85
85
|
* Convert artifact ID to valid Java package name
|
|
86
86
|
* @param {string} artifactId - Artifact ID (e.g., my-project)
|
|
87
|
-
* @returns {string} Valid package name (e.g.,
|
|
87
|
+
* @returns {string} Valid package name (e.g., my_project)
|
|
88
88
|
*/
|
|
89
89
|
function artifactIdToPackageName(artifactId) {
|
|
90
90
|
return artifactId
|
|
91
91
|
.toLowerCase()
|
|
92
|
-
.replace(
|
|
92
|
+
.replace(/-/g, '_')
|
|
93
|
+
.replace(/[^a-z0-9_]/g, '');
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
/**
|