eva4j 1.0.12 → 1.0.13
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 +426 -9
- package/DOMAIN_YAML_GUIDE.md +374 -21
- package/FUTURE_FEATURES.md +94 -1
- package/docs/commands/GENERATE_ENTITIES.md +252 -2404
- package/docs/commands/GENERATE_RECORD.md +335 -311
- package/examples/doctor-evaluation.yaml +3 -3
- package/examples/domain-audit-complete.yaml +2 -2
- package/examples/domain-collections.yaml +2 -2
- package/examples/domain-ecommerce.yaml +2 -2
- package/examples/domain-events.yaml +1 -1
- package/examples/domain-field-visibility.yaml +11 -5
- package/examples/domain-multi-aggregate.yaml +6 -6
- package/examples/domain-one-to-many.yaml +1 -1
- package/examples/domain-one-to-one.yaml +1 -1
- package/examples/domain-secondary-onetomany.yaml +1 -1
- package/examples/domain-secondary-onetoone.yaml +1 -1
- package/examples/domain-simple.yaml +1 -1
- package/examples/domain-soft-delete.yaml +3 -3
- package/examples/domain-transitions.yaml +1 -1
- package/examples/domain-value-objects.yaml +1 -1
- package/package.json +1 -1
- package/src/utils/yaml-to-entity.js +69 -2
- package/templates/aggregate/AggregateRoot.java.ejs +5 -1
- package/templates/aggregate/DomainEntity.java.ejs +5 -1
- package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -1
- package/templates/aggregate/JpaEntity.java.ejs +2 -1
- package/templates/base/root/AGENTS.md.ejs +916 -51
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
aggregates:
|
|
6
6
|
- name: Order
|
|
7
7
|
entities:
|
|
8
|
-
- name:
|
|
8
|
+
- name: Order
|
|
9
9
|
isRoot: true
|
|
10
10
|
tableName: orders
|
|
11
11
|
audit:
|
|
@@ -67,7 +67,7 @@ aggregates:
|
|
|
67
67
|
|
|
68
68
|
- name: Customer
|
|
69
69
|
entities:
|
|
70
|
-
- name:
|
|
70
|
+
- name: Customer
|
|
71
71
|
isRoot: true
|
|
72
72
|
tableName: customers
|
|
73
73
|
audit:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
aggregates:
|
|
6
6
|
- name: BlogPost
|
|
7
7
|
entities:
|
|
8
|
-
- name:
|
|
8
|
+
- name: BlogPost
|
|
9
9
|
isRoot: true
|
|
10
10
|
tableName: blog_posts
|
|
11
11
|
auditable: true
|
|
@@ -41,7 +41,7 @@ aggregates:
|
|
|
41
41
|
cascade: [PERSIST, MERGE, REMOVE]
|
|
42
42
|
fetch: LAZY
|
|
43
43
|
|
|
44
|
-
- name:
|
|
44
|
+
- name: Comment
|
|
45
45
|
tableName: comments
|
|
46
46
|
auditable: true
|
|
47
47
|
fields:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
aggregates:
|
|
6
6
|
- name: ShoppingCart
|
|
7
7
|
entities:
|
|
8
|
-
- name:
|
|
8
|
+
- name: ShoppingCart
|
|
9
9
|
isRoot: true
|
|
10
10
|
tableName: shopping_carts
|
|
11
11
|
auditable: true
|
|
@@ -39,7 +39,7 @@ aggregates:
|
|
|
39
39
|
cascade: [PERSIST, MERGE, REMOVE]
|
|
40
40
|
fetch: LAZY
|
|
41
41
|
|
|
42
|
-
- name:
|
|
42
|
+
- name: CartItem
|
|
43
43
|
tableName: cart_items
|
|
44
44
|
auditable: true
|
|
45
45
|
fields:
|
|
@@ -6,7 +6,7 @@ aggregates:
|
|
|
6
6
|
# User Management Aggregate
|
|
7
7
|
- name: User
|
|
8
8
|
entities:
|
|
9
|
-
- name:
|
|
9
|
+
- name: User
|
|
10
10
|
isRoot: true
|
|
11
11
|
tableName: users
|
|
12
12
|
audit:
|
|
@@ -40,6 +40,7 @@ aggregates:
|
|
|
40
40
|
- name: loginCount
|
|
41
41
|
type: Integer
|
|
42
42
|
readOnly: true
|
|
43
|
+
defaultValue: 0 # Initialized to 0 on creation
|
|
43
44
|
|
|
44
45
|
# Readonly field - NOT in constructor or CreateDto
|
|
45
46
|
# Use case: Calculated timestamp
|
|
@@ -57,7 +58,7 @@ aggregates:
|
|
|
57
58
|
# Order Management Aggregate
|
|
58
59
|
- name: Order
|
|
59
60
|
entities:
|
|
60
|
-
- name:
|
|
61
|
+
- name: Order
|
|
61
62
|
isRoot: true
|
|
62
63
|
tableName: orders
|
|
63
64
|
audit:
|
|
@@ -80,11 +81,13 @@ aggregates:
|
|
|
80
81
|
- name: totalAmount
|
|
81
82
|
type: BigDecimal
|
|
82
83
|
readOnly: true
|
|
84
|
+
defaultValue: "0.00" # Starts at zero before items are added
|
|
83
85
|
|
|
84
86
|
# Readonly field - count of items
|
|
85
87
|
- name: itemCount
|
|
86
88
|
type: Integer
|
|
87
89
|
readOnly: true
|
|
90
|
+
defaultValue: 0 # Starts at zero
|
|
88
91
|
|
|
89
92
|
# Hidden field - internal processing flag
|
|
90
93
|
- name: processingToken
|
|
@@ -97,7 +100,7 @@ aggregates:
|
|
|
97
100
|
mappedBy: order
|
|
98
101
|
cascade: [PERSIST, MERGE, REMOVE]
|
|
99
102
|
|
|
100
|
-
- name:
|
|
103
|
+
- name: OrderItem
|
|
101
104
|
tableName: order_items
|
|
102
105
|
fields:
|
|
103
106
|
- name: id
|
|
@@ -116,6 +119,7 @@ aggregates:
|
|
|
116
119
|
- name: subtotal
|
|
117
120
|
type: BigDecimal
|
|
118
121
|
readOnly: true
|
|
122
|
+
defaultValue: "0.00" # Calculated on creation
|
|
119
123
|
|
|
120
124
|
relationships:
|
|
121
125
|
- type: ManyToOne
|
|
@@ -125,7 +129,7 @@ aggregates:
|
|
|
125
129
|
# Product Catalog Aggregate
|
|
126
130
|
- name: Product
|
|
127
131
|
entities:
|
|
128
|
-
- name:
|
|
132
|
+
- name: Product
|
|
129
133
|
isRoot: true
|
|
130
134
|
tableName: products
|
|
131
135
|
audit:
|
|
@@ -147,6 +151,7 @@ aggregates:
|
|
|
147
151
|
- name: stockLevel
|
|
148
152
|
type: Integer
|
|
149
153
|
readOnly: true
|
|
154
|
+
defaultValue: 0 # New products start with zero stock
|
|
150
155
|
|
|
151
156
|
# Readonly field - last updated stock timestamp
|
|
152
157
|
- name: stockUpdatedAt
|
|
@@ -166,7 +171,7 @@ aggregates:
|
|
|
166
171
|
# Session Management Aggregate
|
|
167
172
|
- name: Session
|
|
168
173
|
entities:
|
|
169
|
-
- name:
|
|
174
|
+
- name: Session
|
|
170
175
|
isRoot: true
|
|
171
176
|
tableName: sessions
|
|
172
177
|
fields:
|
|
@@ -191,6 +196,7 @@ aggregates:
|
|
|
191
196
|
type: Boolean
|
|
192
197
|
hidden: true
|
|
193
198
|
readOnly: true
|
|
199
|
+
defaultValue: false # Sessions start as not revoked
|
|
194
200
|
|
|
195
201
|
- name: createdAt
|
|
196
202
|
type: LocalDateTime
|
|
@@ -6,7 +6,7 @@ aggregates:
|
|
|
6
6
|
# AGREGADO 1: Customer
|
|
7
7
|
- name: Customer
|
|
8
8
|
entities:
|
|
9
|
-
- name:
|
|
9
|
+
- name: Customer
|
|
10
10
|
isRoot: true
|
|
11
11
|
tableName: customers
|
|
12
12
|
auditable: true
|
|
@@ -30,7 +30,7 @@ aggregates:
|
|
|
30
30
|
cascade: [PERSIST, MERGE, REMOVE]
|
|
31
31
|
fetch: LAZY
|
|
32
32
|
|
|
33
|
-
- name:
|
|
33
|
+
- name: Address
|
|
34
34
|
tableName: customer_addresses
|
|
35
35
|
fields:
|
|
36
36
|
- name: id
|
|
@@ -77,7 +77,7 @@ aggregates:
|
|
|
77
77
|
# AGREGADO 2: Supplier
|
|
78
78
|
- name: Supplier
|
|
79
79
|
entities:
|
|
80
|
-
- name:
|
|
80
|
+
- name: Supplier
|
|
81
81
|
isRoot: true
|
|
82
82
|
tableName: suppliers
|
|
83
83
|
auditable: true
|
|
@@ -97,7 +97,7 @@ aggregates:
|
|
|
97
97
|
- name: rating
|
|
98
98
|
type: Integer
|
|
99
99
|
|
|
100
|
-
- name:
|
|
100
|
+
- name: Contract
|
|
101
101
|
tableName: contracts
|
|
102
102
|
auditable: true
|
|
103
103
|
fields:
|
|
@@ -133,7 +133,7 @@ aggregates:
|
|
|
133
133
|
# AGREGADO 3: Inventory
|
|
134
134
|
- name: Inventory
|
|
135
135
|
entities:
|
|
136
|
-
- name:
|
|
136
|
+
- name: Inventory
|
|
137
137
|
isRoot: true
|
|
138
138
|
tableName: inventory
|
|
139
139
|
auditable: true
|
|
@@ -169,7 +169,7 @@ aggregates:
|
|
|
169
169
|
cascade: [PERSIST, MERGE]
|
|
170
170
|
fetch: LAZY
|
|
171
171
|
|
|
172
|
-
- name:
|
|
172
|
+
- name: StockMovement
|
|
173
173
|
tableName: stock_movements
|
|
174
174
|
auditable: true
|
|
175
175
|
fields:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
aggregates:
|
|
6
6
|
- name: Document
|
|
7
7
|
entities:
|
|
8
|
-
- name:
|
|
8
|
+
- name: Document
|
|
9
9
|
isRoot: true
|
|
10
10
|
tableName: documents
|
|
11
11
|
auditable: true
|
|
@@ -48,7 +48,7 @@ aggregates:
|
|
|
48
48
|
cascade: [PERSIST, MERGE, REMOVE]
|
|
49
49
|
fetch: LAZY
|
|
50
50
|
|
|
51
|
-
- name:
|
|
51
|
+
- name: Revision
|
|
52
52
|
tableName: document_revisions
|
|
53
53
|
auditable: true
|
|
54
54
|
fields:
|
|
@@ -66,7 +66,7 @@ aggregates:
|
|
|
66
66
|
type: String
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
- name:
|
|
69
|
+
- name: Attachment
|
|
70
70
|
tableName: document_attachments
|
|
71
71
|
auditable: true
|
|
72
72
|
fields:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eva4j",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "A powerful Node.js CLI for generating Spring Boot projects with modular architecture that enables efficient monolith-first development with seamless transition to microservices",
|
|
5
5
|
"main": "bin/eva4j.js",
|
|
6
6
|
"bin": {
|
|
@@ -242,8 +242,71 @@ function buildAnnotationString(validation) {
|
|
|
242
242
|
return params.length === 0 ? `@${type}` : `@${type}(${params.join(', ')})`;
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Compute the Java literal for a defaultValue declared in domain.yaml.
|
|
247
|
+
* Returns a string ready to be emitted in a template (e.g. `0`, `"foo"`, `Status.ACTIVE`).
|
|
248
|
+
* Returns null for unsupported combinations — callers should guard before emitting.
|
|
249
|
+
*
|
|
250
|
+
* @param {*} rawValue - The raw value from YAML (string, number, boolean)
|
|
251
|
+
* @param {string} javaType - The resolved Java type (e.g. "String", "BigDecimal")
|
|
252
|
+
* @param {boolean} isEnum - Whether the field type is an enum
|
|
253
|
+
* @returns {string|null}
|
|
254
|
+
*/
|
|
255
|
+
function computeJavaDefaultValue(rawValue, javaType, isEnum) {
|
|
256
|
+
if (rawValue === null || rawValue === undefined) return null;
|
|
257
|
+
|
|
258
|
+
const strValue = String(rawValue);
|
|
259
|
+
|
|
260
|
+
// Enum type: emit EnumType.VALUE
|
|
261
|
+
if (isEnum) {
|
|
262
|
+
return `${javaType}.${strValue}`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
switch (javaType) {
|
|
266
|
+
case 'String':
|
|
267
|
+
return `"${strValue}"`;
|
|
268
|
+
case 'Boolean':
|
|
269
|
+
return strValue.toLowerCase() === 'true' ? 'true' : 'false';
|
|
270
|
+
case 'Integer':
|
|
271
|
+
case 'int':
|
|
272
|
+
return strValue;
|
|
273
|
+
case 'Long':
|
|
274
|
+
case 'long':
|
|
275
|
+
return `${strValue}L`;
|
|
276
|
+
case 'Double':
|
|
277
|
+
case 'double':
|
|
278
|
+
case 'Float':
|
|
279
|
+
case 'float':
|
|
280
|
+
return strValue;
|
|
281
|
+
case 'BigDecimal':
|
|
282
|
+
return `new BigDecimal("${strValue}")`;
|
|
283
|
+
case 'LocalDateTime':
|
|
284
|
+
if (strValue === 'now') return 'LocalDateTime.now()';
|
|
285
|
+
return null; // arbitrary datetime strings not supported
|
|
286
|
+
case 'LocalDate':
|
|
287
|
+
if (strValue === 'now') return 'LocalDate.now()';
|
|
288
|
+
return null;
|
|
289
|
+
case 'LocalTime':
|
|
290
|
+
if (strValue === 'now') return 'LocalTime.now()';
|
|
291
|
+
return null;
|
|
292
|
+
case 'Instant':
|
|
293
|
+
if (strValue === 'now') return 'Instant.now()';
|
|
294
|
+
return null;
|
|
295
|
+
case 'UUID':
|
|
296
|
+
if (strValue === 'random') return 'UUID.randomUUID()';
|
|
297
|
+
return null;
|
|
298
|
+
default:
|
|
299
|
+
// Unknown type — cannot safely emit a literal
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
245
304
|
function parseProperty(propData, valueObjectNames = [], aggregateEnums = []) {
|
|
246
|
-
const { name, type, annotations = [], isValueObject = false, isEmbedded = false, enumValues, readOnly = false, hidden = false, validations = [], reference = null } = propData;
|
|
305
|
+
const { name, type, annotations = [], isValueObject = false, isEmbedded = false, enumValues, readOnly = false, hidden = false, validations = [], reference = null, defaultValue = null } = propData;
|
|
306
|
+
|
|
307
|
+
if (defaultValue !== null && !readOnly) {
|
|
308
|
+
console.warn(`⚠️ Field "${name}": "defaultValue" is only meaningful for readOnly fields. It will be ignored since readOnly is not set.`);
|
|
309
|
+
}
|
|
247
310
|
|
|
248
311
|
if (reference !== null) {
|
|
249
312
|
if (typeof reference !== 'object' || !reference.aggregate || typeof reference.aggregate !== 'string') {
|
|
@@ -308,7 +371,11 @@ function parseProperty(propData, valueObjectNames = [], aggregateEnums = []) {
|
|
|
308
371
|
transitionMeta,
|
|
309
372
|
autoInit,
|
|
310
373
|
autoInitValue,
|
|
311
|
-
reference
|
|
374
|
+
reference,
|
|
375
|
+
defaultValue,
|
|
376
|
+
javaDefaultValue: (readOnly && !autoInit && defaultValue !== null)
|
|
377
|
+
? computeJavaDefaultValue(defaultValue, javaType, !!enumValues || isEnumType)
|
|
378
|
+
: null
|
|
312
379
|
};
|
|
313
380
|
}
|
|
314
381
|
|
|
@@ -84,7 +84,8 @@ public class <%= name %> {
|
|
|
84
84
|
|
|
85
85
|
<% const creationFields = fields.filter(f => f.name !== 'id' && f.name !== 'createdAt' && f.name !== 'updatedAt' && f.name !== 'createdBy' && f.name !== 'updatedBy' && !f.readOnly && !f.autoInit); %>
|
|
86
86
|
<% const autoInitFields = fields.filter(f => f.autoInit); %>
|
|
87
|
-
<%
|
|
87
|
+
<% const defaultValueFields = fields.filter(f => f.readOnly && !f.autoInit && f.javaDefaultValue); %>
|
|
88
|
+
<% if (creationFields.length > 0 || autoInitFields.length > 0 || defaultValueFields.length > 0) { %>
|
|
88
89
|
// Constructor for new entity creation (without id, audit fields, readOnly and auto-initialized fields)
|
|
89
90
|
public <%= name %>(<% let paramIdx = 0; %><% creationFields.forEach((field, idx) => { %><% if (paramIdx > 0) { %>, <% } %><%- field.javaType %> <%= field.name %><% paramIdx++; %><% }); %>) {
|
|
90
91
|
<% creationFields.forEach(field => { %>
|
|
@@ -92,6 +93,9 @@ public class <%= name %> {
|
|
|
92
93
|
<% }); %>
|
|
93
94
|
<% autoInitFields.forEach(field => { %>
|
|
94
95
|
this.<%= field.name %> = <%- field.javaType %>.<%= field.autoInitValue %>;
|
|
96
|
+
<% }); %>
|
|
97
|
+
<% defaultValueFields.forEach(field => { %>
|
|
98
|
+
this.<%= field.name %> = <%- field.javaDefaultValue %>;
|
|
95
99
|
<% }); %>
|
|
96
100
|
}
|
|
97
101
|
<% } %>
|
|
@@ -51,7 +51,8 @@ public class <%= name %> {
|
|
|
51
51
|
|
|
52
52
|
<% const creationFields = fields.filter(f => f.name !== 'id' && f.name !== 'createdAt' && f.name !== 'updatedAt' && f.name !== 'createdBy' && f.name !== 'updatedBy' && !f.readOnly && !f.autoInit); %>
|
|
53
53
|
<% const autoInitFields = fields.filter(f => f.autoInit); %>
|
|
54
|
-
<%
|
|
54
|
+
<% const defaultValueFields = fields.filter(f => f.readOnly && !f.autoInit && f.javaDefaultValue); %>
|
|
55
|
+
<% if (creationFields.length > 0 || autoInitFields.length > 0 || defaultValueFields.length > 0) { %>
|
|
55
56
|
// Constructor for new entity creation (without id, audit fields, readOnly and auto-initialized fields)
|
|
56
57
|
public <%= name %>(<% let paramIdx = 0; %><% creationFields.forEach((field, idx) => { %><% if (paramIdx > 0) { %>, <% } %><%- field.javaType %> <%= field.name %><% paramIdx++; %><% }); %>) {
|
|
57
58
|
<% creationFields.forEach(field => { %>
|
|
@@ -59,6 +60,9 @@ public class <%= name %> {
|
|
|
59
60
|
<% }); %>
|
|
60
61
|
<% autoInitFields.forEach(field => { %>
|
|
61
62
|
this.<%= field.name %> = <%- field.javaType %>.<%= field.autoInitValue %>;
|
|
63
|
+
<% }); %>
|
|
64
|
+
<% defaultValueFields.forEach(field => { %>
|
|
65
|
+
this.<%= field.name %> = <%- field.javaDefaultValue %>;
|
|
62
66
|
<% }); %>
|
|
63
67
|
}
|
|
64
68
|
<% } %>
|
|
@@ -75,7 +75,8 @@ if (audit && audit.trackUser) {
|
|
|
75
75
|
<% } %>@Embedded
|
|
76
76
|
<% } %><% if (field.isEnum) { %>@Enumerated(EnumType.STRING)
|
|
77
77
|
<% } %><% if (field.reference) { %>/** Cross-aggregate reference → <%= field.reference.aggregate %><% if (field.reference.module) { %> (module: <%= field.reference.module %>)<% } %> */
|
|
78
|
-
<% }
|
|
78
|
+
<% } %><% if (field.javaDefaultValue) { %>@Builder.Default
|
|
79
|
+
<% } %>private <%- field.isValueObject ? field.javaTypeJpa : field.javaType %> <%= field.name %><% if (field.javaDefaultValue) { %> = <%- field.javaDefaultValue %><% } %>;
|
|
79
80
|
<% } %>
|
|
80
81
|
<% }); %>
|
|
81
82
|
<% relationships.forEach(rel => { %>
|
|
@@ -85,7 +85,8 @@ if (audit && audit.trackUser) {
|
|
|
85
85
|
<% } else if (field.javaType === 'Long' || field.javaType === 'Integer') { %>@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
86
86
|
<% } %><% } %>@Column(name = "<%= field.name.replace(/([A-Z])/g, '_$1').toLowerCase() %>")
|
|
87
87
|
<% if (field.reference) { %>/** Cross-aggregate reference → <%= field.reference.aggregate %><% if (field.reference.module) { %> (module: <%= field.reference.module %>)<% } %> */
|
|
88
|
-
<% }
|
|
88
|
+
<% } %><% if (field.javaDefaultValue) { %>@Builder.Default
|
|
89
|
+
<% } %>private <%- field.javaType %> <%= field.name %><% if (field.javaDefaultValue) { %> = <%- field.javaDefaultValue %><% } %>;
|
|
89
90
|
<% } %>
|
|
90
91
|
<% }); %>
|
|
91
92
|
<% relationships.forEach(rel => { %>
|