@currentjs/gen 0.5.1 → 0.5.3

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.
@@ -0,0 +1,764 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleCreateModel = handleCreateModel;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const yaml_1 = require("yaml");
40
+ const colors_1 = require("../utils/colors");
41
+ const promptUtils_1 = require("../utils/promptUtils");
42
+ const generateAll_1 = require("./generateAll");
43
+ // ─── Supported field types ───────────────────────────────────────────────────
44
+ const PRIMITIVE_TYPES = [
45
+ { label: 'string', value: 'string' },
46
+ { label: 'number', value: 'number' },
47
+ { label: 'integer', value: 'integer' },
48
+ { label: 'decimal', value: 'decimal' },
49
+ { label: 'boolean', value: 'boolean' },
50
+ { label: 'datetime', value: 'datetime' },
51
+ { label: 'date', value: 'date' },
52
+ { label: 'id (foreign-key / reference)', value: 'id' },
53
+ { label: 'json', value: 'json' },
54
+ { label: 'enum (choose values)', value: 'enum' },
55
+ { label: 'Custom value object', value: '__valueObject__' },
56
+ ];
57
+ const PRIMITIVE_TYPES_NO_VO = PRIMITIVE_TYPES.filter(t => t.value !== '__valueObject__');
58
+ const NUMERIC_TYPES = new Set(['number', 'integer', 'decimal']);
59
+ // ─── Auth options ─────────────────────────────────────────────────────────────
60
+ const AUTH_OPTIONS = [
61
+ { label: 'all (public)', value: 'all' },
62
+ { label: 'authenticated (any logged-in user)', value: 'authenticated' },
63
+ { label: 'owner (resource owner)', value: 'owner' },
64
+ { label: 'admin', value: 'admin' },
65
+ { label: 'Custom roles (enter manually)', value: '__custom__' },
66
+ ];
67
+ // ─── Helper: resolve module YAML path ────────────────────────────────────────
68
+ function findModuleYaml(moduleName) {
69
+ const lower = moduleName.toLowerCase();
70
+ const candidates = [
71
+ path.resolve(process.cwd(), 'src', 'modules', moduleName, `${lower}.yaml`),
72
+ path.resolve(process.cwd(), 'src', 'modules', lower, `${lower}.yaml`),
73
+ ];
74
+ for (const c of candidates) {
75
+ if (fs.existsSync(c))
76
+ return c;
77
+ }
78
+ throw new Error(`Module "${moduleName}" not found. Did you run ${colors_1.colors.bold(colors_1.colors.green(`current create module ${moduleName}`))} first?`);
79
+ }
80
+ // ─── Helper: check if module already has an aggregate root ───────────────────
81
+ function hasAggregateRoot(moduleConfig) {
82
+ var _a;
83
+ const aggregates = (_a = moduleConfig === null || moduleConfig === void 0 ? void 0 : moduleConfig.domain) === null || _a === void 0 ? void 0 : _a.aggregates;
84
+ if (!aggregates || typeof aggregates !== 'object')
85
+ return false;
86
+ return Object.values(aggregates).some((agg) => (agg === null || agg === void 0 ? void 0 : agg.root) === true);
87
+ }
88
+ // ─── Phase 1: Collect fields ─────────────────────────────────────────────────
89
+ async function promptValueObjectFields(rl, voName) {
90
+ const fields = [];
91
+ console.log(colors_1.colors.cyan(`\n Defining value object ${colors_1.colors.bold(voName)} fields:`));
92
+ while (true) {
93
+ const hint = fields.length === 0 ? 'Field name:' : 'Field name (leave blank to finish):';
94
+ const name = await (0, promptUtils_1.promptText)(rl, ` ${hint}`, { allowEmpty: fields.length > 0 });
95
+ if (name === '')
96
+ break;
97
+ const typeChoice = await (0, promptUtils_1.promptSelect)(rl, ` Field type for ${colors_1.colors.bold(name)}:`, PRIMITIVE_TYPES_NO_VO);
98
+ let enumValues;
99
+ let constraints = {};
100
+ if (typeChoice.value === 'enum') {
101
+ const raw = await (0, promptUtils_1.promptText)(rl, ` Enum values (comma-separated):`);
102
+ enumValues = raw.split(',').map(s => s.trim()).filter(Boolean);
103
+ }
104
+ else if (NUMERIC_TYPES.has(typeChoice.value)) {
105
+ const min = await (0, promptUtils_1.promptNumber)(rl, ` Min value:`);
106
+ const max = await (0, promptUtils_1.promptNumber)(rl, ` Max value:`);
107
+ if (min !== undefined)
108
+ constraints.min = min;
109
+ if (max !== undefined)
110
+ constraints.max = max;
111
+ }
112
+ else if (typeChoice.value === 'string') {
113
+ const pat = await (0, promptUtils_1.promptText)(rl, ` Pattern (regex, leave blank to skip):`, { allowEmpty: true });
114
+ if (pat)
115
+ constraints.pattern = pat;
116
+ }
117
+ const field = {
118
+ name,
119
+ type: typeChoice.value,
120
+ ...(enumValues ? { enumValues } : {}),
121
+ ...(Object.keys(constraints).length > 0 ? { constraints } : {}),
122
+ };
123
+ fields.push(field);
124
+ }
125
+ return fields;
126
+ }
127
+ async function promptFields(rl, existingValueObjects) {
128
+ const fields = [];
129
+ const valueObjects = new Map(existingValueObjects);
130
+ while (true) {
131
+ const isFirst = fields.length === 0;
132
+ const hint = isFirst ? 'Field name:' : 'Field name (leave blank to finish):';
133
+ console.log('');
134
+ const name = await (0, promptUtils_1.promptText)(rl, hint, { allowEmpty: !isFirst });
135
+ if (name === '')
136
+ break;
137
+ // Build type list including any already-defined value objects
138
+ const voOptions = [...valueObjects.keys()].map(n => ({ label: `${n} (value object, already defined)`, value: n }));
139
+ const typeChoices = [...PRIMITIVE_TYPES.slice(0, -1), ...voOptions, PRIMITIVE_TYPES[PRIMITIVE_TYPES.length - 1]];
140
+ const typeChoice = await (0, promptUtils_1.promptSelect)(rl, `Field type for ${colors_1.colors.bold(name)}:`, typeChoices);
141
+ let enumValues;
142
+ let valueObjectName;
143
+ let resolvedType = typeChoice.value;
144
+ if (typeChoice.value === 'enum') {
145
+ const raw = await (0, promptUtils_1.promptText)(rl, `Enum values (comma-separated):`);
146
+ enumValues = raw.split(',').map(s => s.trim()).filter(Boolean);
147
+ }
148
+ else if (typeChoice.value === '__valueObject__') {
149
+ const voName = await (0, promptUtils_1.promptText)(rl, `Value object name (e.g. Money, Address):`);
150
+ const capitalizedVoName = voName.charAt(0).toUpperCase() + voName.slice(1);
151
+ if (!valueObjects.has(capitalizedVoName)) {
152
+ const voFields = await promptValueObjectFields(rl, capitalizedVoName);
153
+ valueObjects.set(capitalizedVoName, { name: capitalizedVoName, fields: voFields });
154
+ }
155
+ else {
156
+ console.log(colors_1.colors.gray(` Using existing value object ${capitalizedVoName}`));
157
+ }
158
+ resolvedType = capitalizedVoName;
159
+ valueObjectName = capitalizedVoName;
160
+ }
161
+ else if (valueObjects.has(typeChoice.value)) {
162
+ // User selected an already-defined value object
163
+ resolvedType = typeChoice.value;
164
+ valueObjectName = typeChoice.value;
165
+ }
166
+ const required = await (0, promptUtils_1.promptYesNo)(rl, `Required?`, true);
167
+ const unique = await (0, promptUtils_1.promptYesNo)(rl, `Unique?`, false);
168
+ fields.push({
169
+ name,
170
+ type: resolvedType,
171
+ required,
172
+ unique,
173
+ ...(enumValues ? { enumValues } : {}),
174
+ ...(valueObjectName ? { valueObjectName } : {}),
175
+ });
176
+ }
177
+ return { fields, valueObjects };
178
+ }
179
+ // ─── Phase 1b: Child entities ────────────────────────────────────────────────
180
+ async function promptChildEntities(rl, parentName, existingValueObjects) {
181
+ const children = [];
182
+ let currentValueObjects = new Map(existingValueObjects);
183
+ const wantChildren = await (0, promptUtils_1.promptYesNo)(rl, `Add child entities to ${colors_1.colors.bold(parentName)}?`, false);
184
+ if (!wantChildren)
185
+ return { children, valueObjects: currentValueObjects };
186
+ while (true) {
187
+ const rawName = await (0, promptUtils_1.promptText)(rl, `Child entity name:`);
188
+ const childName = rawName.charAt(0).toUpperCase() + rawName.slice(1);
189
+ console.log(colors_1.colors.gray(`\n Defining fields for ${colors_1.colors.bold(childName)}:`));
190
+ const { fields, valueObjects: updatedVOs } = await promptFields(rl, currentValueObjects);
191
+ currentValueObjects = updatedVOs;
192
+ children.push({ name: childName, fields });
193
+ const another = await (0, promptUtils_1.promptYesNo)(rl, `Add another child entity?`, false);
194
+ if (!another)
195
+ break;
196
+ }
197
+ return { children, valueObjects: currentValueObjects };
198
+ }
199
+ // ─── Phase 2: Use cases ───────────────────────────────────────────────────────
200
+ async function promptListUseCase(rl, fieldNames) {
201
+ var _a, _b;
202
+ console.log(colors_1.colors.bold(colors_1.colors.cyan('\n Configuring LIST use case:')));
203
+ const pagination = await (0, promptUtils_1.promptYesNo)(rl, ` Use pagination?`, true);
204
+ let paginationType = 'offset';
205
+ let perPage = 20;
206
+ if (pagination) {
207
+ const ptChoice = await (0, promptUtils_1.promptSelect)(rl, ` Pagination type:`, [
208
+ { label: 'offset (page + limit)', value: 'offset' },
209
+ { label: 'cursor (cursor-based)', value: 'cursor' },
210
+ ]);
211
+ paginationType = ptChoice.value;
212
+ const pp = await (0, promptUtils_1.promptNumber)(rl, ` Items per page:`, 20);
213
+ perPage = pp !== null && pp !== void 0 ? pp : 20;
214
+ }
215
+ const filterChoices = fieldNames.map(f => ({ label: f, value: f }));
216
+ const filterSelected = await (0, promptUtils_1.promptMultiSelect)(rl, ` Filter by which fields? (select fields for filtering)`, filterChoices, { allowNone: true, defaultAll: false });
217
+ const filterFields = filterSelected.map(f => f.value);
218
+ const sortChoices = fieldNames.map(f => ({ label: f, value: f }));
219
+ const sortSelected = await (0, promptUtils_1.promptMultiSelect)(rl, ` Sort by which fields?`, sortChoices, { allowNone: true, defaultAll: false });
220
+ const sortFields = sortSelected.map(f => f.value);
221
+ let defaultSortField = (_b = (_a = sortFields[0]) !== null && _a !== void 0 ? _a : fieldNames[0]) !== null && _b !== void 0 ? _b : 'id';
222
+ let defaultSortOrder = 'asc';
223
+ if (sortFields.length > 0) {
224
+ const dsfChoices = sortFields.map(f => ({ label: f, value: f }));
225
+ const dsf = await (0, promptUtils_1.promptSelect)(rl, ` Default sort field:`, dsfChoices);
226
+ defaultSortField = dsf.value;
227
+ const dsoChoice = await (0, promptUtils_1.promptSelect)(rl, ` Default sort order:`, [
228
+ { label: 'asc', value: 'asc' },
229
+ { label: 'desc', value: 'desc' },
230
+ ]);
231
+ defaultSortOrder = dsoChoice.value;
232
+ }
233
+ const outputModeChoice = await (0, promptUtils_1.promptSelect)(rl, ` Output fields:`, [
234
+ { label: 'all fields', value: 'all' },
235
+ { label: 'pick (select which to include)', value: 'pick' },
236
+ { label: 'omit (select which to exclude)', value: 'omit' },
237
+ ]);
238
+ const outputMode = outputModeChoice.value;
239
+ let outputFields = [];
240
+ if (outputMode !== 'all') {
241
+ const ofChoices = fieldNames.map(f => ({ label: f, value: f }));
242
+ const label = outputMode === 'pick' ? 'fields to include' : 'fields to exclude';
243
+ const selected = await (0, promptUtils_1.promptMultiSelect)(rl, ` Select ${label}:`, ofChoices, { allowNone: false, defaultAll: false });
244
+ outputFields = selected.map(f => f.value);
245
+ }
246
+ return { pagination, paginationType, perPage, filterFields, sortFields, defaultSortField, defaultSortOrder, outputMode, outputFields };
247
+ }
248
+ async function promptCreateUseCase(rl, fieldNames, useCaseName) {
249
+ console.log(colors_1.colors.bold(colors_1.colors.cyan(`\n Configuring ${useCaseName.toUpperCase()} use case:`)));
250
+ const inputModeChoice = await (0, promptUtils_1.promptSelect)(rl, ` Input fields:`, [
251
+ { label: 'all fields', value: 'all' },
252
+ { label: 'pick (select which to include)', value: 'pick' },
253
+ { label: 'omit (select which to exclude)', value: 'omit' },
254
+ ]);
255
+ const inputMode = inputModeChoice.value;
256
+ let inputFields = [];
257
+ if (inputMode !== 'all' && fieldNames.length > 0) {
258
+ const label = inputMode === 'pick' ? 'fields to include' : 'fields to exclude';
259
+ const choices = fieldNames.map(f => ({ label: f, value: f }));
260
+ const selected = await (0, promptUtils_1.promptMultiSelect)(rl, ` Select ${label}:`, choices, { allowNone: false, defaultAll: false });
261
+ inputFields = selected.map(f => f.value);
262
+ }
263
+ const addFields = [];
264
+ const hasAdd = await (0, promptUtils_1.promptYesNo)(rl, ` Add extra input fields not on the model?`, false);
265
+ if (hasAdd) {
266
+ while (true) {
267
+ const fn = await (0, promptUtils_1.promptText)(rl, ` Extra field name (leave blank to finish):`, { allowEmpty: true });
268
+ if (fn === '')
269
+ break;
270
+ const ftChoice = await (0, promptUtils_1.promptSelect)(rl, ` Field type for ${colors_1.colors.bold(fn)}:`, PRIMITIVE_TYPES_NO_VO);
271
+ const freq = await (0, promptUtils_1.promptYesNo)(rl, ` Required?`, false);
272
+ addFields.push({ name: fn, type: ftChoice.value, required: freq });
273
+ }
274
+ }
275
+ // Fields eligible for validation: the picked/non-omitted set, or all if mode is 'all'
276
+ const fieldsForValidation = inputMode === 'all'
277
+ ? fieldNames
278
+ : inputMode === 'pick'
279
+ ? inputFields
280
+ : fieldNames.filter(f => !inputFields.includes(f));
281
+ const validateFields = {};
282
+ const hasValidate = fieldsForValidation.length > 0 && await (0, promptUtils_1.promptYesNo)(rl, ` Add validation rules?`, false);
283
+ if (hasValidate) {
284
+ for (const fn of fieldsForValidation) {
285
+ const doValidate = await (0, promptUtils_1.promptYesNo)(rl, ` Validate field "${fn}"?`, false);
286
+ if (!doValidate)
287
+ continue;
288
+ const rule = {};
289
+ const pat = await (0, promptUtils_1.promptText)(rl, ` Pattern (regex, leave blank to skip):`, { allowEmpty: true });
290
+ if (pat)
291
+ rule.pattern = pat;
292
+ const minV = await (0, promptUtils_1.promptNumber)(rl, ` Min value:`);
293
+ if (minV !== undefined)
294
+ rule.min = minV;
295
+ const maxV = await (0, promptUtils_1.promptNumber)(rl, ` Max value:`);
296
+ if (maxV !== undefined)
297
+ rule.max = maxV;
298
+ if (Object.keys(rule).length > 0)
299
+ validateFields[fn] = rule;
300
+ }
301
+ }
302
+ return { inputMode, inputFields, addFields, validateFields };
303
+ }
304
+ async function promptUpdateUseCase(rl, fieldNames) {
305
+ const base = await promptCreateUseCase(rl, fieldNames, 'update');
306
+ const partial = await (0, promptUtils_1.promptYesNo)(rl, ` Partial update (all fields optional)?`, true);
307
+ return { ...base, partial };
308
+ }
309
+ // ─── Phase 3/4: Auth per route ───────────────────────────────────────────────
310
+ async function promptAuth(rl, label) {
311
+ const choice = await (0, promptUtils_1.promptSelect)(rl, ` Auth for ${colors_1.colors.bold(label)}:`, AUTH_OPTIONS);
312
+ if (choice.value === '__custom__') {
313
+ const raw = await (0, promptUtils_1.promptText)(rl, ` Enter roles (comma-separated):`);
314
+ const roles = raw.split(',').map(s => s.trim()).filter(Boolean);
315
+ return roles.length === 1 ? roles[0] : roles;
316
+ }
317
+ return choice.value;
318
+ }
319
+ // ─── Build YAML fragments ─────────────────────────────────────────────────────
320
+ function buildAggregateConfig(modelName, fields, isRoot, entityNames) {
321
+ const aggregateFields = {};
322
+ for (const f of fields) {
323
+ const fieldDef = { type: f.type, required: f.required };
324
+ if (f.unique)
325
+ fieldDef.unique = true;
326
+ if (f.enumValues)
327
+ fieldDef.values = f.enumValues;
328
+ aggregateFields[f.name] = fieldDef;
329
+ }
330
+ const config = { fields: aggregateFields };
331
+ if (isRoot)
332
+ config.root = true;
333
+ if (entityNames && entityNames.length > 0)
334
+ config.entities = entityNames;
335
+ return config;
336
+ }
337
+ function buildValueObjectsConfig(valueObjects) {
338
+ const result = {};
339
+ for (const [name, vo] of valueObjects) {
340
+ const voFields = {};
341
+ for (const f of vo.fields) {
342
+ const fieldDef = {};
343
+ if (f.type === 'enum' && f.enumValues) {
344
+ fieldDef.type = 'enum';
345
+ fieldDef.values = f.enumValues;
346
+ }
347
+ else {
348
+ fieldDef.type = f.type;
349
+ }
350
+ if (f.constraints && Object.keys(f.constraints).length > 0) {
351
+ fieldDef.constraints = f.constraints;
352
+ }
353
+ voFields[f.name] = fieldDef;
354
+ }
355
+ result[name] = { fields: voFields };
356
+ }
357
+ return result;
358
+ }
359
+ function buildListUseCaseConfig(modelName, opts) {
360
+ const input = {};
361
+ if (opts.pagination) {
362
+ input.pagination = {
363
+ type: opts.paginationType,
364
+ defaults: { limit: opts.perPage, maxLimit: Math.max(opts.perPage * 5, 100) },
365
+ };
366
+ }
367
+ if (opts.filterFields.length > 0) {
368
+ const filters = {};
369
+ for (const f of opts.filterFields) {
370
+ filters[f] = { type: 'string', optional: true };
371
+ }
372
+ input.filters = filters;
373
+ }
374
+ if (opts.sortFields.length > 0) {
375
+ input.sorting = {
376
+ allow: opts.sortFields,
377
+ default: { field: opts.defaultSortField, order: opts.defaultSortOrder },
378
+ };
379
+ }
380
+ const output = { from: modelName, pagination: opts.pagination };
381
+ if (opts.outputMode === 'pick' && opts.outputFields.length > 0) {
382
+ output.pick = ['id', ...opts.outputFields.filter(f => f !== 'id')];
383
+ }
384
+ else if (opts.outputMode === 'omit' && opts.outputFields.length > 0) {
385
+ output.omit = opts.outputFields;
386
+ }
387
+ return { input, output, handlers: ['default:list'] };
388
+ }
389
+ function buildCreateUseCaseConfig(modelName, opts) {
390
+ const input = { from: modelName };
391
+ if (opts.inputMode === 'pick' && opts.inputFields.length > 0) {
392
+ input.pick = opts.inputFields;
393
+ }
394
+ else if (opts.inputMode === 'omit' && opts.inputFields.length > 0) {
395
+ input.omit = opts.inputFields;
396
+ }
397
+ if (opts.addFields.length > 0) {
398
+ input.add = {};
399
+ for (const af of opts.addFields) {
400
+ input.add[af.name] = { type: af.type, required: af.required };
401
+ }
402
+ }
403
+ if (Object.keys(opts.validateFields).length > 0) {
404
+ input.validate = opts.validateFields;
405
+ }
406
+ return { input, output: { from: modelName }, handlers: ['default:create'] };
407
+ }
408
+ function buildUpdateUseCaseConfig(modelName, opts) {
409
+ const base = buildCreateUseCaseConfig(modelName, opts);
410
+ base.input.identifier = 'id';
411
+ if (opts.partial)
412
+ base.input.partial = true;
413
+ base.handlers = ['default:update'];
414
+ return base;
415
+ }
416
+ function buildWebConfig(modelName, lower, useCases, authMap) {
417
+ var _a, _b, _c, _d, _e, _f;
418
+ const pages = [];
419
+ if (useCases.includes('list')) {
420
+ pages.push({ path: '/', useCase: `${modelName}:list`, view: `${lower}List`, auth: (_a = authMap['list']) !== null && _a !== void 0 ? _a : 'all' });
421
+ }
422
+ if (useCases.includes('get')) {
423
+ pages.push({ path: '/:id', useCase: `${modelName}:get`, view: `${lower}Detail`, auth: (_b = authMap['get']) !== null && _b !== void 0 ? _b : 'all' });
424
+ }
425
+ if (useCases.includes('create')) {
426
+ pages.push({ path: '/create', method: 'GET', view: `${lower}Create`, auth: (_c = authMap['create']) !== null && _c !== void 0 ? _c : 'authenticated' });
427
+ pages.push({
428
+ path: '/create',
429
+ method: 'POST',
430
+ useCase: `${modelName}:create`,
431
+ auth: (_d = authMap['create']) !== null && _d !== void 0 ? _d : 'authenticated',
432
+ onSuccess: { redirect: `/${lower}/:id`, toast: `${modelName} created successfully` },
433
+ onError: { stay: true, toast: 'error' },
434
+ });
435
+ }
436
+ if (useCases.includes('update')) {
437
+ pages.push({
438
+ path: '/:id/edit',
439
+ method: 'GET',
440
+ useCase: `${modelName}:get`,
441
+ view: `${lower}Edit`,
442
+ auth: (_e = authMap['update']) !== null && _e !== void 0 ? _e : ['owner', 'admin'],
443
+ });
444
+ pages.push({
445
+ path: '/:id/edit',
446
+ method: 'POST',
447
+ useCase: `${modelName}:update`,
448
+ auth: (_f = authMap['update']) !== null && _f !== void 0 ? _f : ['owner', 'admin'],
449
+ onSuccess: { back: true, toast: `${modelName} updated successfully` },
450
+ onError: { stay: true, toast: 'error' },
451
+ });
452
+ }
453
+ return { prefix: `/${lower}`, layout: 'main_view', pages };
454
+ }
455
+ function buildApiConfig(modelName, lower, useCases, authMap) {
456
+ var _a;
457
+ const endpoints = [];
458
+ const METHOD_MAP = { list: 'GET', get: 'GET', create: 'POST', update: 'PUT', delete: 'DELETE' };
459
+ const PATH_MAP = { list: '/', get: '/:id', create: '/', update: '/:id', delete: '/:id' };
460
+ for (const uc of useCases) {
461
+ endpoints.push({
462
+ method: METHOD_MAP[uc],
463
+ path: PATH_MAP[uc],
464
+ useCase: `${modelName}:${uc}`,
465
+ auth: (_a = authMap[uc]) !== null && _a !== void 0 ? _a : 'all',
466
+ });
467
+ }
468
+ return { prefix: `/api/${lower}`, endpoints };
469
+ }
470
+ function buildChildWebConfig(childName, childLower, parentLower, parentIdField, useCases, authMap) {
471
+ var _a, _b, _c, _d, _e, _f;
472
+ const pages = [];
473
+ if (useCases.includes('list')) {
474
+ pages.push({ path: '/', useCase: `${childName}:list`, view: `${childLower}List`, auth: (_a = authMap['list']) !== null && _a !== void 0 ? _a : 'all' });
475
+ }
476
+ if (useCases.includes('get')) {
477
+ pages.push({ path: '/:id', useCase: `${childName}:get`, view: `${childLower}Detail`, auth: (_b = authMap['get']) !== null && _b !== void 0 ? _b : 'all' });
478
+ }
479
+ if (useCases.includes('create')) {
480
+ pages.push({ path: '/create', method: 'GET', view: `${childLower}Create`, auth: (_c = authMap['create']) !== null && _c !== void 0 ? _c : 'authenticated' });
481
+ pages.push({
482
+ path: '/create',
483
+ method: 'POST',
484
+ useCase: `${childName}:create`,
485
+ auth: (_d = authMap['create']) !== null && _d !== void 0 ? _d : 'authenticated',
486
+ onSuccess: { back: true, toast: `${childName} created successfully` },
487
+ onError: { stay: true, toast: 'error' },
488
+ });
489
+ }
490
+ if (useCases.includes('update')) {
491
+ pages.push({
492
+ path: '/:id/edit',
493
+ method: 'GET',
494
+ useCase: `${childName}:get`,
495
+ view: `${childLower}Edit`,
496
+ auth: (_e = authMap['update']) !== null && _e !== void 0 ? _e : ['owner', 'admin'],
497
+ });
498
+ pages.push({
499
+ path: '/:id/edit',
500
+ method: 'POST',
501
+ useCase: `${childName}:update`,
502
+ auth: (_f = authMap['update']) !== null && _f !== void 0 ? _f : ['owner', 'admin'],
503
+ onSuccess: { back: true, toast: `${childName} updated successfully` },
504
+ onError: { stay: true, toast: 'error' },
505
+ });
506
+ }
507
+ return { prefix: `/${parentLower}/:${parentIdField}/${childLower}`, layout: 'main_view', pages };
508
+ }
509
+ function buildChildApiConfig(childName, childLower, parentLower, parentIdField, useCases, authMap) {
510
+ var _a;
511
+ const endpoints = [];
512
+ const METHOD_MAP = { list: 'GET', get: 'GET', create: 'POST', update: 'PUT', delete: 'DELETE' };
513
+ const PATH_MAP = { list: '/', get: '/:id', create: '/', update: '/:id', delete: '/:id' };
514
+ for (const uc of useCases) {
515
+ endpoints.push({
516
+ method: METHOD_MAP[uc],
517
+ path: PATH_MAP[uc],
518
+ useCase: `${childName}:${uc}`,
519
+ auth: (_a = authMap[uc]) !== null && _a !== void 0 ? _a : 'all',
520
+ });
521
+ }
522
+ return { prefix: `/api/${parentLower}/:${parentIdField}/${childLower}`, endpoints };
523
+ }
524
+ // ─── Merge into existing YAML config ─────────────────────────────────────────
525
+ function deepMerge(target, source) {
526
+ if (source === null || source === undefined)
527
+ return target;
528
+ if (typeof source !== 'object' || Array.isArray(source))
529
+ return source;
530
+ const result = { ...target };
531
+ for (const key of Object.keys(source)) {
532
+ if (key in result &&
533
+ typeof result[key] === 'object' &&
534
+ result[key] !== null &&
535
+ !Array.isArray(result[key]) &&
536
+ typeof source[key] === 'object' &&
537
+ source[key] !== null &&
538
+ !Array.isArray(source[key])) {
539
+ result[key] = deepMerge(result[key], source[key]);
540
+ }
541
+ else {
542
+ result[key] = source[key];
543
+ }
544
+ }
545
+ return result;
546
+ }
547
+ // ─── Main wizard for a single model ──────────────────────────────────────────
548
+ async function runModelWizard(rl, moduleName, modelName, moduleYamlPath) {
549
+ var _a, _b, _c;
550
+ let moduleConfig = {};
551
+ if (fs.existsSync(moduleYamlPath)) {
552
+ moduleConfig = (_a = (0, yaml_1.parse)(fs.readFileSync(moduleYamlPath, 'utf8'))) !== null && _a !== void 0 ? _a : {};
553
+ }
554
+ const isRoot = !hasAggregateRoot(moduleConfig);
555
+ console.log('');
556
+ console.log(colors_1.colors.bold(colors_1.colors.brightCyan(`── Model: ${modelName} ──────────────────────────────────────────`)));
557
+ if (isRoot) {
558
+ console.log(colors_1.colors.gray(` This will be the aggregate root of the ${moduleName} module.`));
559
+ }
560
+ // Phase 1: Fields
561
+ console.log(colors_1.colors.bold('\nStep 1: Define fields'));
562
+ const existingValueObjects = new Map();
563
+ const { fields, valueObjects: parentValueObjects } = await promptFields(rl, existingValueObjects);
564
+ if (fields.length === 0) {
565
+ console.log(colors_1.colors.yellow('No fields defined. Model will be created with no fields.'));
566
+ }
567
+ const fieldNames = fields.map(f => f.name);
568
+ // Phase 2: Child entities
569
+ console.log(colors_1.colors.bold('\nStep 2: Child entities'));
570
+ const { children, valueObjects } = await promptChildEntities(rl, modelName, parentValueObjects);
571
+ // Phase 3: Use cases
572
+ console.log(colors_1.colors.bold('\nStep 3: Use cases'));
573
+ const createUseCases = await (0, promptUtils_1.promptYesNo)(rl, `Create default use cases (list, get, create, update, delete)?`, true);
574
+ const collectedUseCases = {};
575
+ const useCaseNames = [];
576
+ if (createUseCases) {
577
+ useCaseNames.push('list', 'get', 'create', 'update', 'delete');
578
+ const listOpts = await promptListUseCase(rl, fieldNames);
579
+ collectedUseCases['list'] = buildListUseCaseConfig(modelName, listOpts);
580
+ collectedUseCases['get'] = {
581
+ input: { identifier: 'id' },
582
+ output: { from: modelName },
583
+ handlers: ['default:get'],
584
+ };
585
+ const createOpts = await promptCreateUseCase(rl, fieldNames, 'create');
586
+ collectedUseCases['create'] = buildCreateUseCaseConfig(modelName, createOpts);
587
+ const updateOpts = await promptUpdateUseCase(rl, fieldNames);
588
+ collectedUseCases['update'] = buildUpdateUseCaseConfig(modelName, updateOpts);
589
+ collectedUseCases['delete'] = {
590
+ input: { identifier: 'id' },
591
+ output: 'void',
592
+ handlers: ['default:delete'],
593
+ };
594
+ }
595
+ const childConfigs = new Map();
596
+ const lower = modelName.charAt(0).toLowerCase() + modelName.slice(1);
597
+ if (createUseCases && children.length > 0) {
598
+ for (const child of children) {
599
+ const childLower = child.name.charAt(0).toLowerCase() + child.name.slice(1);
600
+ const parentIdField = `${lower}Id`;
601
+ const childFieldNames = child.fields.map(f => f.name);
602
+ console.log(colors_1.colors.bold(colors_1.colors.brightCyan(`\n ── Child: ${child.name} ──────────────────────────────────────────`)));
603
+ const createChildUCs = await (0, promptUtils_1.promptYesNo)(rl, ` Create CRUD use cases for ${colors_1.colors.bold(child.name)}?`, true);
604
+ if (!createChildUCs)
605
+ continue;
606
+ const childUCNames = ['list', 'get', 'create', 'update', 'delete'];
607
+ const childUseCases = {};
608
+ const childListOpts = await promptListUseCase(rl, childFieldNames);
609
+ const childListConfig = buildListUseCaseConfig(child.name, childListOpts);
610
+ childListConfig.input = { parentId: parentIdField, ...childListConfig.input };
611
+ childUseCases['list'] = childListConfig;
612
+ childUseCases['get'] = {
613
+ input: { identifier: 'id', parentId: parentIdField },
614
+ output: { from: child.name },
615
+ handlers: ['default:get'],
616
+ };
617
+ const childCreateOpts = await promptCreateUseCase(rl, childFieldNames, 'create');
618
+ const childCreateConfig = buildCreateUseCaseConfig(child.name, childCreateOpts);
619
+ childCreateConfig.input = { parentId: parentIdField, ...childCreateConfig.input };
620
+ childUseCases['create'] = childCreateConfig;
621
+ const childUpdateOpts = await promptUpdateUseCase(rl, childFieldNames);
622
+ const childUpdateConfig = buildUpdateUseCaseConfig(child.name, childUpdateOpts);
623
+ childUpdateConfig.input = { parentId: parentIdField, ...childUpdateConfig.input };
624
+ childUseCases['update'] = childUpdateConfig;
625
+ childUseCases['delete'] = {
626
+ input: { identifier: 'id', parentId: parentIdField },
627
+ output: 'void',
628
+ handlers: ['default:delete'],
629
+ };
630
+ const childConfig = { useCases: childUseCases };
631
+ // Web routes for child
632
+ const childWebAuthMap = {};
633
+ const createChildWeb = await (0, promptUtils_1.promptYesNo)(rl, ` Create web routes for ${colors_1.colors.bold(child.name)}?`, true);
634
+ if (createChildWeb) {
635
+ console.log(colors_1.colors.gray(' Configure auth for each web route:'));
636
+ for (const uc of childUCNames) {
637
+ childWebAuthMap[uc] = await promptAuth(rl, `${child.name}:${uc} (web)`);
638
+ }
639
+ childConfig.web = buildChildWebConfig(child.name, childLower, lower, parentIdField, childUCNames, childWebAuthMap);
640
+ }
641
+ // API routes for child
642
+ const childApiAuthMap = {};
643
+ const createChildApi = await (0, promptUtils_1.promptYesNo)(rl, ` Create API routes for ${colors_1.colors.bold(child.name)}?`, true);
644
+ if (createChildApi) {
645
+ console.log(colors_1.colors.gray(' Configure auth for each API endpoint:'));
646
+ for (const uc of childUCNames) {
647
+ childApiAuthMap[uc] = await promptAuth(rl, `${child.name}:${uc} (api)`);
648
+ }
649
+ childConfig.api = buildChildApiConfig(child.name, childLower, lower, parentIdField, childUCNames, childApiAuthMap);
650
+ }
651
+ childConfigs.set(child.name, childConfig);
652
+ }
653
+ }
654
+ // Phase 4: Web routes (only when use cases were created)
655
+ let createWeb = false;
656
+ const webAuthMap = {};
657
+ if (createUseCases) {
658
+ console.log(colors_1.colors.bold('\nStep 4: Web routes'));
659
+ createWeb = await (0, promptUtils_1.promptYesNo)(rl, `Create web routes?`, true);
660
+ if (createWeb) {
661
+ console.log(colors_1.colors.gray(' Configure auth for each web route:'));
662
+ for (const uc of useCaseNames) {
663
+ webAuthMap[uc] = await promptAuth(rl, `${modelName}:${uc} (web)`);
664
+ }
665
+ }
666
+ }
667
+ // Phase 5: API routes (only when use cases were created)
668
+ let createApi = false;
669
+ const apiAuthMap = {};
670
+ if (createUseCases) {
671
+ console.log(colors_1.colors.bold('\nStep 5: API routes'));
672
+ createApi = await (0, promptUtils_1.promptYesNo)(rl, `Create API routes?`, true);
673
+ if (createApi) {
674
+ console.log(colors_1.colors.gray(' Configure auth for each API endpoint:'));
675
+ for (const uc of useCaseNames) {
676
+ apiAuthMap[uc] = await promptAuth(rl, `${modelName}:${uc} (api)`);
677
+ }
678
+ }
679
+ }
680
+ // Phase 6: Build and merge YAML
681
+ const childNames = children.map(c => c.name);
682
+ const newAggregateConfig = buildAggregateConfig(modelName, fields, isRoot, childNames.length > 0 ? childNames : undefined);
683
+ const newValueObjectsConfig = buildValueObjectsConfig(valueObjects);
684
+ const patch = {
685
+ domain: {
686
+ aggregates: { [modelName]: newAggregateConfig },
687
+ },
688
+ };
689
+ // Add child aggregates to domain
690
+ for (const child of children) {
691
+ patch.domain.aggregates[child.name] = buildAggregateConfig(child.name, child.fields, false);
692
+ }
693
+ if (Object.keys(newValueObjectsConfig).length > 0) {
694
+ patch.domain.valueObjects = newValueObjectsConfig;
695
+ }
696
+ if (createUseCases && Object.keys(collectedUseCases).length > 0) {
697
+ patch.useCases = { [modelName]: collectedUseCases };
698
+ // Add child use cases
699
+ for (const [childName, childCfg] of childConfigs) {
700
+ patch.useCases[childName] = childCfg.useCases;
701
+ }
702
+ }
703
+ if (createWeb && useCaseNames.length > 0) {
704
+ patch.web = { [modelName]: buildWebConfig(modelName, lower, useCaseNames, webAuthMap) };
705
+ }
706
+ // Add child web configs (regardless of whether parent has web routes)
707
+ for (const [childName, childCfg] of childConfigs) {
708
+ if (childCfg.web) {
709
+ patch.web = (_b = patch.web) !== null && _b !== void 0 ? _b : {};
710
+ patch.web[childName] = childCfg.web;
711
+ }
712
+ }
713
+ if (createApi && useCaseNames.length > 0) {
714
+ patch.api = { [modelName]: buildApiConfig(modelName, lower, useCaseNames, apiAuthMap) };
715
+ }
716
+ // Add child api configs (regardless of whether parent has api routes)
717
+ for (const [childName, childCfg] of childConfigs) {
718
+ if (childCfg.api) {
719
+ patch.api = (_c = patch.api) !== null && _c !== void 0 ? _c : {};
720
+ patch.api[childName] = childCfg.api;
721
+ }
722
+ }
723
+ const merged = deepMerge(moduleConfig, patch);
724
+ fs.writeFileSync(moduleYamlPath, (0, yaml_1.stringify)(merged), 'utf8');
725
+ console.log('');
726
+ console.log(colors_1.colors.green(` Model ${colors_1.colors.bold(modelName)} saved to ${moduleYamlPath}`));
727
+ }
728
+ // ─── Entry point ──────────────────────────────────────────────────────────────
729
+ async function handleCreateModel(nameArg) {
730
+ // Parse <ModuleName:ModelName>
731
+ if (!nameArg || !nameArg.includes(':')) {
732
+ throw new Error(`Usage: current create model <ModuleName:ModelName>\n Example: current create model Invoice:InvoiceItem`);
733
+ }
734
+ const colonIdx = nameArg.indexOf(':');
735
+ const moduleName = nameArg.slice(0, colonIdx).trim();
736
+ let modelName = nameArg.slice(colonIdx + 1).trim();
737
+ if (!moduleName || !modelName) {
738
+ throw new Error(`Both module name and model name are required: current create model <ModuleName:ModelName>`);
739
+ }
740
+ // Capitalize model name
741
+ modelName = modelName.charAt(0).toUpperCase() + modelName.slice(1);
742
+ const moduleYamlPath = findModuleYaml(moduleName);
743
+ const rl = (0, promptUtils_1.createRl)();
744
+ try {
745
+ let currentModelName = modelName;
746
+ while (true) {
747
+ await runModelWizard(rl, moduleName, currentModelName, moduleYamlPath);
748
+ console.log('');
749
+ const another = await (0, promptUtils_1.promptYesNo)(rl, `Create another model in the ${colors_1.colors.bold(moduleName)} module?`, false);
750
+ if (!another)
751
+ break;
752
+ currentModelName = await (0, promptUtils_1.promptText)(rl, `Model name:`);
753
+ currentModelName = currentModelName.charAt(0).toUpperCase() + currentModelName.slice(1);
754
+ }
755
+ }
756
+ finally {
757
+ rl.close();
758
+ }
759
+ console.log('');
760
+ console.log(colors_1.colors.cyan(`Running ${colors_1.colors.bold('current generate')} for module ${colors_1.colors.bold(moduleName)}...`));
761
+ // Find the module's name as registered in app.yaml for generate
762
+ const moduleKey = moduleName.charAt(0).toUpperCase() + moduleName.slice(1).toLowerCase();
763
+ await (0, generateAll_1.handleGenerateAll)(undefined, undefined, moduleKey, { force: false, skip: false, withTemplates: false });
764
+ }