@gravito/scaffold 1.0.0-beta.1

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/dist/index.js ADDED
@@ -0,0 +1,3227 @@
1
+ // src/generators/BaseGenerator.ts
2
+ import fs2 from "fs/promises";
3
+ import path2 from "path";
4
+
5
+ // src/generators/StubGenerator.ts
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import Handlebars from "handlebars";
9
+ var StubGenerator = class {
10
+ config;
11
+ handlebars;
12
+ constructor(config) {
13
+ this.config = config;
14
+ this.handlebars = Handlebars.create();
15
+ this.registerBuiltinHelpers();
16
+ if (config.helpers) {
17
+ for (const [name, helper] of Object.entries(config.helpers)) {
18
+ this.handlebars.registerHelper(name, helper);
19
+ }
20
+ }
21
+ }
22
+ /**
23
+ * Register built-in Handlebars helpers.
24
+ */
25
+ registerBuiltinHelpers() {
26
+ this.handlebars.registerHelper("capitalize", (str) => {
27
+ if (!str) {
28
+ return "";
29
+ }
30
+ return str.charAt(0).toUpperCase() + str.slice(1);
31
+ });
32
+ this.handlebars.registerHelper("lowercase", (str) => {
33
+ return str?.toLowerCase() ?? "";
34
+ });
35
+ this.handlebars.registerHelper("uppercase", (str) => {
36
+ return str?.toUpperCase() ?? "";
37
+ });
38
+ this.handlebars.registerHelper("camelCase", (str) => {
39
+ if (!str) {
40
+ return "";
41
+ }
42
+ return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toLowerCase());
43
+ });
44
+ this.handlebars.registerHelper("pascalCase", (str) => {
45
+ if (!str) {
46
+ return "";
47
+ }
48
+ return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toUpperCase());
49
+ });
50
+ this.handlebars.registerHelper("snakeCase", (str) => {
51
+ if (!str) {
52
+ return "";
53
+ }
54
+ return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replace(/[-\s]+/g, "_");
55
+ });
56
+ this.handlebars.registerHelper("kebabCase", (str) => {
57
+ if (!str) {
58
+ return "";
59
+ }
60
+ return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "").replace(/[_\s]+/g, "-");
61
+ });
62
+ this.handlebars.registerHelper("pluralize", (str) => {
63
+ if (!str) {
64
+ return "";
65
+ }
66
+ if (str.endsWith("y")) {
67
+ return `${str.slice(0, -1)}ies`;
68
+ }
69
+ if (str.endsWith("s") || str.endsWith("x") || str.endsWith("ch") || str.endsWith("sh")) {
70
+ return `${str}es`;
71
+ }
72
+ return `${str}s`;
73
+ });
74
+ this.handlebars.registerHelper("date", (format) => {
75
+ const now = /* @__PURE__ */ new Date();
76
+ if (format === "iso") {
77
+ return now.toISOString();
78
+ }
79
+ if (format === "year") {
80
+ return now.getFullYear().toString();
81
+ }
82
+ return now.toISOString().split("T")[0];
83
+ });
84
+ this.handlebars.registerHelper("eq", (a, b) => a === b);
85
+ this.handlebars.registerHelper("neq", (a, b) => a !== b);
86
+ }
87
+ /**
88
+ * Generate a file from a stub template.
89
+ *
90
+ * @param stubName - Name of the stub file (relative to stubsDir)
91
+ * @param outputPath - Output path (relative to outputDir)
92
+ * @param variables - Template variables
93
+ * @returns Path to the generated file
94
+ */
95
+ async generate(stubName, outputPath, variables = {}) {
96
+ const stubPath = path.resolve(this.config.stubsDir, stubName);
97
+ const template = await fs.readFile(stubPath, "utf-8");
98
+ const compiled = this.handlebars.compile(template);
99
+ const content = compiled({
100
+ ...this.config.defaultVariables,
101
+ ...variables
102
+ });
103
+ const fullOutputPath = path.resolve(this.config.outputDir, outputPath);
104
+ await fs.mkdir(path.dirname(fullOutputPath), { recursive: true });
105
+ await fs.writeFile(fullOutputPath, content, "utf-8");
106
+ return fullOutputPath;
107
+ }
108
+ /**
109
+ * Generate multiple files from a stub template.
110
+ *
111
+ * @param stubName - Name of the stub file
112
+ * @param outputs - Array of [outputPath, variables] tuples
113
+ * @returns Array of generated file paths
114
+ */
115
+ async generateMany(stubName, outputs) {
116
+ const results = [];
117
+ for (const [outputPath, variables] of outputs) {
118
+ const result = await this.generate(stubName, outputPath, variables);
119
+ results.push(result);
120
+ }
121
+ return results;
122
+ }
123
+ /**
124
+ * Render a template string directly.
125
+ *
126
+ * @param template - Template string
127
+ * @param variables - Template variables
128
+ * @returns Rendered content
129
+ */
130
+ render(template, variables = {}) {
131
+ const compiled = this.handlebars.compile(template);
132
+ return compiled({
133
+ ...this.config.defaultVariables,
134
+ ...variables
135
+ });
136
+ }
137
+ /**
138
+ * Register a custom Handlebars helper.
139
+ *
140
+ * @param name - Helper name
141
+ * @param helper - Helper function
142
+ */
143
+ registerHelper(name, helper) {
144
+ this.handlebars.registerHelper(name, helper);
145
+ }
146
+ /**
147
+ * Register a Handlebars partial.
148
+ *
149
+ * @param name - Partial name
150
+ * @param partial - Partial template string
151
+ */
152
+ registerPartial(name, partial) {
153
+ this.handlebars.registerPartial(name, partial);
154
+ }
155
+ };
156
+
157
+ // src/generators/BaseGenerator.ts
158
+ var BaseGenerator = class {
159
+ config;
160
+ stubGenerator;
161
+ filesCreated = [];
162
+ constructor(config) {
163
+ this.config = config;
164
+ this.stubGenerator = new StubGenerator({
165
+ stubsDir: config.templatesDir,
166
+ outputDir: ""
167
+ // Set per-generation
168
+ });
169
+ }
170
+ /**
171
+ * Generate the project scaffold.
172
+ *
173
+ * @param context - Generator context
174
+ * @returns Array of created file paths
175
+ */
176
+ async generate(context) {
177
+ this.filesCreated = [];
178
+ const structure = this.getDirectoryStructure(context);
179
+ await this.createStructure(context.targetDir, structure, context);
180
+ await this.generateCommonFiles(context);
181
+ return this.filesCreated;
182
+ }
183
+ /**
184
+ * Create directory structure recursively.
185
+ */
186
+ async createStructure(basePath, nodes, context) {
187
+ for (const node of nodes) {
188
+ const fullPath = path2.resolve(basePath, node.name);
189
+ if (node.type === "directory") {
190
+ await fs2.mkdir(fullPath, { recursive: true });
191
+ this.log(`\u{1F4C1} Created directory: ${node.name}`);
192
+ if (node.children) {
193
+ await this.createStructure(fullPath, node.children, context);
194
+ }
195
+ } else {
196
+ await fs2.mkdir(path2.dirname(fullPath), { recursive: true });
197
+ if (node.template) {
198
+ const templatePath = path2.resolve(this.config.templatesDir, node.template);
199
+ try {
200
+ const template = await fs2.readFile(templatePath, "utf-8");
201
+ const content = this.stubGenerator.render(template, context);
202
+ await fs2.writeFile(fullPath, content, "utf-8");
203
+ } catch {
204
+ await fs2.writeFile(fullPath, node.content ?? "", "utf-8");
205
+ }
206
+ } else if (node.content) {
207
+ await fs2.writeFile(fullPath, node.content, "utf-8");
208
+ } else {
209
+ await fs2.writeFile(fullPath, "", "utf-8");
210
+ }
211
+ this.filesCreated.push(fullPath);
212
+ this.log(`\u{1F4C4} Created file: ${node.name}`);
213
+ }
214
+ }
215
+ }
216
+ /**
217
+ * Generate common files (package.json, .env, etc.)
218
+ */
219
+ async generateCommonFiles(context) {
220
+ await this.writeFile(context.targetDir, "package.json", this.generatePackageJson(context));
221
+ await this.writeFile(context.targetDir, ".env.example", this.generateEnvExample(context));
222
+ await this.writeFile(context.targetDir, ".env", this.generateEnvExample(context));
223
+ await this.writeFile(context.targetDir, ".gitignore", this.generateGitignore());
224
+ await this.writeFile(context.targetDir, "tsconfig.json", this.generateTsConfig());
225
+ await this.writeFile(
226
+ context.targetDir,
227
+ "ARCHITECTURE.md",
228
+ this.generateArchitectureDoc(context)
229
+ );
230
+ }
231
+ /**
232
+ * Write a file and track it.
233
+ */
234
+ async writeFile(basePath, relativePath, content) {
235
+ const fullPath = path2.resolve(basePath, relativePath);
236
+ await fs2.mkdir(path2.dirname(fullPath), { recursive: true });
237
+ await fs2.writeFile(fullPath, content, "utf-8");
238
+ this.filesCreated.push(fullPath);
239
+ this.log(`\u{1F4C4} Created file: ${relativePath}`);
240
+ }
241
+ /**
242
+ * Generate package.json content.
243
+ */
244
+ generatePackageJson(context) {
245
+ const pkg = {
246
+ name: context.nameKebabCase,
247
+ version: "0.1.0",
248
+ type: "module",
249
+ scripts: {
250
+ dev: "bun run --watch src/bootstrap.ts",
251
+ build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
252
+ start: "bun run dist/bootstrap.js",
253
+ test: "bun test",
254
+ typecheck: "tsc --noEmit"
255
+ },
256
+ dependencies: {
257
+ "gravito-core": "^1.0.0-beta.5"
258
+ },
259
+ devDependencies: {
260
+ "@types/bun": "latest",
261
+ typescript: "^5.0.0"
262
+ }
263
+ };
264
+ return JSON.stringify(pkg, null, 2);
265
+ }
266
+ /**
267
+ * Generate .env.example content.
268
+ */
269
+ generateEnvExample(context) {
270
+ return `# Application
271
+ APP_NAME="${context.name}"
272
+ APP_ENV=development
273
+ APP_KEY=
274
+ APP_DEBUG=true
275
+ APP_URL=http://localhost:3000
276
+
277
+ # Server
278
+ PORT=3000
279
+
280
+ # Database
281
+ DB_CONNECTION=sqlite
282
+ DB_DATABASE=database/database.sqlite
283
+
284
+ # Cache
285
+ CACHE_DRIVER=memory
286
+
287
+ # Logging
288
+ LOG_LEVEL=debug
289
+ `;
290
+ }
291
+ /**
292
+ * Generate .gitignore content.
293
+ */
294
+ generateGitignore() {
295
+ return `# Dependencies
296
+ node_modules/
297
+
298
+ # Build output
299
+ dist/
300
+
301
+ # Environment
302
+ .env
303
+ .env.local
304
+ .env.*.local
305
+
306
+ # IDE
307
+ .idea/
308
+ .vscode/
309
+ *.swp
310
+ *.swo
311
+
312
+ # System
313
+ .DS_Store
314
+ Thumbs.db
315
+
316
+ # Logs
317
+ *.log
318
+ logs/
319
+
320
+ # Database
321
+ *.sqlite
322
+ *.sqlite-journal
323
+
324
+ # Coverage
325
+ coverage/
326
+ `;
327
+ }
328
+ /**
329
+ * Generate tsconfig.json content.
330
+ */
331
+ generateTsConfig() {
332
+ const config = {
333
+ compilerOptions: {
334
+ target: "ESNext",
335
+ module: "ESNext",
336
+ moduleResolution: "bundler",
337
+ esModuleInterop: true,
338
+ strict: true,
339
+ skipLibCheck: true,
340
+ declaration: true,
341
+ outDir: "./dist",
342
+ rootDir: "./src",
343
+ baseUrl: ".",
344
+ paths: {
345
+ "@/*": ["./src/*"]
346
+ }
347
+ },
348
+ include: ["src/**/*"],
349
+ exclude: ["node_modules", "dist"]
350
+ };
351
+ return JSON.stringify(config, null, 2);
352
+ }
353
+ /**
354
+ * Log a message if verbose mode is enabled.
355
+ */
356
+ log(message) {
357
+ if (this.config.verbose) {
358
+ console.log(message);
359
+ }
360
+ }
361
+ /**
362
+ * Create generator context from options.
363
+ */
364
+ static createContext(name, targetDir, architecture, packageManager = "bun", extra = {}) {
365
+ const now = /* @__PURE__ */ new Date();
366
+ const pascalCase = name.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toUpperCase());
367
+ const camelCase = pascalCase.replace(/^./, (c) => c.toLowerCase());
368
+ const snakeCase = name.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replace(/[-\s]+/g, "_");
369
+ const kebabCase = name.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "").replace(/[_\s]+/g, "-");
370
+ return {
371
+ name,
372
+ namePascalCase: pascalCase,
373
+ nameCamelCase: camelCase,
374
+ nameSnakeCase: snakeCase,
375
+ nameKebabCase: kebabCase,
376
+ targetDir,
377
+ architecture,
378
+ packageManager,
379
+ year: now.getFullYear().toString(),
380
+ date: now.toISOString().split("T")[0] ?? now.toISOString().slice(0, 10),
381
+ ...extra
382
+ };
383
+ }
384
+ };
385
+
386
+ // src/generators/CleanArchitectureGenerator.ts
387
+ var CleanArchitectureGenerator = class extends BaseGenerator {
388
+ get architectureType() {
389
+ return "clean";
390
+ }
391
+ get displayName() {
392
+ return "Clean Architecture";
393
+ }
394
+ get description() {
395
+ return "Uncle Bob's Clean Architecture with strict dependency rules and pure domain layer";
396
+ }
397
+ getDirectoryStructure(context) {
398
+ return [
399
+ {
400
+ type: "directory",
401
+ name: "config",
402
+ children: [
403
+ { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
404
+ { type: "file", name: "database.ts", content: this.generateDatabaseConfig() },
405
+ { type: "file", name: "auth.ts", content: this.generateAuthConfig() },
406
+ { type: "file", name: "cache.ts", content: this.generateCacheConfig() },
407
+ { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
408
+ ]
409
+ },
410
+ {
411
+ type: "directory",
412
+ name: "src",
413
+ children: [
414
+ // Domain Layer (innermost - no dependencies)
415
+ {
416
+ type: "directory",
417
+ name: "Domain",
418
+ children: [
419
+ {
420
+ type: "directory",
421
+ name: "Entities",
422
+ children: [
423
+ { type: "file", name: "Entity.ts", content: this.generateBaseEntity() },
424
+ { type: "file", name: "User.ts", content: this.generateUserEntity() }
425
+ ]
426
+ },
427
+ {
428
+ type: "directory",
429
+ name: "ValueObjects",
430
+ children: [
431
+ { type: "file", name: "ValueObject.ts", content: this.generateBaseValueObject() },
432
+ { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
433
+ ]
434
+ },
435
+ {
436
+ type: "directory",
437
+ name: "Interfaces",
438
+ children: [
439
+ {
440
+ type: "file",
441
+ name: "IUserRepository.ts",
442
+ content: this.generateUserRepositoryInterface()
443
+ }
444
+ ]
445
+ },
446
+ {
447
+ type: "directory",
448
+ name: "Exceptions",
449
+ children: [
450
+ {
451
+ type: "file",
452
+ name: "DomainException.ts",
453
+ content: this.generateDomainException()
454
+ }
455
+ ]
456
+ }
457
+ ]
458
+ },
459
+ // Application Layer (use cases)
460
+ {
461
+ type: "directory",
462
+ name: "Application",
463
+ children: [
464
+ {
465
+ type: "directory",
466
+ name: "UseCases",
467
+ children: [
468
+ {
469
+ type: "directory",
470
+ name: "User",
471
+ children: [
472
+ {
473
+ type: "file",
474
+ name: "CreateUser.ts",
475
+ content: this.generateCreateUserUseCase()
476
+ },
477
+ { type: "file", name: "GetUser.ts", content: this.generateGetUserUseCase() }
478
+ ]
479
+ }
480
+ ]
481
+ },
482
+ {
483
+ type: "directory",
484
+ name: "DTOs",
485
+ children: [{ type: "file", name: "UserDTO.ts", content: this.generateUserDTO() }]
486
+ },
487
+ {
488
+ type: "directory",
489
+ name: "Interfaces",
490
+ children: [
491
+ {
492
+ type: "file",
493
+ name: "IMailService.ts",
494
+ content: this.generateMailServiceInterface()
495
+ }
496
+ ]
497
+ }
498
+ ]
499
+ },
500
+ // Infrastructure Layer (external implementations)
501
+ {
502
+ type: "directory",
503
+ name: "Infrastructure",
504
+ children: [
505
+ {
506
+ type: "directory",
507
+ name: "Persistence",
508
+ children: [
509
+ {
510
+ type: "directory",
511
+ name: "Repositories",
512
+ children: [
513
+ {
514
+ type: "file",
515
+ name: "UserRepository.ts",
516
+ content: this.generateUserRepository()
517
+ }
518
+ ]
519
+ },
520
+ {
521
+ type: "directory",
522
+ name: "Migrations",
523
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
524
+ }
525
+ ]
526
+ },
527
+ {
528
+ type: "directory",
529
+ name: "ExternalServices",
530
+ children: [
531
+ { type: "file", name: "MailService.ts", content: this.generateMailService() }
532
+ ]
533
+ },
534
+ {
535
+ type: "directory",
536
+ name: "Providers",
537
+ children: [
538
+ {
539
+ type: "file",
540
+ name: "AppServiceProvider.ts",
541
+ content: this.generateAppServiceProvider(context)
542
+ },
543
+ {
544
+ type: "file",
545
+ name: "RepositoryServiceProvider.ts",
546
+ content: this.generateRepositoryServiceProvider()
547
+ }
548
+ ]
549
+ }
550
+ ]
551
+ },
552
+ // Interface Layer (controllers, presenters)
553
+ {
554
+ type: "directory",
555
+ name: "Interface",
556
+ children: [
557
+ {
558
+ type: "directory",
559
+ name: "Http",
560
+ children: [
561
+ {
562
+ type: "directory",
563
+ name: "Controllers",
564
+ children: [
565
+ {
566
+ type: "file",
567
+ name: "UserController.ts",
568
+ content: this.generateUserController()
569
+ }
570
+ ]
571
+ },
572
+ {
573
+ type: "directory",
574
+ name: "Middleware",
575
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
576
+ },
577
+ {
578
+ type: "directory",
579
+ name: "Routes",
580
+ children: [{ type: "file", name: "api.ts", content: this.generateApiRoutes() }]
581
+ }
582
+ ]
583
+ },
584
+ {
585
+ type: "directory",
586
+ name: "Presenters",
587
+ children: [
588
+ { type: "file", name: "UserPresenter.ts", content: this.generateUserPresenter() }
589
+ ]
590
+ }
591
+ ]
592
+ },
593
+ { type: "file", name: "bootstrap.ts", content: this.generateBootstrap(context) }
594
+ ]
595
+ },
596
+ {
597
+ type: "directory",
598
+ name: "tests",
599
+ children: [
600
+ {
601
+ type: "directory",
602
+ name: "Unit",
603
+ children: [
604
+ {
605
+ type: "directory",
606
+ name: "Domain",
607
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
608
+ },
609
+ {
610
+ type: "directory",
611
+ name: "Application",
612
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
613
+ }
614
+ ]
615
+ },
616
+ {
617
+ type: "directory",
618
+ name: "Integration",
619
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
620
+ }
621
+ ]
622
+ }
623
+ ];
624
+ }
625
+ // ─────────────────────────────────────────────────────────────
626
+ // Config Generators (similar to MVC but simplified)
627
+ // ─────────────────────────────────────────────────────────────
628
+ generateAppConfig(context) {
629
+ return `export default {
630
+ name: process.env.APP_NAME ?? '${context.name}',
631
+ env: process.env.APP_ENV ?? 'development',
632
+ debug: process.env.APP_DEBUG === 'true',
633
+ url: process.env.APP_URL ?? 'http://localhost:3000',
634
+ key: process.env.APP_KEY,
635
+ }
636
+ `;
637
+ }
638
+ generateDatabaseConfig() {
639
+ return `export default {
640
+ default: process.env.DB_CONNECTION ?? 'sqlite',
641
+ connections: {
642
+ sqlite: {
643
+ driver: 'sqlite',
644
+ database: process.env.DB_DATABASE ?? 'database/database.sqlite',
645
+ },
646
+ },
647
+ }
648
+ `;
649
+ }
650
+ generateAuthConfig() {
651
+ return `export default {
652
+ defaults: { guard: 'web' },
653
+ guards: {
654
+ web: { driver: 'session', provider: 'users' },
655
+ api: { driver: 'token', provider: 'users' },
656
+ },
657
+ }
658
+ `;
659
+ }
660
+ generateCacheConfig() {
661
+ return `export default {
662
+ default: process.env.CACHE_DRIVER ?? 'memory',
663
+ stores: {
664
+ memory: { driver: 'memory' },
665
+ },
666
+ }
667
+ `;
668
+ }
669
+ generateLoggingConfig() {
670
+ return `export default {
671
+ default: process.env.LOG_CHANNEL ?? 'console',
672
+ channels: {
673
+ console: { driver: 'console', level: process.env.LOG_LEVEL ?? 'debug' },
674
+ },
675
+ }
676
+ `;
677
+ }
678
+ // ─────────────────────────────────────────────────────────────
679
+ // Domain Layer
680
+ // ─────────────────────────────────────────────────────────────
681
+ generateBaseEntity() {
682
+ return `/**
683
+ * Base Entity
684
+ *
685
+ * All domain entities extend this base class.
686
+ * Entities have identity and lifecycle.
687
+ *
688
+ * IMPORTANT: This layer must have NO external dependencies.
689
+ */
690
+
691
+ export abstract class Entity<T> {
692
+ protected readonly _id: T
693
+
694
+ constructor(id: T) {
695
+ this._id = id
696
+ }
697
+
698
+ get id(): T {
699
+ return this._id
700
+ }
701
+
702
+ equals(other: Entity<T>): boolean {
703
+ if (other === null || other === undefined) {
704
+ return false
705
+ }
706
+ if (!(other instanceof Entity)) {
707
+ return false
708
+ }
709
+ return this._id === other._id
710
+ }
711
+ }
712
+ `;
713
+ }
714
+ generateUserEntity() {
715
+ return `/**
716
+ * User Entity
717
+ *
718
+ * Core domain entity representing a user.
719
+ * Contains business logic related to users.
720
+ */
721
+
722
+ import { Entity } from './Entity'
723
+ import { Email } from '../ValueObjects/Email'
724
+
725
+ export interface UserProps {
726
+ name: string
727
+ email: Email
728
+ createdAt: Date
729
+ updatedAt?: Date
730
+ }
731
+
732
+ export class User extends Entity<string> {
733
+ private props: UserProps
734
+
735
+ private constructor(id: string, props: UserProps) {
736
+ super(id)
737
+ this.props = props
738
+ }
739
+
740
+ static create(id: string, name: string, email: string): User {
741
+ return new User(id, {
742
+ name,
743
+ email: Email.create(email),
744
+ createdAt: new Date(),
745
+ })
746
+ }
747
+
748
+ static reconstitute(id: string, props: UserProps): User {
749
+ return new User(id, props)
750
+ }
751
+
752
+ get name(): string {
753
+ return this.props.name
754
+ }
755
+
756
+ get email(): Email {
757
+ return this.props.email
758
+ }
759
+
760
+ get createdAt(): Date {
761
+ return this.props.createdAt
762
+ }
763
+
764
+ updateName(name: string): void {
765
+ if (!name || name.trim().length === 0) {
766
+ throw new Error('Name cannot be empty')
767
+ }
768
+ this.props.name = name.trim()
769
+ this.props.updatedAt = new Date()
770
+ }
771
+ }
772
+ `;
773
+ }
774
+ generateBaseValueObject() {
775
+ return `/**
776
+ * Base Value Object
777
+ *
778
+ * Value objects are immutable and compared by value.
779
+ * They have no identity of their own.
780
+ */
781
+
782
+ export abstract class ValueObject<T> {
783
+ protected readonly props: T
784
+
785
+ constructor(props: T) {
786
+ this.props = Object.freeze(props)
787
+ }
788
+
789
+ equals(other: ValueObject<T>): boolean {
790
+ if (other === null || other === undefined) {
791
+ return false
792
+ }
793
+ return JSON.stringify(this.props) === JSON.stringify(other.props)
794
+ }
795
+ }
796
+ `;
797
+ }
798
+ generateEmailValueObject() {
799
+ return `/**
800
+ * Email Value Object
801
+ *
802
+ * Encapsulates email validation and comparison.
803
+ */
804
+
805
+ import { ValueObject } from './ValueObject'
806
+
807
+ interface EmailProps {
808
+ value: string
809
+ }
810
+
811
+ export class Email extends ValueObject<EmailProps> {
812
+ private constructor(props: EmailProps) {
813
+ super(props)
814
+ }
815
+
816
+ static create(email: string): Email {
817
+ if (!Email.isValid(email)) {
818
+ throw new Error(\`Invalid email: \${email}\`)
819
+ }
820
+ return new Email({ value: email.toLowerCase().trim() })
821
+ }
822
+
823
+ static isValid(email: string): boolean {
824
+ const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/
825
+ return emailRegex.test(email)
826
+ }
827
+
828
+ get value(): string {
829
+ return this.props.value
830
+ }
831
+
832
+ toString(): string {
833
+ return this.value
834
+ }
835
+ }
836
+ `;
837
+ }
838
+ generateUserRepositoryInterface() {
839
+ return `/**
840
+ * User Repository Interface
841
+ *
842
+ * Defines the contract for user persistence.
843
+ * Implementations are in Infrastructure layer.
844
+ */
845
+
846
+ import type { User } from '../Entities/User'
847
+
848
+ export interface IUserRepository {
849
+ findById(id: string): Promise<User | null>
850
+ findByEmail(email: string): Promise<User | null>
851
+ save(user: User): Promise<void>
852
+ delete(id: string): Promise<void>
853
+ findAll(): Promise<User[]>
854
+ }
855
+ `;
856
+ }
857
+ generateDomainException() {
858
+ return `/**
859
+ * Domain Exception
860
+ *
861
+ * Base exception for domain-level errors.
862
+ */
863
+
864
+ export class DomainException extends Error {
865
+ constructor(message: string) {
866
+ super(message)
867
+ this.name = 'DomainException'
868
+ }
869
+ }
870
+
871
+ export class EntityNotFoundException extends DomainException {
872
+ constructor(entity: string, id: string) {
873
+ super(\`\${entity} with id \${id} not found\`)
874
+ this.name = 'EntityNotFoundException'
875
+ }
876
+ }
877
+
878
+ export class InvalidValueException extends DomainException {
879
+ constructor(message: string) {
880
+ super(message)
881
+ this.name = 'InvalidValueException'
882
+ }
883
+ }
884
+ `;
885
+ }
886
+ // ─────────────────────────────────────────────────────────────
887
+ // Application Layer
888
+ // ─────────────────────────────────────────────────────────────
889
+ generateCreateUserUseCase() {
890
+ return `/**
891
+ * Create User Use Case
892
+ *
893
+ * Application service for creating new users.
894
+ */
895
+
896
+ import { User } from '../../../Domain/Entities/User'
897
+ import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
898
+ import type { UserDTO } from '../../DTOs/UserDTO'
899
+
900
+ export interface CreateUserInput {
901
+ name: string
902
+ email: string
903
+ }
904
+
905
+ export interface CreateUserOutput {
906
+ user: UserDTO
907
+ }
908
+
909
+ export class CreateUserUseCase {
910
+ constructor(private userRepository: IUserRepository) {}
911
+
912
+ async execute(input: CreateUserInput): Promise<CreateUserOutput> {
913
+ // Check if email already exists
914
+ const existing = await this.userRepository.findByEmail(input.email)
915
+ if (existing) {
916
+ throw new Error('User with this email already exists')
917
+ }
918
+
919
+ // Create user entity
920
+ const user = User.create(
921
+ crypto.randomUUID(),
922
+ input.name,
923
+ input.email
924
+ )
925
+
926
+ // Persist
927
+ await this.userRepository.save(user)
928
+
929
+ // Return DTO
930
+ return {
931
+ user: {
932
+ id: user.id,
933
+ name: user.name,
934
+ email: user.email.value,
935
+ createdAt: user.createdAt.toISOString(),
936
+ },
937
+ }
938
+ }
939
+ }
940
+ `;
941
+ }
942
+ generateGetUserUseCase() {
943
+ return `/**
944
+ * Get User Use Case
945
+ */
946
+
947
+ import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
948
+ import { EntityNotFoundException } from '../../../Domain/Exceptions/DomainException'
949
+ import type { UserDTO } from '../../DTOs/UserDTO'
950
+
951
+ export interface GetUserInput {
952
+ id: string
953
+ }
954
+
955
+ export interface GetUserOutput {
956
+ user: UserDTO
957
+ }
958
+
959
+ export class GetUserUseCase {
960
+ constructor(private userRepository: IUserRepository) {}
961
+
962
+ async execute(input: GetUserInput): Promise<GetUserOutput> {
963
+ const user = await this.userRepository.findById(input.id)
964
+
965
+ if (!user) {
966
+ throw new EntityNotFoundException('User', input.id)
967
+ }
968
+
969
+ return {
970
+ user: {
971
+ id: user.id,
972
+ name: user.name,
973
+ email: user.email.value,
974
+ createdAt: user.createdAt.toISOString(),
975
+ },
976
+ }
977
+ }
978
+ }
979
+ `;
980
+ }
981
+ generateUserDTO() {
982
+ return `/**
983
+ * User Data Transfer Object
984
+ *
985
+ * Used for transferring user data across boundaries.
986
+ */
987
+
988
+ export interface UserDTO {
989
+ id: string
990
+ name: string
991
+ email: string
992
+ createdAt: string
993
+ updatedAt?: string
994
+ }
995
+
996
+ export interface CreateUserDTO {
997
+ name: string
998
+ email: string
999
+ }
1000
+
1001
+ export interface UpdateUserDTO {
1002
+ name?: string
1003
+ email?: string
1004
+ }
1005
+ `;
1006
+ }
1007
+ generateMailServiceInterface() {
1008
+ return `/**
1009
+ * Mail Service Interface
1010
+ *
1011
+ * Contract for email sending functionality.
1012
+ */
1013
+
1014
+ export interface MailMessage {
1015
+ to: string
1016
+ subject: string
1017
+ body: string
1018
+ html?: string
1019
+ }
1020
+
1021
+ export interface IMailService {
1022
+ send(message: MailMessage): Promise<void>
1023
+ }
1024
+ `;
1025
+ }
1026
+ // ─────────────────────────────────────────────────────────────
1027
+ // Infrastructure Layer
1028
+ // ─────────────────────────────────────────────────────────────
1029
+ generateUserRepository() {
1030
+ return `/**
1031
+ * User Repository Implementation
1032
+ *
1033
+ * Concrete implementation of IUserRepository.
1034
+ */
1035
+
1036
+ import type { User } from '../../../Domain/Entities/User'
1037
+ import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
1038
+
1039
+ // In-memory store for demo purposes
1040
+ const users = new Map<string, User>()
1041
+
1042
+ export class UserRepository implements IUserRepository {
1043
+ async findById(id: string): Promise<User | null> {
1044
+ return users.get(id) ?? null
1045
+ }
1046
+
1047
+ async findByEmail(email: string): Promise<User | null> {
1048
+ for (const user of users.values()) {
1049
+ if (user.email.value === email) {
1050
+ return user
1051
+ }
1052
+ }
1053
+ return null
1054
+ }
1055
+
1056
+ async save(user: User): Promise<void> {
1057
+ users.set(user.id, user)
1058
+ }
1059
+
1060
+ async delete(id: string): Promise<void> {
1061
+ users.delete(id)
1062
+ }
1063
+
1064
+ async findAll(): Promise<User[]> {
1065
+ return Array.from(users.values())
1066
+ }
1067
+ }
1068
+ `;
1069
+ }
1070
+ generateMailService() {
1071
+ return `/**
1072
+ * Mail Service Implementation
1073
+ */
1074
+
1075
+ import type { IMailService, MailMessage } from '../../Application/Interfaces/IMailService'
1076
+
1077
+ export class MailService implements IMailService {
1078
+ async send(message: MailMessage): Promise<void> {
1079
+ // TODO: Implement actual email sending
1080
+ console.log(\`[Mail] Sending to \${message.to}: \${message.subject}\`)
1081
+ }
1082
+ }
1083
+ `;
1084
+ }
1085
+ generateAppServiceProvider(context) {
1086
+ return `/**
1087
+ * App Service Provider
1088
+ */
1089
+
1090
+ import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
1091
+
1092
+ export class AppServiceProvider extends ServiceProvider {
1093
+ register(_container: Container): void {
1094
+ // Register application services
1095
+ }
1096
+
1097
+ boot(_core: PlanetCore): void {
1098
+ console.log('${context.name} (Clean Architecture) booted!')
1099
+ }
1100
+ }
1101
+ `;
1102
+ }
1103
+ generateRepositoryServiceProvider() {
1104
+ return `/**
1105
+ * Repository Service Provider
1106
+ *
1107
+ * Binds repository interfaces to implementations.
1108
+ */
1109
+
1110
+ import { ServiceProvider, type Container } from 'gravito-core'
1111
+ import { UserRepository } from '../Persistence/Repositories/UserRepository'
1112
+ import { MailService } from '../ExternalServices/MailService'
1113
+
1114
+ export class RepositoryServiceProvider extends ServiceProvider {
1115
+ register(container: Container): void {
1116
+ // Bind repositories
1117
+ container.singleton('userRepository', () => new UserRepository())
1118
+
1119
+ // Bind external services
1120
+ container.singleton('mailService', () => new MailService())
1121
+ }
1122
+ }
1123
+ `;
1124
+ }
1125
+ // ─────────────────────────────────────────────────────────────
1126
+ // Interface Layer
1127
+ // ─────────────────────────────────────────────────────────────
1128
+ generateUserController() {
1129
+ return `/**
1130
+ * User Controller
1131
+ */
1132
+
1133
+ import type { GravitoContext } from 'gravito-core'
1134
+ import { CreateUserUseCase } from '../../../Application/UseCases/User/CreateUser'
1135
+ import { GetUserUseCase } from '../../../Application/UseCases/User/GetUser'
1136
+ import { UserRepository } from '../../../Infrastructure/Persistence/Repositories/UserRepository'
1137
+
1138
+ const userRepository = new UserRepository()
1139
+
1140
+ export class UserController {
1141
+ async create(c: GravitoContext) {
1142
+ const body = await c.req.json() as { name: string; email: string }
1143
+ const useCase = new CreateUserUseCase(userRepository)
1144
+
1145
+ const result = await useCase.execute({
1146
+ name: body.name,
1147
+ email: body.email,
1148
+ })
1149
+
1150
+ return c.json({ success: true, data: result.user }, 201)
1151
+ }
1152
+
1153
+ async show(c: GravitoContext) {
1154
+ const id = c.req.param('id') ?? ''
1155
+ const useCase = new GetUserUseCase(userRepository)
1156
+
1157
+ const result = await useCase.execute({ id })
1158
+
1159
+ return c.json({ success: true, data: result.user })
1160
+ }
1161
+ }
1162
+ `;
1163
+ }
1164
+ generateApiRoutes() {
1165
+ return `/**
1166
+ * API Routes
1167
+ */
1168
+
1169
+ import { UserController } from '../Controllers/UserController'
1170
+
1171
+ export function registerApiRoutes(router: any): void {
1172
+ const users = new UserController()
1173
+
1174
+ router.post('/api/users', (c: any) => users.create(c))
1175
+ router.get('/api/users/:id', (c: any) => users.show(c))
1176
+ }
1177
+ `;
1178
+ }
1179
+ generateUserPresenter() {
1180
+ return `/**
1181
+ * User Presenter
1182
+ *
1183
+ * Formats user data for different output formats.
1184
+ */
1185
+
1186
+ import type { UserDTO } from '../../Application/DTOs/UserDTO'
1187
+
1188
+ export class UserPresenter {
1189
+ static toJson(user: UserDTO): object {
1190
+ return {
1191
+ id: user.id,
1192
+ name: user.name,
1193
+ email: user.email,
1194
+ created_at: user.createdAt,
1195
+ }
1196
+ }
1197
+
1198
+ static toList(users: UserDTO[]): object {
1199
+ return {
1200
+ data: users.map((u) => this.toJson(u)),
1201
+ total: users.length,
1202
+ }
1203
+ }
1204
+ }
1205
+ `;
1206
+ }
1207
+ generateBootstrap(context) {
1208
+ return `/**
1209
+ * Application Bootstrap
1210
+ */
1211
+
1212
+ import { PlanetCore } from 'gravito-core'
1213
+ import { AppServiceProvider } from './Infrastructure/Providers/AppServiceProvider'
1214
+ import { RepositoryServiceProvider } from './Infrastructure/Providers/RepositoryServiceProvider'
1215
+ import { registerApiRoutes } from './Interface/Http/Routes/api'
1216
+
1217
+ const core = new PlanetCore({
1218
+ config: { APP_NAME: '${context.name}' },
1219
+ })
1220
+
1221
+ core.register(new RepositoryServiceProvider())
1222
+ core.register(new AppServiceProvider())
1223
+
1224
+ await core.bootstrap()
1225
+
1226
+ // Register routes
1227
+ registerApiRoutes(core.router)
1228
+
1229
+ export default core.liftoff()
1230
+ `;
1231
+ }
1232
+ generateArchitectureDoc(context) {
1233
+ return `# ${context.name} - Clean Architecture Guide
1234
+
1235
+ ## Overview
1236
+
1237
+ This project follows **Clean Architecture** (by Robert C. Martin).
1238
+ The key principle is the **Dependency Rule**: dependencies point inward.
1239
+
1240
+ ## Layer Structure
1241
+
1242
+ \`\`\`
1243
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1244
+ \u2502 Interface Layer \u2502
1245
+ \u2502 (Controllers, Presenters) \u2502
1246
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1247
+ \u2502 Infrastructure Layer \u2502
1248
+ \u2502 (Repositories, External Services) \u2502
1249
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1250
+ \u2502 Application Layer \u2502
1251
+ \u2502 (Use Cases, DTOs) \u2502
1252
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1253
+ \u2502 Domain Layer \u2502
1254
+ \u2502 (Entities, Value Objects, Interfaces) \u2502
1255
+ \u2502 \u2B50 PURE \u2B50 \u2502
1256
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
1257
+ \`\`\`
1258
+
1259
+ ## Dependency Rule
1260
+
1261
+ > Inner layers know nothing about outer layers.
1262
+
1263
+ - **Domain**: No dependencies (pure TypeScript only)
1264
+ - **Application**: Depends only on Domain
1265
+ - **Infrastructure**: Implements Domain interfaces
1266
+ - **Interface**: Calls Application use cases
1267
+
1268
+ ## Directory Structure
1269
+
1270
+ \`\`\`
1271
+ src/
1272
+ \u251C\u2500\u2500 Domain/ # Enterprise Business Rules
1273
+ \u2502 \u251C\u2500\u2500 Entities/ # Business objects with identity
1274
+ \u2502 \u251C\u2500\u2500 ValueObjects/ # Immutable value types
1275
+ \u2502 \u251C\u2500\u2500 Interfaces/ # Repository contracts
1276
+ \u2502 \u2514\u2500\u2500 Exceptions/ # Domain errors
1277
+ \u251C\u2500\u2500 Application/ # Application Business Rules
1278
+ \u2502 \u251C\u2500\u2500 UseCases/ # Business operations
1279
+ \u2502 \u251C\u2500\u2500 DTOs/ # Data transfer objects
1280
+ \u2502 \u2514\u2500\u2500 Interfaces/ # Service contracts
1281
+ \u251C\u2500\u2500 Infrastructure/ # Frameworks & Drivers
1282
+ \u2502 \u251C\u2500\u2500 Persistence/ # Database implementations
1283
+ \u2502 \u251C\u2500\u2500 ExternalServices/ # Third-party integrations
1284
+ \u2502 \u2514\u2500\u2500 Providers/ # Service providers
1285
+ \u2514\u2500\u2500 Interface/ # Interface Adapters
1286
+ \u251C\u2500\u2500 Http/ # HTTP controllers & routes
1287
+ \u2514\u2500\u2500 Presenters/ # Output formatters
1288
+ \`\`\`
1289
+
1290
+ ## Key Benefits
1291
+
1292
+ 1. **Testable**: Domain and Use Cases can be tested without frameworks
1293
+ 2. **Independent**: Business logic is framework-agnostic
1294
+ 3. **Maintainable**: Changes in outer layers don't affect inner layers
1295
+ 4. **Flexible**: Easy to swap databases or frameworks
1296
+
1297
+ Created with \u2764\uFE0F using Gravito Framework
1298
+ `;
1299
+ }
1300
+ };
1301
+
1302
+ // src/generators/DddGenerator.ts
1303
+ var DddGenerator = class extends BaseGenerator {
1304
+ get architectureType() {
1305
+ return "ddd";
1306
+ }
1307
+ get displayName() {
1308
+ return "Domain-Driven Design (DDD)";
1309
+ }
1310
+ get description() {
1311
+ return "Full DDD with Bounded Contexts, Aggregates, and Event-Driven patterns";
1312
+ }
1313
+ getDirectoryStructure(context) {
1314
+ return [
1315
+ {
1316
+ type: "directory",
1317
+ name: "config",
1318
+ children: [
1319
+ { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
1320
+ { type: "file", name: "database.ts", content: this.generateDatabaseConfig() },
1321
+ { type: "file", name: "modules.ts", content: this.generateModulesConfig() },
1322
+ { type: "file", name: "cache.ts", content: this.generateCacheConfig() },
1323
+ { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
1324
+ ]
1325
+ },
1326
+ {
1327
+ type: "directory",
1328
+ name: "src",
1329
+ children: [
1330
+ // Bootstrap - Application startup and configuration
1331
+ this.generateBootstrapDirectory(context),
1332
+ // Shared - Cross-module shared components
1333
+ this.generateShared(),
1334
+ // Modules - Bounded Contexts
1335
+ {
1336
+ type: "directory",
1337
+ name: "Modules",
1338
+ children: [
1339
+ this.generateModule("Ordering", context),
1340
+ this.generateModule("Catalog", context)
1341
+ ]
1342
+ },
1343
+ { type: "file", name: "main.ts", content: this.generateMainEntry(context) }
1344
+ ]
1345
+ },
1346
+ {
1347
+ type: "directory",
1348
+ name: "tests",
1349
+ children: [
1350
+ {
1351
+ type: "directory",
1352
+ name: "Modules",
1353
+ children: [
1354
+ {
1355
+ type: "directory",
1356
+ name: "Ordering",
1357
+ children: [
1358
+ {
1359
+ type: "directory",
1360
+ name: "Unit",
1361
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
1362
+ },
1363
+ {
1364
+ type: "directory",
1365
+ name: "Integration",
1366
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
1367
+ }
1368
+ ]
1369
+ },
1370
+ {
1371
+ type: "directory",
1372
+ name: "Catalog",
1373
+ children: [
1374
+ {
1375
+ type: "directory",
1376
+ name: "Unit",
1377
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
1378
+ }
1379
+ ]
1380
+ }
1381
+ ]
1382
+ },
1383
+ {
1384
+ type: "directory",
1385
+ name: "Shared",
1386
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
1387
+ }
1388
+ ]
1389
+ }
1390
+ ];
1391
+ }
1392
+ // ─────────────────────────────────────────────────────────────
1393
+ // Bootstrap Directory
1394
+ // ─────────────────────────────────────────────────────────────
1395
+ generateBootstrapDirectory(context) {
1396
+ return {
1397
+ type: "directory",
1398
+ name: "Bootstrap",
1399
+ children: [
1400
+ { type: "file", name: "app.ts", content: this.generateBootstrapApp(context) },
1401
+ { type: "file", name: "providers.ts", content: this.generateProvidersRegistry(context) },
1402
+ { type: "file", name: "events.ts", content: this.generateEventsRegistry() },
1403
+ { type: "file", name: "routes.ts", content: this.generateRoutesRegistry(context) }
1404
+ ]
1405
+ };
1406
+ }
1407
+ // ─────────────────────────────────────────────────────────────
1408
+ // Shared Directory (replaces SharedKernel with user's structure)
1409
+ // ─────────────────────────────────────────────────────────────
1410
+ generateShared() {
1411
+ return {
1412
+ type: "directory",
1413
+ name: "Shared",
1414
+ children: [
1415
+ {
1416
+ type: "directory",
1417
+ name: "Domain",
1418
+ children: [
1419
+ {
1420
+ type: "directory",
1421
+ name: "ValueObjects",
1422
+ children: [
1423
+ { type: "file", name: "Id.ts", content: this.generateIdValueObject() },
1424
+ { type: "file", name: "Money.ts", content: this.generateMoneyValueObject() },
1425
+ { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
1426
+ ]
1427
+ },
1428
+ {
1429
+ type: "directory",
1430
+ name: "Events",
1431
+ children: [
1432
+ { type: "file", name: "DomainEvent.ts", content: this.generateDomainEvent() }
1433
+ ]
1434
+ },
1435
+ {
1436
+ type: "directory",
1437
+ name: "Primitives",
1438
+ children: [
1439
+ { type: "file", name: "AggregateRoot.ts", content: this.generateAggregateRoot() },
1440
+ { type: "file", name: "Entity.ts", content: this.generateEntity() },
1441
+ { type: "file", name: "ValueObject.ts", content: this.generateValueObject() }
1442
+ ]
1443
+ }
1444
+ ]
1445
+ },
1446
+ {
1447
+ type: "directory",
1448
+ name: "Infrastructure",
1449
+ children: [
1450
+ {
1451
+ type: "directory",
1452
+ name: "EventBus",
1453
+ children: [
1454
+ {
1455
+ type: "file",
1456
+ name: "EventDispatcher.ts",
1457
+ content: this.generateEventDispatcher()
1458
+ }
1459
+ ]
1460
+ }
1461
+ ]
1462
+ },
1463
+ {
1464
+ type: "directory",
1465
+ name: "Exceptions",
1466
+ children: [
1467
+ { type: "file", name: "Handler.ts", content: this.generateExceptionHandler() }
1468
+ ]
1469
+ }
1470
+ ]
1471
+ };
1472
+ }
1473
+ // ─────────────────────────────────────────────────────────────
1474
+ // Module Generator (replaces Bounded Context)
1475
+ // ─────────────────────────────────────────────────────────────
1476
+ generateModule(name, context) {
1477
+ return {
1478
+ type: "directory",
1479
+ name,
1480
+ children: [
1481
+ // Domain Layer
1482
+ {
1483
+ type: "directory",
1484
+ name: "Domain",
1485
+ children: [
1486
+ {
1487
+ type: "directory",
1488
+ name: "Aggregates",
1489
+ children: [
1490
+ {
1491
+ type: "directory",
1492
+ name,
1493
+ children: [
1494
+ { type: "file", name: `${name}.ts`, content: this.generateAggregate(name) },
1495
+ {
1496
+ type: "file",
1497
+ name: `${name}Status.ts`,
1498
+ content: this.generateAggregateStatus(name)
1499
+ }
1500
+ ]
1501
+ }
1502
+ ]
1503
+ },
1504
+ {
1505
+ type: "directory",
1506
+ name: "Events",
1507
+ children: [
1508
+ {
1509
+ type: "file",
1510
+ name: `${name}Created.ts`,
1511
+ content: this.generateCreatedEvent(name)
1512
+ }
1513
+ ]
1514
+ },
1515
+ {
1516
+ type: "directory",
1517
+ name: "Repositories",
1518
+ children: [
1519
+ {
1520
+ type: "file",
1521
+ name: `I${name}Repository.ts`,
1522
+ content: this.generateRepositoryInterface(name)
1523
+ }
1524
+ ]
1525
+ },
1526
+ {
1527
+ type: "directory",
1528
+ name: "Services",
1529
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
1530
+ }
1531
+ ]
1532
+ },
1533
+ // Application Layer
1534
+ {
1535
+ type: "directory",
1536
+ name: "Application",
1537
+ children: [
1538
+ {
1539
+ type: "directory",
1540
+ name: "Commands",
1541
+ children: [
1542
+ {
1543
+ type: "directory",
1544
+ name: `Create${name}`,
1545
+ children: [
1546
+ {
1547
+ type: "file",
1548
+ name: `Create${name}Command.ts`,
1549
+ content: this.generateCommand(name)
1550
+ },
1551
+ {
1552
+ type: "file",
1553
+ name: `Create${name}Handler.ts`,
1554
+ content: this.generateCommandHandler(name)
1555
+ }
1556
+ ]
1557
+ }
1558
+ ]
1559
+ },
1560
+ {
1561
+ type: "directory",
1562
+ name: "Queries",
1563
+ children: [
1564
+ {
1565
+ type: "directory",
1566
+ name: `Get${name}ById`,
1567
+ children: [
1568
+ {
1569
+ type: "file",
1570
+ name: `Get${name}ByIdQuery.ts`,
1571
+ content: this.generateQuery(name)
1572
+ },
1573
+ {
1574
+ type: "file",
1575
+ name: `Get${name}ByIdHandler.ts`,
1576
+ content: this.generateQueryHandler(name)
1577
+ }
1578
+ ]
1579
+ }
1580
+ ]
1581
+ },
1582
+ {
1583
+ type: "directory",
1584
+ name: "DTOs",
1585
+ children: [{ type: "file", name: `${name}DTO.ts`, content: this.generateDTO(name) }]
1586
+ }
1587
+ ]
1588
+ },
1589
+ // Infrastructure Layer
1590
+ {
1591
+ type: "directory",
1592
+ name: "Infrastructure",
1593
+ children: [
1594
+ {
1595
+ type: "directory",
1596
+ name: "Persistence",
1597
+ children: [
1598
+ {
1599
+ type: "file",
1600
+ name: `${name}Repository.ts`,
1601
+ content: this.generateRepository(name)
1602
+ }
1603
+ ]
1604
+ },
1605
+ {
1606
+ type: "directory",
1607
+ name: "Providers",
1608
+ children: [
1609
+ {
1610
+ type: "file",
1611
+ name: `${name}ServiceProvider.ts`,
1612
+ content: this.generateModuleServiceProvider(name, context)
1613
+ }
1614
+ ]
1615
+ }
1616
+ ]
1617
+ }
1618
+ ]
1619
+ };
1620
+ }
1621
+ // ─────────────────────────────────────────────────────────────
1622
+ // Bootstrap File Generators
1623
+ // ─────────────────────────────────────────────────────────────
1624
+ generateBootstrapApp(context) {
1625
+ return `/**
1626
+ * Application Bootstrap
1627
+ *
1628
+ * Central configuration and initialization of the application.
1629
+ */
1630
+
1631
+ import { PlanetCore } from 'gravito-core'
1632
+ import { registerProviders } from './providers'
1633
+ import { registerRoutes } from './routes'
1634
+
1635
+ export async function createApp(): Promise<PlanetCore> {
1636
+ const core = new PlanetCore({
1637
+ config: {
1638
+ APP_NAME: '${context.name}',
1639
+ },
1640
+ })
1641
+
1642
+ // Register all service providers
1643
+ await registerProviders(core)
1644
+
1645
+ // Bootstrap the application
1646
+ await core.bootstrap()
1647
+
1648
+ // Register routes
1649
+ registerRoutes(core.router)
1650
+
1651
+ return core
1652
+ }
1653
+ `;
1654
+ }
1655
+ generateProvidersRegistry(_context) {
1656
+ return `/**
1657
+ * Service Providers Registry
1658
+ *
1659
+ * Register all module service providers here.
1660
+ */
1661
+
1662
+ import type { PlanetCore } from 'gravito-core'
1663
+ import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
1664
+ import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
1665
+
1666
+ export async function registerProviders(core: PlanetCore): Promise<void> {
1667
+ // Register module providers
1668
+ core.register(new OrderingServiceProvider())
1669
+ core.register(new CatalogServiceProvider())
1670
+
1671
+ // Add more providers as needed
1672
+ }
1673
+ `;
1674
+ }
1675
+ generateEventsRegistry() {
1676
+ return `/**
1677
+ * Domain Events Registry
1678
+ *
1679
+ * Register all domain event handlers here.
1680
+ */
1681
+
1682
+ import { EventDispatcher } from '../Shared/Infrastructure/EventBus/EventDispatcher'
1683
+
1684
+ export function registerEvents(dispatcher: EventDispatcher): void {
1685
+ // Register event handlers
1686
+ // dispatcher.subscribe('ordering.created', async (event) => { ... })
1687
+ }
1688
+ `;
1689
+ }
1690
+ generateRoutesRegistry(_context) {
1691
+ return `/**
1692
+ * Routes Registry
1693
+ *
1694
+ * Register all module routes here.
1695
+ */
1696
+
1697
+ export function registerRoutes(router: any): void {
1698
+ // Health check
1699
+ router.get('/health', (c: any) => c.json({ status: 'healthy' }))
1700
+
1701
+ // Ordering module
1702
+ router.get('/api/orders', (c: any) => c.json({ message: 'Order list' }))
1703
+ router.post('/api/orders', (c: any) => c.json({ message: 'Order created' }, 201))
1704
+
1705
+ // Catalog module
1706
+ router.get('/api/products', (c: any) => c.json({ message: 'Product list' }))
1707
+ }
1708
+ `;
1709
+ }
1710
+ generateMainEntry(_context) {
1711
+ return `/**
1712
+ * Application Entry Point
1713
+ *
1714
+ * Start the HTTP server.
1715
+ */
1716
+
1717
+ import { createApp } from './Bootstrap/app'
1718
+
1719
+ const app = await createApp()
1720
+
1721
+ export default app.liftoff()
1722
+ `;
1723
+ }
1724
+ generateModulesConfig() {
1725
+ return `/**
1726
+ * Modules Configuration
1727
+ *
1728
+ * Define module boundaries and their dependencies.
1729
+ */
1730
+
1731
+ export default {
1732
+ modules: {
1733
+ ordering: {
1734
+ name: 'Ordering',
1735
+ description: 'Order management module',
1736
+ prefix: '/api/orders',
1737
+ },
1738
+ catalog: {
1739
+ name: 'Catalog',
1740
+ description: 'Product catalog module',
1741
+ prefix: '/api/products',
1742
+ },
1743
+ },
1744
+
1745
+ // Module dependencies
1746
+ dependencies: {
1747
+ ordering: ['catalog'], // Ordering depends on Catalog
1748
+ },
1749
+ }
1750
+ `;
1751
+ }
1752
+ generateModuleServiceProvider(name, _context) {
1753
+ return `/**
1754
+ * ${name} Service Provider
1755
+ */
1756
+
1757
+ import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
1758
+ import { ${name}Repository } from '../Persistence/${name}Repository'
1759
+
1760
+ export class ${name}ServiceProvider extends ServiceProvider {
1761
+ register(container: Container): void {
1762
+ container.singleton('${name.toLowerCase()}Repository', () => new ${name}Repository())
1763
+ }
1764
+
1765
+ boot(_core: PlanetCore): void {
1766
+ console.log('[${name}] Module loaded')
1767
+ }
1768
+ }
1769
+ `;
1770
+ }
1771
+ /**
1772
+ * Override package.json for DDD architecture (uses main.ts instead of bootstrap.ts)
1773
+ */
1774
+ generatePackageJson(context) {
1775
+ const pkg = {
1776
+ name: context.nameKebabCase,
1777
+ version: "0.1.0",
1778
+ type: "module",
1779
+ scripts: {
1780
+ dev: "bun run --watch src/main.ts",
1781
+ build: "bun build ./src/main.ts --outdir ./dist --target bun",
1782
+ start: "bun run dist/main.js",
1783
+ test: "bun test",
1784
+ typecheck: "tsc --noEmit"
1785
+ },
1786
+ dependencies: {
1787
+ "gravito-core": "^1.0.0-beta.5"
1788
+ },
1789
+ devDependencies: {
1790
+ "@types/bun": "latest",
1791
+ typescript: "^5.0.0"
1792
+ }
1793
+ };
1794
+ return JSON.stringify(pkg, null, 2);
1795
+ }
1796
+ // ─────────────────────────────────────────────────────────────
1797
+ // Config Generators
1798
+ // ─────────────────────────────────────────────────────────────
1799
+ generateAppConfig(context) {
1800
+ return `export default {
1801
+ name: process.env.APP_NAME ?? '${context.name}',
1802
+ env: process.env.APP_ENV ?? 'development',
1803
+ debug: process.env.APP_DEBUG === 'true',
1804
+ url: process.env.APP_URL ?? 'http://localhost:3000',
1805
+ }
1806
+ `;
1807
+ }
1808
+ generateDatabaseConfig() {
1809
+ return `export default {
1810
+ default: process.env.DB_CONNECTION ?? 'sqlite',
1811
+ connections: {
1812
+ sqlite: { driver: 'sqlite', database: 'database/database.sqlite' },
1813
+ },
1814
+ }
1815
+ `;
1816
+ }
1817
+ generateCacheConfig() {
1818
+ return `export default {
1819
+ default: process.env.CACHE_DRIVER ?? 'memory',
1820
+ stores: { memory: { driver: 'memory' } },
1821
+ }
1822
+ `;
1823
+ }
1824
+ generateLoggingConfig() {
1825
+ return `export default {
1826
+ default: 'console',
1827
+ channels: { console: { driver: 'console', level: 'debug' } },
1828
+ }
1829
+ `;
1830
+ }
1831
+ // ─────────────────────────────────────────────────────────────
1832
+ // Shared Kernel Files
1833
+ // ─────────────────────────────────────────────────────────────
1834
+ generateIdValueObject() {
1835
+ return `/**
1836
+ * ID Value Object
1837
+ *
1838
+ * Shared identifier across all contexts.
1839
+ */
1840
+
1841
+ export class Id {
1842
+ private readonly _value: string
1843
+
1844
+ private constructor(value: string) {
1845
+ this._value = value
1846
+ }
1847
+
1848
+ static create(): Id {
1849
+ return new Id(crypto.randomUUID())
1850
+ }
1851
+
1852
+ static from(value: string): Id {
1853
+ if (!value) throw new Error('Id cannot be empty')
1854
+ return new Id(value)
1855
+ }
1856
+
1857
+ get value(): string {
1858
+ return this._value
1859
+ }
1860
+
1861
+ equals(other: Id): boolean {
1862
+ return this._value === other._value
1863
+ }
1864
+
1865
+ toString(): string {
1866
+ return this._value
1867
+ }
1868
+ }
1869
+ `;
1870
+ }
1871
+ generateMoneyValueObject() {
1872
+ return `/**
1873
+ * Money Value Object
1874
+ */
1875
+
1876
+ export class Money {
1877
+ constructor(
1878
+ public readonly amount: number,
1879
+ public readonly currency: string = 'USD'
1880
+ ) {
1881
+ if (amount < 0) throw new Error('Amount cannot be negative')
1882
+ }
1883
+
1884
+ add(other: Money): Money {
1885
+ this.assertSameCurrency(other)
1886
+ return new Money(this.amount + other.amount, this.currency)
1887
+ }
1888
+
1889
+ subtract(other: Money): Money {
1890
+ this.assertSameCurrency(other)
1891
+ return new Money(this.amount - other.amount, this.currency)
1892
+ }
1893
+
1894
+ private assertSameCurrency(other: Money): void {
1895
+ if (this.currency !== other.currency) {
1896
+ throw new Error('Cannot operate on different currencies')
1897
+ }
1898
+ }
1899
+
1900
+ equals(other: Money): boolean {
1901
+ return this.amount === other.amount && this.currency === other.currency
1902
+ }
1903
+ }
1904
+ `;
1905
+ }
1906
+ generateEmailValueObject() {
1907
+ return `/**
1908
+ * Email Value Object
1909
+ */
1910
+
1911
+ export class Email {
1912
+ private readonly _value: string
1913
+
1914
+ private constructor(value: string) {
1915
+ this._value = value.toLowerCase().trim()
1916
+ }
1917
+
1918
+ static create(email: string): Email {
1919
+ if (!Email.isValid(email)) {
1920
+ throw new Error(\`Invalid email: \${email}\`)
1921
+ }
1922
+ return new Email(email)
1923
+ }
1924
+
1925
+ static isValid(email: string): boolean {
1926
+ return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)
1927
+ }
1928
+
1929
+ get value(): string {
1930
+ return this._value
1931
+ }
1932
+ }
1933
+ `;
1934
+ }
1935
+ generateDomainEvent() {
1936
+ return `/**
1937
+ * Domain Event Base
1938
+ */
1939
+
1940
+ export abstract class DomainEvent {
1941
+ readonly occurredOn: Date
1942
+ readonly eventId: string
1943
+
1944
+ constructor() {
1945
+ this.occurredOn = new Date()
1946
+ this.eventId = crypto.randomUUID()
1947
+ }
1948
+
1949
+ abstract get eventName(): string
1950
+ abstract get aggregateId(): string
1951
+ }
1952
+ `;
1953
+ }
1954
+ generateAggregateRoot() {
1955
+ return `/**
1956
+ * Aggregate Root Base
1957
+ */
1958
+
1959
+ import type { DomainEvent } from '../Events/DomainEvent'
1960
+ import type { Id } from '../ValueObjects/Id'
1961
+
1962
+ export abstract class AggregateRoot<T extends Id = Id> {
1963
+ private _domainEvents: DomainEvent[] = []
1964
+
1965
+ protected constructor(protected readonly _id: T) {}
1966
+
1967
+ get id(): T {
1968
+ return this._id
1969
+ }
1970
+
1971
+ get domainEvents(): DomainEvent[] {
1972
+ return [...this._domainEvents]
1973
+ }
1974
+
1975
+ protected addDomainEvent(event: DomainEvent): void {
1976
+ this._domainEvents.push(event)
1977
+ }
1978
+
1979
+ clearDomainEvents(): DomainEvent[] {
1980
+ const events = [...this._domainEvents]
1981
+ this._domainEvents = []
1982
+ return events
1983
+ }
1984
+ }
1985
+ `;
1986
+ }
1987
+ generateEntity() {
1988
+ return `/**
1989
+ * Entity Base
1990
+ */
1991
+
1992
+ import type { Id } from '../ValueObjects/Id'
1993
+
1994
+ export abstract class Entity<T extends Id = Id> {
1995
+ protected constructor(protected readonly _id: T) {}
1996
+
1997
+ get id(): T {
1998
+ return this._id
1999
+ }
2000
+
2001
+ equals(other: Entity<T>): boolean {
2002
+ return this._id.equals(other._id)
2003
+ }
2004
+ }
2005
+ `;
2006
+ }
2007
+ generateValueObject() {
2008
+ return `/**
2009
+ * Value Object Base
2010
+ */
2011
+
2012
+ export abstract class ValueObject<T> {
2013
+ protected readonly props: T
2014
+
2015
+ constructor(props: T) {
2016
+ this.props = Object.freeze(props)
2017
+ }
2018
+
2019
+ equals(other: ValueObject<T>): boolean {
2020
+ return JSON.stringify(this.props) === JSON.stringify(other.props)
2021
+ }
2022
+ }
2023
+ `;
2024
+ }
2025
+ generateEventDispatcher() {
2026
+ return `/**
2027
+ * Event Dispatcher
2028
+ */
2029
+
2030
+ import type { DomainEvent } from '../../Domain/Events/DomainEvent'
2031
+
2032
+ type EventHandler = (event: DomainEvent) => void | Promise<void>
2033
+
2034
+ export class EventDispatcher {
2035
+ private handlers: Map<string, EventHandler[]> = new Map()
2036
+
2037
+ subscribe(eventName: string, handler: EventHandler): void {
2038
+ const handlers = this.handlers.get(eventName) ?? []
2039
+ handlers.push(handler)
2040
+ this.handlers.set(eventName, handlers)
2041
+ }
2042
+
2043
+ async dispatch(event: DomainEvent): Promise<void> {
2044
+ const handlers = this.handlers.get(event.eventName) ?? []
2045
+ for (const handler of handlers) {
2046
+ await handler(event)
2047
+ }
2048
+ }
2049
+
2050
+ async dispatchAll(events: DomainEvent[]): Promise<void> {
2051
+ for (const event of events) {
2052
+ await this.dispatch(event)
2053
+ }
2054
+ }
2055
+ }
2056
+ `;
2057
+ }
2058
+ // ─────────────────────────────────────────────────────────────
2059
+ // Bounded Context Templates
2060
+ // ─────────────────────────────────────────────────────────────
2061
+ generateAggregate(name) {
2062
+ return `/**
2063
+ * ${name} Aggregate Root
2064
+ */
2065
+
2066
+ import { AggregateRoot } from '../../../../../Shared/Domain/Primitives/AggregateRoot'
2067
+ import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2068
+ import { ${name}Created } from '../../Events/${name}Created'
2069
+ import { ${name}Status } from './${name}Status'
2070
+
2071
+ export interface ${name}Props {
2072
+ // Add properties here
2073
+ status: ${name}Status
2074
+ createdAt: Date
2075
+ }
2076
+
2077
+ export class ${name} extends AggregateRoot {
2078
+ private props: ${name}Props
2079
+
2080
+ private constructor(id: Id, props: ${name}Props) {
2081
+ super(id)
2082
+ this.props = props
2083
+ }
2084
+
2085
+ static create(id: Id): ${name} {
2086
+ const aggregate = new ${name}(id, {
2087
+ status: ${name}Status.PENDING,
2088
+ createdAt: new Date(),
2089
+ })
2090
+
2091
+ aggregate.addDomainEvent(new ${name}Created(id.value))
2092
+
2093
+ return aggregate
2094
+ }
2095
+
2096
+ get status(): ${name}Status {
2097
+ return this.props.status
2098
+ }
2099
+
2100
+ // Add domain methods here
2101
+ }
2102
+ `;
2103
+ }
2104
+ generateAggregateStatus(name) {
2105
+ return `/**
2106
+ * ${name} Status
2107
+ */
2108
+
2109
+ export enum ${name}Status {
2110
+ PENDING = 'pending',
2111
+ ACTIVE = 'active',
2112
+ COMPLETED = 'completed',
2113
+ CANCELLED = 'cancelled',
2114
+ }
2115
+ `;
2116
+ }
2117
+ generateCreatedEvent(name) {
2118
+ return `/**
2119
+ * ${name} Created Event
2120
+ */
2121
+
2122
+ import { DomainEvent } from '../../../../Shared/Domain/Events/DomainEvent'
2123
+
2124
+ export class ${name}Created extends DomainEvent {
2125
+ constructor(public readonly ${name.toLowerCase()}Id: string) {
2126
+ super()
2127
+ }
2128
+
2129
+ get eventName(): string {
2130
+ return '${name.toLowerCase()}.created'
2131
+ }
2132
+
2133
+ get aggregateId(): string {
2134
+ return this.${name.toLowerCase()}Id
2135
+ }
2136
+ }
2137
+ `;
2138
+ }
2139
+ generateRepositoryInterface(name) {
2140
+ return `/**
2141
+ * ${name} Repository Interface
2142
+ */
2143
+
2144
+ import type { ${name} } from '../Aggregates/${name}/${name}'
2145
+
2146
+ export interface I${name}Repository {
2147
+ findById(id: string): Promise<${name} | null>
2148
+ save(aggregate: ${name}): Promise<void>
2149
+ delete(id: string): Promise<void>
2150
+ }
2151
+ `;
2152
+ }
2153
+ generateCommand(name) {
2154
+ return `/**
2155
+ * Create ${name} Command
2156
+ */
2157
+
2158
+ export class Create${name}Command {
2159
+ constructor(
2160
+ // Add command properties
2161
+ public readonly id?: string
2162
+ ) {}
2163
+ }
2164
+ `;
2165
+ }
2166
+ generateCommandHandler(name) {
2167
+ return `/**
2168
+ * Create ${name} Handler
2169
+ */
2170
+
2171
+ import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
2172
+ import { ${name} } from '../../../Domain/Aggregates/${name}/${name}'
2173
+ import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2174
+ import type { Create${name}Command } from './Create${name}Command'
2175
+
2176
+ export class Create${name}Handler {
2177
+ constructor(private repository: I${name}Repository) {}
2178
+
2179
+ async handle(command: Create${name}Command): Promise<string> {
2180
+ const id = command.id ? Id.from(command.id) : Id.create()
2181
+ const aggregate = ${name}.create(id)
2182
+
2183
+ await this.repository.save(aggregate)
2184
+
2185
+ return id.value
2186
+ }
2187
+ }
2188
+ `;
2189
+ }
2190
+ generateQuery(name) {
2191
+ return `/**
2192
+ * Get ${name} By Id Query
2193
+ */
2194
+
2195
+ export class Get${name}ByIdQuery {
2196
+ constructor(public readonly id: string) {}
2197
+ }
2198
+ `;
2199
+ }
2200
+ generateQueryHandler(name) {
2201
+ return `/**
2202
+ * Get ${name} By Id Handler
2203
+ */
2204
+
2205
+ import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
2206
+ import type { ${name}DTO } from '../../DTOs/${name}DTO'
2207
+ import type { Get${name}ByIdQuery } from './Get${name}ByIdQuery'
2208
+
2209
+ export class Get${name}ByIdHandler {
2210
+ constructor(private repository: I${name}Repository) {}
2211
+
2212
+ async handle(query: Get${name}ByIdQuery): Promise<${name}DTO | null> {
2213
+ const aggregate = await this.repository.findById(query.id)
2214
+ if (!aggregate) return null
2215
+
2216
+ return {
2217
+ id: aggregate.id.value,
2218
+ status: aggregate.status,
2219
+ }
2220
+ }
2221
+ }
2222
+ `;
2223
+ }
2224
+ generateDTO(name) {
2225
+ return `/**
2226
+ * ${name} DTO
2227
+ */
2228
+
2229
+ import type { ${name}Status } from '../../Domain/Aggregates/${name}/${name}Status'
2230
+
2231
+ export interface ${name}DTO {
2232
+ id: string
2233
+ status: ${name}Status
2234
+ // Add more fields
2235
+ }
2236
+ `;
2237
+ }
2238
+ generateRepository(name) {
2239
+ return `/**
2240
+ * ${name} Repository Implementation
2241
+ */
2242
+
2243
+ import type { ${name} } from '../../Domain/Aggregates/${name}/${name}'
2244
+ import type { I${name}Repository } from '../../Domain/Repositories/I${name}Repository'
2245
+
2246
+ const store = new Map<string, ${name}>()
2247
+
2248
+ export class ${name}Repository implements I${name}Repository {
2249
+ async findById(id: string): Promise<${name} | null> {
2250
+ return store.get(id) ?? null
2251
+ }
2252
+
2253
+ async save(aggregate: ${name}): Promise<void> {
2254
+ store.set(aggregate.id.value, aggregate)
2255
+ }
2256
+
2257
+ async delete(id: string): Promise<void> {
2258
+ store.delete(id)
2259
+ }
2260
+ }
2261
+ `;
2262
+ }
2263
+ generateExceptionHandler() {
2264
+ return `/**
2265
+ * Exception Handler
2266
+ */
2267
+
2268
+ export function report(error: unknown): void {
2269
+ console.error('[Exception]', error)
2270
+ }
2271
+ `;
2272
+ }
2273
+ generateArchitectureDoc(context) {
2274
+ return `# ${context.name} - DDD Architecture Guide
2275
+
2276
+ ## Overview
2277
+
2278
+ This project follows **Domain-Driven Design (DDD)** with strategic and tactical patterns.
2279
+
2280
+ ## Bounded Contexts
2281
+
2282
+ \`\`\`
2283
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2284
+ \u2502 Ordering \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 Catalog \u2502
2285
+ \u2502 (Core Domain) \u2502 \u2502 (Supporting) \u2502
2286
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2287
+ \u2502
2288
+ \u25BC
2289
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2290
+ \u2502 SharedKernel \u2502
2291
+ \u2502 (Shared Types) \u2502
2292
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2293
+ \`\`\`
2294
+
2295
+ ## Context Structure
2296
+
2297
+ Each bounded context follows this structure:
2298
+
2299
+ \`\`\`
2300
+ Context/
2301
+ \u251C\u2500\u2500 Domain/ # Core business logic
2302
+ \u2502 \u251C\u2500\u2500 Aggregates/ # Aggregate roots + entities
2303
+ \u2502 \u251C\u2500\u2500 Events/ # Domain events
2304
+ \u2502 \u251C\u2500\u2500 Repositories/ # Repository interfaces
2305
+ \u2502 \u2514\u2500\u2500 Services/ # Domain services
2306
+ \u251C\u2500\u2500 Application/ # Use cases
2307
+ \u2502 \u251C\u2500\u2500 Commands/ # Write operations
2308
+ \u2502 \u251C\u2500\u2500 Queries/ # Read operations
2309
+ \u2502 \u251C\u2500\u2500 EventHandlers/ # Event reactions
2310
+ \u2502 \u2514\u2500\u2500 DTOs/ # Data transfer objects
2311
+ \u251C\u2500\u2500 Infrastructure/ # External concerns
2312
+ \u2502 \u251C\u2500\u2500 Persistence/ # Repository implementations
2313
+ \u2502 \u2514\u2500\u2500 Providers/ # DI configuration
2314
+ \u2514\u2500\u2500 UserInterface/ # Entry points
2315
+ \u251C\u2500\u2500 Http/ # REST controllers
2316
+ \u2514\u2500\u2500 Cli/ # CLI commands
2317
+ \`\`\`
2318
+
2319
+ ## SharedKernel
2320
+
2321
+ Contains types shared across contexts:
2322
+ - **ValueObjects**: Id, Money, Email
2323
+ - **Primitives**: AggregateRoot, Entity, ValueObject
2324
+ - **Events**: DomainEvent base class
2325
+ - **EventBus**: Event dispatcher
2326
+
2327
+ ## Key Patterns
2328
+
2329
+ 1. **Aggregates**: Consistency boundaries
2330
+ 2. **Domain Events**: Inter-context communication
2331
+ 3. **CQRS**: Separate read/write models
2332
+ 4. **Repository Pattern**: Persistence abstraction
2333
+
2334
+ Created with \u2764\uFE0F using Gravito Framework
2335
+ `;
2336
+ }
2337
+ };
2338
+
2339
+ // src/generators/EnterpriseMvcGenerator.ts
2340
+ var EnterpriseMvcGenerator = class extends BaseGenerator {
2341
+ get architectureType() {
2342
+ return "enterprise-mvc";
2343
+ }
2344
+ get displayName() {
2345
+ return "Enterprise MVC";
2346
+ }
2347
+ get description() {
2348
+ return "Laravel-inspired MVC with Services and Repositories for enterprise applications";
2349
+ }
2350
+ getDirectoryStructure(context) {
2351
+ return [
2352
+ {
2353
+ type: "directory",
2354
+ name: "config",
2355
+ children: [
2356
+ { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
2357
+ { type: "file", name: "database.ts", content: this.generateDatabaseConfig() },
2358
+ { type: "file", name: "auth.ts", content: this.generateAuthConfig() },
2359
+ { type: "file", name: "cache.ts", content: this.generateCacheConfig() },
2360
+ { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
2361
+ ]
2362
+ },
2363
+ {
2364
+ type: "directory",
2365
+ name: "src",
2366
+ children: [
2367
+ {
2368
+ type: "directory",
2369
+ name: "Http",
2370
+ children: [
2371
+ { type: "file", name: "Kernel.ts", content: this.generateHttpKernel() },
2372
+ {
2373
+ type: "directory",
2374
+ name: "Controllers",
2375
+ children: [
2376
+ { type: "file", name: "Controller.ts", content: this.generateBaseController() },
2377
+ {
2378
+ type: "file",
2379
+ name: "HomeController.ts",
2380
+ content: this.generateHomeController(context)
2381
+ }
2382
+ ]
2383
+ },
2384
+ {
2385
+ type: "directory",
2386
+ name: "Middleware",
2387
+ children: [
2388
+ { type: "file", name: "Authenticate.ts", content: this.generateAuthMiddleware() }
2389
+ ]
2390
+ }
2391
+ ]
2392
+ },
2393
+ {
2394
+ type: "directory",
2395
+ name: "Services",
2396
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2397
+ },
2398
+ {
2399
+ type: "directory",
2400
+ name: "Repositories",
2401
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2402
+ },
2403
+ {
2404
+ type: "directory",
2405
+ name: "Models",
2406
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2407
+ },
2408
+ {
2409
+ type: "directory",
2410
+ name: "Resources",
2411
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2412
+ },
2413
+ {
2414
+ type: "directory",
2415
+ name: "Providers",
2416
+ children: [
2417
+ {
2418
+ type: "file",
2419
+ name: "AppServiceProvider.ts",
2420
+ content: this.generateAppServiceProvider(context)
2421
+ },
2422
+ {
2423
+ type: "file",
2424
+ name: "RouteServiceProvider.ts",
2425
+ content: this.generateRouteServiceProvider(context)
2426
+ }
2427
+ ]
2428
+ },
2429
+ {
2430
+ type: "directory",
2431
+ name: "Exceptions",
2432
+ children: [
2433
+ { type: "file", name: "Handler.ts", content: this.generateExceptionHandler() }
2434
+ ]
2435
+ },
2436
+ { type: "file", name: "bootstrap.ts", content: this.generateBootstrap(context) },
2437
+ { type: "file", name: "routes.ts", content: this.generateRoutes(context) }
2438
+ ]
2439
+ },
2440
+ {
2441
+ type: "directory",
2442
+ name: "tests",
2443
+ children: [
2444
+ {
2445
+ type: "directory",
2446
+ name: "Unit",
2447
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2448
+ },
2449
+ {
2450
+ type: "directory",
2451
+ name: "Feature",
2452
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2453
+ }
2454
+ ]
2455
+ },
2456
+ {
2457
+ type: "directory",
2458
+ name: "database",
2459
+ children: [
2460
+ {
2461
+ type: "directory",
2462
+ name: "migrations",
2463
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2464
+ },
2465
+ {
2466
+ type: "directory",
2467
+ name: "seeders",
2468
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
2469
+ }
2470
+ ]
2471
+ }
2472
+ ];
2473
+ }
2474
+ // ─────────────────────────────────────────────────────────────
2475
+ // Config Generators
2476
+ // ─────────────────────────────────────────────────────────────
2477
+ generateAppConfig(context) {
2478
+ return `/**
2479
+ * Application Configuration
2480
+ */
2481
+ export default {
2482
+ /**
2483
+ * Application name
2484
+ */
2485
+ name: process.env.APP_NAME ?? '${context.name}',
2486
+
2487
+ /**
2488
+ * Application environment
2489
+ */
2490
+ env: process.env.APP_ENV ?? 'development',
2491
+
2492
+ /**
2493
+ * Debug mode
2494
+ */
2495
+ debug: process.env.APP_DEBUG === 'true',
2496
+
2497
+ /**
2498
+ * Application URL
2499
+ */
2500
+ url: process.env.APP_URL ?? 'http://localhost:3000',
2501
+
2502
+ /**
2503
+ * Timezone
2504
+ */
2505
+ timezone: 'UTC',
2506
+
2507
+ /**
2508
+ * Locale
2509
+ */
2510
+ locale: 'en',
2511
+
2512
+ /**
2513
+ * Fallback locale
2514
+ */
2515
+ fallbackLocale: 'en',
2516
+
2517
+ /**
2518
+ * Encryption key
2519
+ */
2520
+ key: process.env.APP_KEY,
2521
+
2522
+ /**
2523
+ * Service providers to register
2524
+ */
2525
+ providers: [
2526
+ // Framework providers
2527
+ // 'RouteServiceProvider',
2528
+
2529
+ // Application providers
2530
+ // 'AppServiceProvider',
2531
+ ],
2532
+ }
2533
+ `;
2534
+ }
2535
+ generateDatabaseConfig() {
2536
+ return `/**
2537
+ * Database Configuration
2538
+ */
2539
+ export default {
2540
+ /**
2541
+ * Default connection
2542
+ */
2543
+ default: process.env.DB_CONNECTION ?? 'sqlite',
2544
+
2545
+ /**
2546
+ * Database connections
2547
+ */
2548
+ connections: {
2549
+ sqlite: {
2550
+ driver: 'sqlite',
2551
+ database: process.env.DB_DATABASE ?? 'database/database.sqlite',
2552
+ },
2553
+
2554
+ mysql: {
2555
+ driver: 'mysql',
2556
+ host: process.env.DB_HOST ?? 'localhost',
2557
+ port: Number(process.env.DB_PORT ?? 3306),
2558
+ database: process.env.DB_DATABASE ?? 'forge',
2559
+ username: process.env.DB_USERNAME ?? 'forge',
2560
+ password: process.env.DB_PASSWORD ?? '',
2561
+ },
2562
+
2563
+ postgres: {
2564
+ driver: 'postgres',
2565
+ host: process.env.DB_HOST ?? 'localhost',
2566
+ port: Number(process.env.DB_PORT ?? 5432),
2567
+ database: process.env.DB_DATABASE ?? 'forge',
2568
+ username: process.env.DB_USERNAME ?? 'forge',
2569
+ password: process.env.DB_PASSWORD ?? '',
2570
+ },
2571
+ },
2572
+
2573
+ /**
2574
+ * Migration settings
2575
+ */
2576
+ migrations: {
2577
+ table: 'migrations',
2578
+ path: 'database/migrations',
2579
+ },
2580
+ }
2581
+ `;
2582
+ }
2583
+ generateAuthConfig() {
2584
+ return `/**
2585
+ * Authentication Configuration
2586
+ */
2587
+ export default {
2588
+ /**
2589
+ * Default guard
2590
+ */
2591
+ defaults: {
2592
+ guard: 'web',
2593
+ },
2594
+
2595
+ /**
2596
+ * Authentication guards
2597
+ */
2598
+ guards: {
2599
+ web: {
2600
+ driver: 'session',
2601
+ provider: 'users',
2602
+ },
2603
+
2604
+ api: {
2605
+ driver: 'token',
2606
+ provider: 'users',
2607
+ },
2608
+ },
2609
+
2610
+ /**
2611
+ * User providers
2612
+ */
2613
+ providers: {
2614
+ users: {
2615
+ driver: 'database',
2616
+ table: 'users',
2617
+ },
2618
+ },
2619
+ }
2620
+ `;
2621
+ }
2622
+ generateCacheConfig() {
2623
+ return `/**
2624
+ * Cache Configuration
2625
+ */
2626
+ export default {
2627
+ /**
2628
+ * Default cache driver
2629
+ */
2630
+ default: process.env.CACHE_DRIVER ?? 'memory',
2631
+
2632
+ /**
2633
+ * Cache stores
2634
+ */
2635
+ stores: {
2636
+ memory: {
2637
+ driver: 'memory',
2638
+ },
2639
+
2640
+ file: {
2641
+ driver: 'file',
2642
+ path: 'storage/cache',
2643
+ },
2644
+
2645
+ redis: {
2646
+ driver: 'redis',
2647
+ host: process.env.REDIS_HOST ?? 'localhost',
2648
+ port: Number(process.env.REDIS_PORT ?? 6379),
2649
+ password: process.env.REDIS_PASSWORD ?? null,
2650
+ database: Number(process.env.REDIS_CACHE_DB ?? 0),
2651
+ },
2652
+ },
2653
+
2654
+ /**
2655
+ * Cache key prefix
2656
+ */
2657
+ prefix: 'app_cache_',
2658
+ }
2659
+ `;
2660
+ }
2661
+ generateLoggingConfig() {
2662
+ return `/**
2663
+ * Logging Configuration
2664
+ */
2665
+ export default {
2666
+ /**
2667
+ * Default log channel
2668
+ */
2669
+ default: process.env.LOG_CHANNEL ?? 'console',
2670
+
2671
+ /**
2672
+ * Log channels
2673
+ */
2674
+ channels: {
2675
+ console: {
2676
+ driver: 'console',
2677
+ level: process.env.LOG_LEVEL ?? 'debug',
2678
+ },
2679
+
2680
+ file: {
2681
+ driver: 'file',
2682
+ path: 'storage/logs/app.log',
2683
+ level: process.env.LOG_LEVEL ?? 'debug',
2684
+ },
2685
+ },
2686
+ }
2687
+ `;
2688
+ }
2689
+ // ─────────────────────────────────────────────────────────────
2690
+ // Core File Generators
2691
+ // ─────────────────────────────────────────────────────────────
2692
+ generateHttpKernel() {
2693
+ return `/**
2694
+ * HTTP Kernel
2695
+ *
2696
+ * The HTTP kernel is the central point for managing HTTP middleware.
2697
+ * Global middleware runs on every request, while route middleware
2698
+ * can be assigned to specific routes.
2699
+ */
2700
+
2701
+ import type { GravitoMiddleware } from 'gravito-core'
2702
+
2703
+ /**
2704
+ * Global middleware stack.
2705
+ * These middleware are run during every request.
2706
+ */
2707
+ export const globalMiddleware: GravitoMiddleware[] = [
2708
+ // Add global middleware here
2709
+ ]
2710
+
2711
+ /**
2712
+ * Route middleware groups.
2713
+ * These can be assigned to routes using the middleware name.
2714
+ */
2715
+ export const middlewareGroups: Record<string, GravitoMiddleware[]> = {
2716
+ web: [
2717
+ // Session middleware
2718
+ // CSRF middleware
2719
+ ],
2720
+ api: [
2721
+ // Rate limiting
2722
+ // API authentication
2723
+ ],
2724
+ }
2725
+
2726
+ /**
2727
+ * Route middleware aliases.
2728
+ * Convenient names for middleware that can be assigned to routes.
2729
+ */
2730
+ export const routeMiddleware: Record<string, GravitoMiddleware> = {
2731
+ // auth: AuthenticateMiddleware,
2732
+ // guest: RedirectIfAuthenticated,
2733
+ // throttle: ThrottleRequests,
2734
+ }
2735
+
2736
+ /**
2737
+ * Middleware priority.
2738
+ * Determines the order in which middleware are executed.
2739
+ */
2740
+ export const middlewarePriority: string[] = [
2741
+ // StartSession
2742
+ // ShareErrorsFromSession
2743
+ // AuthenticateSession
2744
+ // SubstituteBindings
2745
+ // Authorize
2746
+ ]
2747
+ `;
2748
+ }
2749
+ generateBaseController() {
2750
+ return `/**
2751
+ * Base Controller
2752
+ *
2753
+ * All controllers should extend this base class.
2754
+ * Provides common functionality and helper methods.
2755
+ */
2756
+
2757
+ export abstract class Controller {
2758
+ /**
2759
+ * Create a success response.
2760
+ */
2761
+ protected success<T>(data: T, message = 'Success') {
2762
+ return {
2763
+ success: true,
2764
+ message,
2765
+ data,
2766
+ }
2767
+ }
2768
+
2769
+ /**
2770
+ * Create an error response.
2771
+ */
2772
+ protected error(message: string, code = 'ERROR', details?: unknown) {
2773
+ return {
2774
+ success: false,
2775
+ error: {
2776
+ code,
2777
+ message,
2778
+ details,
2779
+ },
2780
+ }
2781
+ }
2782
+ }
2783
+ `;
2784
+ }
2785
+ generateHomeController(context) {
2786
+ return `/**
2787
+ * Home Controller
2788
+ */
2789
+
2790
+ import type { GravitoContext } from 'gravito-core'
2791
+ import { Controller } from './Controller'
2792
+
2793
+ export class HomeController extends Controller {
2794
+ /**
2795
+ * Display the home page.
2796
+ */
2797
+ async index(c: GravitoContext) {
2798
+ return c.json(this.success({
2799
+ name: '${context.name}',
2800
+ message: 'Welcome to your new Gravito application!',
2801
+ architecture: 'Enterprise MVC',
2802
+ }))
2803
+ }
2804
+
2805
+ /**
2806
+ * Health check endpoint.
2807
+ */
2808
+ async health(c: GravitoContext) {
2809
+ return c.json({
2810
+ status: 'healthy',
2811
+ timestamp: new Date().toISOString(),
2812
+ })
2813
+ }
2814
+ }
2815
+ `;
2816
+ }
2817
+ generateAuthMiddleware() {
2818
+ return `/**
2819
+ * Authenticate Middleware
2820
+ *
2821
+ * Protects routes that require authentication.
2822
+ */
2823
+
2824
+ import type { GravitoContext, GravitoNext } from 'gravito-core'
2825
+
2826
+ export async function Authenticate(c: GravitoContext, next: GravitoNext) {
2827
+ // TODO: Implement authentication check
2828
+ // const session = c.get('session')
2829
+ // if (!session?.user) {
2830
+ // return c.json({ error: 'Authentication required' }, 401)
2831
+ // }
2832
+
2833
+ await next()
2834
+ }
2835
+ `;
2836
+ }
2837
+ generateAppServiceProvider(context) {
2838
+ return `/**
2839
+ * App Service Provider
2840
+ *
2841
+ * This is the main application service provider.
2842
+ * Register and bootstrap application services here.
2843
+ */
2844
+
2845
+ import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
2846
+
2847
+ export class AppServiceProvider extends ServiceProvider {
2848
+ /**
2849
+ * Register any application services.
2850
+ */
2851
+ register(container: Container): void {
2852
+ // Register services
2853
+ // container.singleton('myService', () => new MyService())
2854
+ }
2855
+
2856
+ /**
2857
+ * Bootstrap any application services.
2858
+ */
2859
+ boot(core: PlanetCore): void {
2860
+ // Bootstrap services
2861
+ console.log('${context.name} application booted!')
2862
+ }
2863
+ }
2864
+ `;
2865
+ }
2866
+ generateRouteServiceProvider(_context) {
2867
+ return `/**
2868
+ * Route Service Provider
2869
+ *
2870
+ * Configures and registers application routes.
2871
+ */
2872
+
2873
+ import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
2874
+ import { registerRoutes } from '../routes'
2875
+
2876
+ export class RouteServiceProvider extends ServiceProvider {
2877
+ /**
2878
+ * Register any application services.
2879
+ */
2880
+ register(_container: Container): void {
2881
+ // Routes are registered in boot
2882
+ }
2883
+
2884
+ /**
2885
+ * Bootstrap any application services.
2886
+ */
2887
+ boot(core: PlanetCore): void {
2888
+ registerRoutes(core.router)
2889
+ }
2890
+ }
2891
+ `;
2892
+ }
2893
+ generateExceptionHandler() {
2894
+ return `/**
2895
+ * Exception Handler
2896
+ *
2897
+ * Handles all exceptions thrown by the application.
2898
+ * Customize error responses and logging here.
2899
+ */
2900
+
2901
+ import type { ErrorHandlerContext } from 'gravito-core'
2902
+
2903
+ /**
2904
+ * Report an exception (logging, monitoring, etc.)
2905
+ */
2906
+ export function report(error: unknown, context: ErrorHandlerContext): void {
2907
+ // Log to external service (Sentry, etc.)
2908
+ if (!context.isProduction) {
2909
+ console.error('[Exception Handler]', error)
2910
+ }
2911
+ }
2912
+
2913
+ /**
2914
+ * Determine if the exception should be reported.
2915
+ */
2916
+ export function shouldReport(error: unknown): boolean {
2917
+ // Don't report 4xx errors
2918
+ if (error instanceof Error && 'status' in error) {
2919
+ const status = (error as any).status
2920
+ if (status >= 400 && status < 500) {
2921
+ return false
2922
+ }
2923
+ }
2924
+
2925
+ return true
2926
+ }
2927
+
2928
+ /**
2929
+ * A list of exception types that should not be reported.
2930
+ */
2931
+ export const dontReport: string[] = [
2932
+ 'ValidationException',
2933
+ 'NotFoundException',
2934
+ ]
2935
+ `;
2936
+ }
2937
+ generateBootstrap(context) {
2938
+ return `/**
2939
+ * Application Bootstrap
2940
+ *
2941
+ * This is the entry point for your application.
2942
+ * It initializes the core and registers all providers.
2943
+ */
2944
+
2945
+ import { PlanetCore } from 'gravito-core'
2946
+ import { AppServiceProvider } from './Providers/AppServiceProvider'
2947
+ import { RouteServiceProvider } from './Providers/RouteServiceProvider'
2948
+
2949
+ // Load environment variables
2950
+ // Bun automatically loads .env
2951
+
2952
+ // Create application core
2953
+ const core = new PlanetCore({
2954
+ config: {
2955
+ APP_NAME: '${context.name}',
2956
+ },
2957
+ })
2958
+
2959
+ // Register service providers
2960
+ core.register(new AppServiceProvider())
2961
+ core.register(new RouteServiceProvider())
2962
+
2963
+ // Bootstrap the application
2964
+ await core.bootstrap()
2965
+
2966
+ // Export for Bun.serve()
2967
+ export default core.liftoff()
2968
+ `;
2969
+ }
2970
+ generateRoutes(_context) {
2971
+ return `/**
2972
+ * Application Routes
2973
+ *
2974
+ * Define your application routes here.
2975
+ */
2976
+
2977
+ import { HomeController } from './Http/Controllers/HomeController'
2978
+
2979
+ export function registerRoutes(router: any): void {
2980
+ const home = new HomeController()
2981
+
2982
+ // API Routes
2983
+ router.get('/api', (c: any) => home.index(c))
2984
+ router.get('/api/health', (c: any) => home.health(c))
2985
+
2986
+ // Web Routes
2987
+ router.get('/', (c: any) => home.index(c))
2988
+ }
2989
+ `;
2990
+ }
2991
+ // ─────────────────────────────────────────────────────────────
2992
+ // Architecture Documentation
2993
+ // ─────────────────────────────────────────────────────────────
2994
+ generateArchitectureDoc(context) {
2995
+ return `# ${context.name} - Architecture Guide
2996
+
2997
+ ## Overview
2998
+
2999
+ This project uses **Enterprise MVC Architecture**, inspired by Laravel's conventions.
3000
+ It provides a clear separation of concerns while remaining pragmatic and easy to understand.
3001
+
3002
+ ## Directory Structure
3003
+
3004
+ \`\`\`
3005
+ ${context.name}/
3006
+ \u251C\u2500\u2500 config/ # Configuration files
3007
+ \u2502 \u251C\u2500\u2500 app.ts # Application settings
3008
+ \u2502 \u251C\u2500\u2500 database.ts # Database connections
3009
+ \u2502 \u251C\u2500\u2500 auth.ts # Authentication settings
3010
+ \u2502 \u251C\u2500\u2500 cache.ts # Cache configuration
3011
+ \u2502 \u2514\u2500\u2500 logging.ts # Logging channels
3012
+ \u251C\u2500\u2500 src/
3013
+ \u2502 \u251C\u2500\u2500 Http/
3014
+ \u2502 \u2502 \u251C\u2500\u2500 Kernel.ts # HTTP middleware management
3015
+ \u2502 \u2502 \u251C\u2500\u2500 Controllers/ # HTTP request handlers
3016
+ \u2502 \u2502 \u2514\u2500\u2500 Middleware/ # Request/Response interceptors
3017
+ \u2502 \u251C\u2500\u2500 Services/ # Business logic layer
3018
+ \u2502 \u251C\u2500\u2500 Repositories/ # Data access layer
3019
+ \u2502 \u251C\u2500\u2500 Models/ # Data models/entities
3020
+ \u2502 \u251C\u2500\u2500 Resources/ # API response transformers
3021
+ \u2502 \u251C\u2500\u2500 Providers/ # Service providers
3022
+ \u2502 \u251C\u2500\u2500 Exceptions/ # Custom exceptions
3023
+ \u2502 \u251C\u2500\u2500 bootstrap.ts # Application entry point
3024
+ \u2502 \u2514\u2500\u2500 routes.ts # Route definitions
3025
+ \u251C\u2500\u2500 tests/
3026
+ \u2502 \u251C\u2500\u2500 Unit/ # Unit tests
3027
+ \u2502 \u2514\u2500\u2500 Feature/ # Integration tests
3028
+ \u2514\u2500\u2500 database/
3029
+ \u251C\u2500\u2500 migrations/ # Database migrations
3030
+ \u2514\u2500\u2500 seeders/ # Database seeders
3031
+ \`\`\`
3032
+
3033
+ ## Request Flow
3034
+
3035
+ \`\`\`
3036
+ Request
3037
+ \u2502
3038
+ \u25BC
3039
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
3040
+ \u2502 Http/Kernel \u2502 \u2500\u2500\u25B6 Global Middleware
3041
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3042
+ \u2502
3043
+ \u25BC
3044
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
3045
+ \u2502 Router \u2502 \u2500\u2500\u25B6 Route Matching
3046
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3047
+ \u2502
3048
+ \u25BC
3049
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
3050
+ \u2502 Controller \u2502 \u2500\u2500\u25B6 Request Handling
3051
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3052
+ \u2502
3053
+ \u25BC
3054
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
3055
+ \u2502 Service \u2502 \u2500\u2500\u25B6 Business Logic
3056
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3057
+ \u2502
3058
+ \u25BC
3059
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
3060
+ \u2502 Repository \u2502 \u2500\u2500\u25B6 Data Access
3061
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3062
+ \u2502
3063
+ \u25BC
3064
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
3065
+ \u2502 Resource \u2502 \u2500\u2500\u25B6 Response Transform
3066
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3067
+ \u2502
3068
+ \u25BC
3069
+ Response
3070
+ \`\`\`
3071
+
3072
+ ## Layer Responsibilities
3073
+
3074
+ ### Controllers
3075
+ - Handle HTTP requests and responses
3076
+ - Validate input (or delegate to Form Requests)
3077
+ - Call services for business logic
3078
+ - Return transformed responses
3079
+
3080
+ ### Services
3081
+ - Contain business logic
3082
+ - Orchestrate multiple repositories
3083
+ - Enforce business rules
3084
+ - Should be framework-agnostic
3085
+
3086
+ ### Repositories
3087
+ - Abstract data access
3088
+ - Handle database queries
3089
+ - Return domain objects
3090
+
3091
+ ### Resources
3092
+ - Transform models for API responses
3093
+ - Control what data is exposed
3094
+ - Handle nested relationships
3095
+
3096
+ ## Getting Started
3097
+
3098
+ \`\`\`bash
3099
+ # Install dependencies
3100
+ bun install
3101
+
3102
+ # Run development server
3103
+ bun run dev
3104
+
3105
+ # Run tests
3106
+ bun test
3107
+ \`\`\`
3108
+
3109
+ ## Configuration
3110
+
3111
+ All configuration is in the \`config/\` directory.
3112
+ Environment-specific values should use environment variables.
3113
+
3114
+ ## Service Providers
3115
+
3116
+ Service providers are the central place to configure your application.
3117
+ They are registered in \`src/bootstrap.ts\`:
3118
+
3119
+ \`\`\`typescript
3120
+ core.register(new AppServiceProvider())
3121
+ core.register(new RouteServiceProvider())
3122
+ \`\`\`
3123
+
3124
+ Created with \u2764\uFE0F using Gravito Framework
3125
+ `;
3126
+ }
3127
+ };
3128
+
3129
+ // src/Scaffold.ts
3130
+ import path3 from "path";
3131
+ var Scaffold = class {
3132
+ templatesDir;
3133
+ verbose;
3134
+ constructor(options = {}) {
3135
+ this.templatesDir = options.templatesDir ?? path3.resolve(__dirname, "../templates");
3136
+ this.verbose = options.verbose ?? false;
3137
+ }
3138
+ /**
3139
+ * Get all available architecture types.
3140
+ */
3141
+ getArchitectureTypes() {
3142
+ return [
3143
+ {
3144
+ type: "enterprise-mvc",
3145
+ name: "Enterprise MVC",
3146
+ description: "Laravel-inspired MVC with Services and Repositories"
3147
+ },
3148
+ {
3149
+ type: "clean",
3150
+ name: "Clean Architecture",
3151
+ description: "Uncle Bob's Clean Architecture with strict dependency rules"
3152
+ },
3153
+ {
3154
+ type: "ddd",
3155
+ name: "Domain-Driven Design",
3156
+ description: "Full DDD with Bounded Contexts and CQRS"
3157
+ }
3158
+ ];
3159
+ }
3160
+ /**
3161
+ * Create a new project scaffold.
3162
+ */
3163
+ async create(options) {
3164
+ const generator = this.createGenerator(options.architecture);
3165
+ const context = BaseGenerator.createContext(
3166
+ options.name,
3167
+ options.targetDir,
3168
+ options.architecture,
3169
+ options.packageManager ?? "bun",
3170
+ options.context ?? {}
3171
+ );
3172
+ try {
3173
+ const filesCreated = await generator.generate(context);
3174
+ return {
3175
+ success: true,
3176
+ targetDir: options.targetDir,
3177
+ filesCreated
3178
+ };
3179
+ } catch (error) {
3180
+ return {
3181
+ success: false,
3182
+ targetDir: options.targetDir,
3183
+ filesCreated: [],
3184
+ errors: [error instanceof Error ? error.message : String(error)]
3185
+ };
3186
+ }
3187
+ }
3188
+ /**
3189
+ * Create a generator for the specified architecture.
3190
+ */
3191
+ createGenerator(type) {
3192
+ const config = {
3193
+ templatesDir: this.templatesDir,
3194
+ verbose: this.verbose
3195
+ };
3196
+ switch (type) {
3197
+ case "enterprise-mvc":
3198
+ return new EnterpriseMvcGenerator(config);
3199
+ case "clean":
3200
+ return new CleanArchitectureGenerator(config);
3201
+ case "ddd":
3202
+ return new DddGenerator(config);
3203
+ default:
3204
+ throw new Error(`Unknown architecture type: ${type}`);
3205
+ }
3206
+ }
3207
+ /**
3208
+ * Generate a single module (for DDD bounded context).
3209
+ */
3210
+ async generateModule(_targetDir, _moduleName, _options = {}) {
3211
+ throw new Error("Module generation not yet implemented");
3212
+ }
3213
+ /**
3214
+ * Generate a service provider.
3215
+ */
3216
+ async generateProvider(_targetDir, _providerName) {
3217
+ throw new Error("Provider generation not yet implemented");
3218
+ }
3219
+ };
3220
+ export {
3221
+ BaseGenerator,
3222
+ CleanArchitectureGenerator,
3223
+ DddGenerator,
3224
+ EnterpriseMvcGenerator,
3225
+ Scaffold,
3226
+ StubGenerator
3227
+ };