@gravito/scaffold 1.0.0-beta.1 → 1.0.0

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.
Files changed (33) hide show
  1. package/dist/index.cjs +1473 -299
  2. package/dist/index.d.cts +132 -9
  3. package/dist/index.d.ts +132 -9
  4. package/dist/index.js +1468 -299
  5. package/package.json +8 -5
  6. package/templates/features/otel/.env.example +2 -0
  7. package/templates/features/otel/config/telemetry.ts +10 -0
  8. package/templates/features/otel/docker-compose.yml +15 -0
  9. package/templates/features/otel/package.json +7 -0
  10. package/templates/features/redis/.env.example +7 -0
  11. package/templates/features/redis/config/cache.ts +14 -0
  12. package/templates/features/redis/config/queue.ts +13 -0
  13. package/templates/features/redis/docker-compose.yml +17 -0
  14. package/templates/features/redis/package.json +5 -0
  15. package/templates/overlays/core/.env.example +14 -0
  16. package/templates/overlays/core/config/cache.ts +26 -0
  17. package/templates/overlays/core/config/database.ts +38 -0
  18. package/templates/overlays/core/config/queue.ts +31 -0
  19. package/templates/overlays/core/package.json +5 -0
  20. package/templates/overlays/enterprise/.env.example +26 -0
  21. package/templates/overlays/enterprise/config/cache.ts +31 -0
  22. package/templates/overlays/enterprise/config/database.ts +34 -0
  23. package/templates/overlays/enterprise/config/logging.ts +32 -0
  24. package/templates/overlays/enterprise/config/queue.ts +31 -0
  25. package/templates/overlays/enterprise/config/security.ts +20 -0
  26. package/templates/overlays/enterprise/docker-compose.yml +32 -0
  27. package/templates/overlays/enterprise/package.json +7 -0
  28. package/templates/overlays/scale/.env.example +22 -0
  29. package/templates/overlays/scale/config/cache.ts +31 -0
  30. package/templates/overlays/scale/config/database.ts +34 -0
  31. package/templates/overlays/scale/config/queue.ts +34 -0
  32. package/templates/overlays/scale/docker-compose.yml +32 -0
  33. package/templates/overlays/scale/package.json +6 -0
package/dist/index.cjs CHANGED
@@ -34,11 +34,115 @@ __export(index_exports, {
34
34
  CleanArchitectureGenerator: () => CleanArchitectureGenerator,
35
35
  DddGenerator: () => DddGenerator,
36
36
  EnterpriseMvcGenerator: () => EnterpriseMvcGenerator,
37
+ EnvironmentDetector: () => EnvironmentDetector,
38
+ FileMerger: () => FileMerger,
39
+ LockGenerator: () => LockGenerator,
40
+ ProfileResolver: () => ProfileResolver,
41
+ SatelliteGenerator: () => SatelliteGenerator,
37
42
  Scaffold: () => Scaffold,
38
43
  StubGenerator: () => StubGenerator
39
44
  });
40
45
  module.exports = __toCommonJS(index_exports);
41
46
 
47
+ // src/EnvironmentDetector.ts
48
+ var EnvironmentDetector = class {
49
+ detect() {
50
+ if (process.env.KUBERNETES_SERVICE_HOST) {
51
+ return {
52
+ platform: "k8s",
53
+ suggestedProfile: "enterprise",
54
+ confidence: "high",
55
+ reason: "Kubernetes environment detected (KUBERNETES_SERVICE_HOST)"
56
+ };
57
+ }
58
+ if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.AWS_EXECUTION_ENV) {
59
+ return {
60
+ platform: "aws",
61
+ suggestedProfile: "scale",
62
+ confidence: "high",
63
+ reason: "AWS Lambda environment detected"
64
+ };
65
+ }
66
+ if (process.env.VERCEL) {
67
+ return {
68
+ platform: "vercel",
69
+ suggestedProfile: "core",
70
+ confidence: "high",
71
+ reason: "Vercel environment detected"
72
+ };
73
+ }
74
+ if (process.env.RAILWAY_ENVIRONMENT || process.env.RENDER) {
75
+ return {
76
+ platform: "unknown",
77
+ // Generic cloud
78
+ suggestedProfile: "scale",
79
+ confidence: "medium",
80
+ reason: "Cloud container environment detected"
81
+ };
82
+ }
83
+ return {
84
+ platform: "unknown",
85
+ suggestedProfile: "core",
86
+ confidence: "low",
87
+ reason: "No specific cloud environment detected, defaulting to Core"
88
+ };
89
+ }
90
+ };
91
+
92
+ // src/utils/deepMerge.ts
93
+ function deepMerge(target, source) {
94
+ if (typeof target !== "object" || target === null) {
95
+ return source;
96
+ }
97
+ if (typeof source !== "object" || source === null) {
98
+ return source;
99
+ }
100
+ if (Array.isArray(target) && Array.isArray(source)) {
101
+ return Array.from(/* @__PURE__ */ new Set([...target, ...source]));
102
+ }
103
+ const output = { ...target };
104
+ for (const key of Object.keys(source)) {
105
+ if (source[key] instanceof Object && key in target) {
106
+ output[key] = deepMerge(target[key], source[key]);
107
+ } else {
108
+ output[key] = source[key];
109
+ }
110
+ }
111
+ return output;
112
+ }
113
+
114
+ // src/FileMerger.ts
115
+ var FileMerger = class {
116
+ /**
117
+ * Merge content of two files based on their type.
118
+ */
119
+ merge(fileName, baseContent, overlayContent) {
120
+ if (fileName.endsWith(".json")) {
121
+ return this.mergeJson(baseContent, overlayContent);
122
+ }
123
+ if (fileName.endsWith(".env") || fileName.endsWith(".env.example")) {
124
+ return this.mergeEnv(baseContent, overlayContent);
125
+ }
126
+ return overlayContent;
127
+ }
128
+ mergeJson(base, overlay) {
129
+ try {
130
+ const baseJson = JSON.parse(base);
131
+ const overlayJson = JSON.parse(overlay);
132
+ const merged = deepMerge(baseJson, overlayJson);
133
+ return JSON.stringify(merged, null, 2);
134
+ } catch (e) {
135
+ console.warn("Failed to merge JSON, returning overlay", e);
136
+ return overlay;
137
+ }
138
+ }
139
+ mergeEnv(base, overlay) {
140
+ return `${base}
141
+ # --- Overlay ---
142
+ ${overlay}`;
143
+ }
144
+ };
145
+
42
146
  // src/generators/BaseGenerator.ts
43
147
  var import_promises2 = __toESM(require("fs/promises"), 1);
44
148
  var import_node_path2 = __toESM(require("path"), 1);
@@ -196,9 +300,24 @@ var StubGenerator = class {
196
300
  };
197
301
 
198
302
  // src/generators/BaseGenerator.ts
303
+ async function walk(dir) {
304
+ const files = await import_promises2.default.readdir(dir);
305
+ const paths = [];
306
+ for (const file of files) {
307
+ const filePath = import_node_path2.default.join(dir, file);
308
+ const stat = await import_promises2.default.stat(filePath);
309
+ if (stat.isDirectory()) {
310
+ paths.push(...await walk(filePath));
311
+ } else {
312
+ paths.push(filePath);
313
+ }
314
+ }
315
+ return paths;
316
+ }
199
317
  var BaseGenerator = class {
200
318
  config;
201
319
  stubGenerator;
320
+ fileMerger;
202
321
  filesCreated = [];
203
322
  constructor(config) {
204
323
  this.config = config;
@@ -207,6 +326,7 @@ var BaseGenerator = class {
207
326
  outputDir: ""
208
327
  // Set per-generation
209
328
  });
329
+ this.fileMerger = new FileMerger();
210
330
  }
211
331
  /**
212
332
  * Generate the project scaffold.
@@ -219,6 +339,8 @@ var BaseGenerator = class {
219
339
  const structure = this.getDirectoryStructure(context);
220
340
  await this.createStructure(context.targetDir, structure, context);
221
341
  await this.generateCommonFiles(context);
342
+ await this.applyOverlays(context);
343
+ await this.applyFeatureOverlays(context);
222
344
  return this.filesCreated;
223
345
  }
224
346
  /**
@@ -263,19 +385,69 @@ var BaseGenerator = class {
263
385
  await this.writeFile(context.targetDir, ".env", this.generateEnvExample(context));
264
386
  await this.writeFile(context.targetDir, ".gitignore", this.generateGitignore());
265
387
  await this.writeFile(context.targetDir, "tsconfig.json", this.generateTsConfig());
388
+ await this.writeFile(context.targetDir, "Dockerfile", this.generateDockerfile(context));
389
+ await this.writeFile(context.targetDir, ".dockerignore", this.generateDockerIgnore());
266
390
  await this.writeFile(
267
391
  context.targetDir,
268
392
  "ARCHITECTURE.md",
269
393
  this.generateArchitectureDoc(context)
270
394
  );
271
395
  }
396
+ /**
397
+ * Apply profile-specific overlays
398
+ */
399
+ async applyOverlays(context) {
400
+ const profile = context.profile;
401
+ if (!profile) return;
402
+ const overlayDir = import_node_path2.default.resolve(this.config.templatesDir, "overlays", profile);
403
+ await this.copyOverlayDirectory(overlayDir, context);
404
+ }
405
+ /**
406
+ * Apply feature-specific overlays
407
+ */
408
+ async applyFeatureOverlays(context) {
409
+ const features = context.features || [];
410
+ for (const feature of features) {
411
+ const overlayDir = import_node_path2.default.resolve(this.config.templatesDir, "features", feature);
412
+ await this.copyOverlayDirectory(overlayDir, context);
413
+ }
414
+ }
415
+ /**
416
+ * Helper to copy/merge an overlay directory into the target
417
+ */
418
+ async copyOverlayDirectory(sourceDir, context) {
419
+ try {
420
+ await import_promises2.default.access(sourceDir);
421
+ } catch {
422
+ return;
423
+ }
424
+ const files = await walk(sourceDir);
425
+ for (const filePath of files) {
426
+ const relativePath = import_node_path2.default.relative(sourceDir, filePath);
427
+ let content = await import_promises2.default.readFile(filePath, "utf-8");
428
+ try {
429
+ content = this.stubGenerator.render(content, context);
430
+ } catch {
431
+ }
432
+ await this.writeFile(context.targetDir, relativePath, content);
433
+ }
434
+ }
272
435
  /**
273
436
  * Write a file and track it.
274
437
  */
275
438
  async writeFile(basePath, relativePath, content) {
276
439
  const fullPath = import_node_path2.default.resolve(basePath, relativePath);
277
440
  await import_promises2.default.mkdir(import_node_path2.default.dirname(fullPath), { recursive: true });
278
- await import_promises2.default.writeFile(fullPath, content, "utf-8");
441
+ let finalContent = content;
442
+ try {
443
+ const existingContent = await import_promises2.default.readFile(fullPath, "utf-8");
444
+ finalContent = this.fileMerger.merge(relativePath, existingContent, content);
445
+ if (finalContent !== content) {
446
+ this.log(`\u{1F504} Merged file: ${relativePath}`);
447
+ }
448
+ } catch {
449
+ }
450
+ await import_promises2.default.writeFile(fullPath, finalContent, "utf-8");
279
451
  this.filesCreated.push(fullPath);
280
452
  this.log(`\u{1F4C4} Created file: ${relativePath}`);
281
453
  }
@@ -283,6 +455,20 @@ var BaseGenerator = class {
283
455
  * Generate package.json content.
284
456
  */
285
457
  generatePackageJson(context) {
458
+ const profile = context.profile || "core";
459
+ const baseDependencies = {
460
+ "@gravito/core": "^1.0.0-beta.5",
461
+ "@gravito/atlas": "^1.0.0-beta.5",
462
+ "@gravito/plasma": "^1.0.0-beta.5",
463
+ "@gravito/stream": "^1.0.0-beta.5"
464
+ };
465
+ if (profile === "enterprise" || profile === "scale") {
466
+ baseDependencies["@gravito/quasar"] = "^1.0.0-beta.5";
467
+ baseDependencies["@gravito/horizon"] = "^1.0.0-beta.5";
468
+ }
469
+ if (context.withSpectrum) {
470
+ baseDependencies["@gravito/spectrum"] = "^1.0.0-beta.1";
471
+ }
286
472
  const pkg = {
287
473
  name: context.nameKebabCase,
288
474
  version: "0.1.0",
@@ -292,42 +478,193 @@ var BaseGenerator = class {
292
478
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
293
479
  start: "bun run dist/bootstrap.js",
294
480
  test: "bun test",
295
- typecheck: "tsc --noEmit"
296
- },
297
- dependencies: {
298
- "gravito-core": "^1.0.0-beta.5"
481
+ typecheck: "tsc --noEmit",
482
+ "docker:build": `docker build -t ${context.nameKebabCase} .`,
483
+ "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
299
484
  },
485
+ dependencies: baseDependencies,
300
486
  devDependencies: {
301
- "@types/bun": "latest",
487
+ "bun-types": "latest",
302
488
  typescript: "^5.0.0"
303
489
  }
304
490
  };
305
491
  return JSON.stringify(pkg, null, 2);
306
492
  }
493
+ /**
494
+ * Generate Dockerfile content.
495
+ */
496
+ generateDockerfile(context) {
497
+ const entrypoint = context.architecture === "ddd" ? "dist/main.js" : "dist/bootstrap.js";
498
+ return `FROM oven/bun:1.0 AS base
499
+ WORKDIR /usr/src/app
500
+
501
+ # Install dependencies
502
+ FROM base AS install
503
+ RUN mkdir -p /temp/dev
504
+ COPY package.json bun.lockb /temp/dev/
505
+ RUN cd /temp/dev && bun install --frozen-lockfile
506
+
507
+ # Build application
508
+ FROM base AS build
509
+ COPY --from=install /temp/dev/node_modules node_modules
510
+ COPY . .
511
+ ENV NODE_ENV=production
512
+ RUN bun run build
513
+
514
+ # Final production image
515
+ FROM base AS release
516
+ COPY --from=build /usr/src/app/${entrypoint} index.js
517
+ COPY --from=build /usr/src/app/package.json .
518
+
519
+ # Create a non-root user for security
520
+ USER bun
521
+ EXPOSE 3000/tcp
522
+ ENTRYPOINT [ "bun", "run", "index.js" ]
523
+ `;
524
+ }
525
+ /**
526
+ * Generate .dockerignore content.
527
+ */
528
+ generateDockerIgnore() {
529
+ return `node_modules
530
+ dist
531
+ .git
532
+ .env
533
+ *.log
534
+ .vscode
535
+ .idea
536
+ tests
537
+ `;
538
+ }
307
539
  /**
308
540
  * Generate .env.example content.
309
541
  */
310
542
  generateEnvExample(context) {
311
- return `# Application
312
- APP_NAME="${context.name}"
543
+ const profile = context.profile || "core";
544
+ let envContent = `# ============================================================================
545
+ # Application Configuration
546
+ # ============================================================================
547
+
548
+ APP_NAME=${context.name}
313
549
  APP_ENV=development
314
- APP_KEY=
315
550
  APP_DEBUG=true
316
551
  APP_URL=http://localhost:3000
552
+ APP_KEY=
317
553
 
318
- # Server
319
- PORT=3000
554
+ # ============================================================================
555
+ # Database Configuration
556
+ # ============================================================================
320
557
 
321
- # Database
322
- DB_CONNECTION=sqlite
558
+ # Database Connection (sqlite, postgres, mysql)
559
+ DB_CONNECTION=${profile === "core" ? "sqlite" : "postgres"}
560
+
561
+ # SQLite Configuration (when DB_CONNECTION=sqlite)
323
562
  DB_DATABASE=database/database.sqlite
324
563
 
325
- # Cache
326
- CACHE_DRIVER=memory
564
+ # PostgreSQL Configuration (when DB_CONNECTION=postgres)
565
+ ${profile !== "core" ? `DB_HOST=127.0.0.1
566
+ DB_PORT=5432
567
+ DB_DATABASE=${context.name}
568
+ DB_USERNAME=postgres
569
+ DB_PASSWORD=
570
+ DB_SSLMODE=prefer` : `# DB_HOST=127.0.0.1
571
+ # DB_PORT=5432
572
+ # DB_DATABASE=${context.name}
573
+ # DB_USERNAME=postgres
574
+ # DB_PASSWORD=
575
+ # DB_SSLMODE=prefer`}
576
+
577
+ # MySQL Configuration (when DB_CONNECTION=mysql)
578
+ # DB_HOST=127.0.0.1
579
+ # DB_PORT=3306
580
+ # DB_DATABASE=${context.name}
581
+ # DB_USERNAME=root
582
+ # DB_PASSWORD=
583
+
584
+ # ============================================================================
585
+ # Redis Configuration (@gravito/plasma)
586
+ # ============================================================================
587
+
588
+ # Default Redis Connection
589
+ REDIS_CONNECTION=default
590
+ REDIS_HOST=127.0.0.1
591
+ REDIS_PORT=6379
592
+ REDIS_PASSWORD=
593
+ REDIS_DB=0
594
+
595
+ # Redis Connection Options
596
+ REDIS_CONNECT_TIMEOUT=10000
597
+ REDIS_COMMAND_TIMEOUT=5000
598
+ REDIS_KEY_PREFIX=
599
+ REDIS_MAX_RETRIES=3
600
+ REDIS_RETRY_DELAY=1000
601
+
602
+ # Cache-specific Redis Connection (optional, falls back to default)
603
+ # REDIS_CACHE_HOST=127.0.0.1
604
+ # REDIS_CACHE_PORT=6379
605
+ # REDIS_CACHE_PASSWORD=
606
+ REDIS_CACHE_DB=1
607
+
608
+ # Queue-specific Redis Connection (optional, falls back to default)
609
+ # REDIS_QUEUE_HOST=127.0.0.1
610
+ # REDIS_QUEUE_PORT=6379
611
+ # REDIS_QUEUE_PASSWORD=
612
+ REDIS_QUEUE_DB=2
613
+
614
+ # ============================================================================
615
+ # Cache Configuration (@gravito/stasis)
616
+ # ============================================================================
617
+
618
+ # Cache Driver (memory, file, redis)
619
+ CACHE_DRIVER=${profile === "core" ? "memory" : "redis"}
620
+
621
+ # File Cache Path (when CACHE_DRIVER=file)
622
+ CACHE_PATH=storage/framework/cache
623
+
624
+ # Redis Cache Configuration (when CACHE_DRIVER=redis)
625
+ REDIS_CACHE_CONNECTION=cache
626
+ REDIS_CACHE_PREFIX=cache:
627
+
628
+ # ============================================================================
629
+ # Queue Configuration (@gravito/stream)
630
+ # ============================================================================
631
+
632
+ # Queue Connection (sync, memory, database, redis, kafka, sqs, rabbitmq)
633
+ QUEUE_CONNECTION=${profile === "core" ? "sync" : "redis"}
634
+
635
+ # Database Queue Configuration (when QUEUE_CONNECTION=database)
636
+ QUEUE_TABLE=jobs
637
+
638
+ # Redis Queue Configuration (when QUEUE_CONNECTION=redis)
639
+ REDIS_PREFIX=queue:
640
+
641
+ `;
642
+ if (profile === "enterprise" || profile === "scale") {
643
+ envContent += `# Kafka Queue Configuration (when QUEUE_CONNECTION=kafka)
644
+ # KAFKA_BROKERS=localhost:9092
645
+ # KAFKA_CONSUMER_GROUP_ID=gravito-workers
646
+ # KAFKA_CLIENT_ID=${context.name}
647
+
648
+ # AWS SQS Queue Configuration (when QUEUE_CONNECTION=sqs)
649
+ # AWS_REGION=us-east-1
650
+ # SQS_QUEUE_URL_PREFIX=
651
+ # SQS_VISIBILITY_TIMEOUT=30
652
+ # SQS_WAIT_TIME_SECONDS=20
653
+
654
+ # RabbitMQ Queue Configuration (when QUEUE_CONNECTION=rabbitmq)
655
+ # RABBITMQ_URL=amqp://localhost
656
+ # RABBITMQ_EXCHANGE=gravito.events
657
+ # RABBITMQ_EXCHANGE_TYPE=fanout
658
+
659
+ `;
660
+ }
661
+ envContent += `# ============================================================================
662
+ # Logging Configuration
663
+ # ============================================================================
327
664
 
328
- # Logging
329
665
  LOG_LEVEL=debug
330
666
  `;
667
+ return envContent;
331
668
  }
332
669
  /**
333
670
  * Generate .gitignore content.
@@ -379,6 +716,9 @@ coverage/
379
716
  strict: true,
380
717
  skipLibCheck: true,
381
718
  declaration: true,
719
+ experimentalDecorators: true,
720
+ emitDecoratorMetadata: true,
721
+ types: ["bun-types"],
382
722
  outDir: "./dist",
383
723
  rootDir: "./src",
384
724
  baseUrl: ".",
@@ -448,6 +788,11 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
448
788
  { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
449
789
  ]
450
790
  },
791
+ {
792
+ type: "directory",
793
+ name: "database",
794
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
795
+ },
451
796
  {
452
797
  type: "directory",
453
798
  name: "src",
@@ -460,16 +805,12 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
460
805
  {
461
806
  type: "directory",
462
807
  name: "Entities",
463
- children: [
464
- { type: "file", name: "Entity.ts", content: this.generateBaseEntity() },
465
- { type: "file", name: "User.ts", content: this.generateUserEntity() }
466
- ]
808
+ children: [{ type: "file", name: "User.ts", content: this.generateUserEntity() }]
467
809
  },
468
810
  {
469
811
  type: "directory",
470
812
  name: "ValueObjects",
471
813
  children: [
472
- { type: "file", name: "ValueObject.ts", content: this.generateBaseValueObject() },
473
814
  { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
474
815
  ]
475
816
  },
@@ -487,13 +828,7 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
487
828
  {
488
829
  type: "directory",
489
830
  name: "Exceptions",
490
- children: [
491
- {
492
- type: "file",
493
- name: "DomainException.ts",
494
- content: this.generateDomainException()
495
- }
496
- ]
831
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
497
832
  }
498
833
  ]
499
834
  },
@@ -714,42 +1049,6 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
714
1049
  console: { driver: 'console', level: process.env.LOG_LEVEL ?? 'debug' },
715
1050
  },
716
1051
  }
717
- `;
718
- }
719
- // ─────────────────────────────────────────────────────────────
720
- // Domain Layer
721
- // ─────────────────────────────────────────────────────────────
722
- generateBaseEntity() {
723
- return `/**
724
- * Base Entity
725
- *
726
- * All domain entities extend this base class.
727
- * Entities have identity and lifecycle.
728
- *
729
- * IMPORTANT: This layer must have NO external dependencies.
730
- */
731
-
732
- export abstract class Entity<T> {
733
- protected readonly _id: T
734
-
735
- constructor(id: T) {
736
- this._id = id
737
- }
738
-
739
- get id(): T {
740
- return this._id
741
- }
742
-
743
- equals(other: Entity<T>): boolean {
744
- if (other === null || other === undefined) {
745
- return false
746
- }
747
- if (!(other instanceof Entity)) {
748
- return false
749
- }
750
- return this._id === other._id
751
- }
752
- }
753
1052
  `;
754
1053
  }
755
1054
  generateUserEntity() {
@@ -760,7 +1059,7 @@ export abstract class Entity<T> {
760
1059
  * Contains business logic related to users.
761
1060
  */
762
1061
 
763
- import { Entity } from './Entity'
1062
+ import { Entity } from '@gravito/enterprise'
764
1063
  import { Email } from '../ValueObjects/Email'
765
1064
 
766
1065
  export interface UserProps {
@@ -810,30 +1109,6 @@ export class User extends Entity<string> {
810
1109
  this.props.updatedAt = new Date()
811
1110
  }
812
1111
  }
813
- `;
814
- }
815
- generateBaseValueObject() {
816
- return `/**
817
- * Base Value Object
818
- *
819
- * Value objects are immutable and compared by value.
820
- * They have no identity of their own.
821
- */
822
-
823
- export abstract class ValueObject<T> {
824
- protected readonly props: T
825
-
826
- constructor(props: T) {
827
- this.props = Object.freeze(props)
828
- }
829
-
830
- equals(other: ValueObject<T>): boolean {
831
- if (other === null || other === undefined) {
832
- return false
833
- }
834
- return JSON.stringify(this.props) === JSON.stringify(other.props)
835
- }
836
- }
837
1112
  `;
838
1113
  }
839
1114
  generateEmailValueObject() {
@@ -843,7 +1118,7 @@ export abstract class ValueObject<T> {
843
1118
  * Encapsulates email validation and comparison.
844
1119
  */
845
1120
 
846
- import { ValueObject } from './ValueObject'
1121
+ import { ValueObject } from '@gravito/enterprise'
847
1122
 
848
1123
  interface EmailProps {
849
1124
  value: string
@@ -884,43 +1159,11 @@ export class Email extends ValueObject<EmailProps> {
884
1159
  * Implementations are in Infrastructure layer.
885
1160
  */
886
1161
 
1162
+ import type { Repository } from '@gravito/enterprise'
887
1163
  import type { User } from '../Entities/User'
888
1164
 
889
- export interface IUserRepository {
890
- findById(id: string): Promise<User | null>
1165
+ export interface IUserRepository extends Repository<User, string> {
891
1166
  findByEmail(email: string): Promise<User | null>
892
- save(user: User): Promise<void>
893
- delete(id: string): Promise<void>
894
- findAll(): Promise<User[]>
895
- }
896
- `;
897
- }
898
- generateDomainException() {
899
- return `/**
900
- * Domain Exception
901
- *
902
- * Base exception for domain-level errors.
903
- */
904
-
905
- export class DomainException extends Error {
906
- constructor(message: string) {
907
- super(message)
908
- this.name = 'DomainException'
909
- }
910
- }
911
-
912
- export class EntityNotFoundException extends DomainException {
913
- constructor(entity: string, id: string) {
914
- super(\`\${entity} with id \${id} not found\`)
915
- this.name = 'EntityNotFoundException'
916
- }
917
- }
918
-
919
- export class InvalidValueException extends DomainException {
920
- constructor(message: string) {
921
- super(message)
922
- this.name = 'InvalidValueException'
923
- }
924
1167
  }
925
1168
  `;
926
1169
  }
@@ -934,6 +1177,7 @@ export class InvalidValueException extends DomainException {
934
1177
  * Application service for creating new users.
935
1178
  */
936
1179
 
1180
+ import { UseCase } from '@gravito/enterprise'
937
1181
  import { User } from '../../../Domain/Entities/User'
938
1182
  import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
939
1183
  import type { UserDTO } from '../../DTOs/UserDTO'
@@ -947,8 +1191,10 @@ export interface CreateUserOutput {
947
1191
  user: UserDTO
948
1192
  }
949
1193
 
950
- export class CreateUserUseCase {
951
- constructor(private userRepository: IUserRepository) {}
1194
+ export class CreateUserUseCase extends UseCase<CreateUserInput, CreateUserOutput> {
1195
+ constructor(private userRepository: IUserRepository) {
1196
+ super()
1197
+ }
952
1198
 
953
1199
  async execute(input: CreateUserInput): Promise<CreateUserOutput> {
954
1200
  // Check if email already exists
@@ -985,8 +1231,8 @@ export class CreateUserUseCase {
985
1231
  * Get User Use Case
986
1232
  */
987
1233
 
1234
+ import { UseCase } from '@gravito/enterprise'
988
1235
  import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
989
- import { EntityNotFoundException } from '../../../Domain/Exceptions/DomainException'
990
1236
  import type { UserDTO } from '../../DTOs/UserDTO'
991
1237
 
992
1238
  export interface GetUserInput {
@@ -997,14 +1243,16 @@ export interface GetUserOutput {
997
1243
  user: UserDTO
998
1244
  }
999
1245
 
1000
- export class GetUserUseCase {
1001
- constructor(private userRepository: IUserRepository) {}
1246
+ export class GetUserUseCase extends UseCase<GetUserInput, GetUserOutput> {
1247
+ constructor(private userRepository: IUserRepository) {
1248
+ super()
1249
+ }
1002
1250
 
1003
1251
  async execute(input: GetUserInput): Promise<GetUserOutput> {
1004
1252
  const user = await this.userRepository.findById(input.id)
1005
1253
 
1006
1254
  if (!user) {
1007
- throw new EntityNotFoundException('User', input.id)
1255
+ throw new Error(\`User with id \${input.id} not found\`)
1008
1256
  }
1009
1257
 
1010
1258
  return {
@@ -1105,6 +1353,10 @@ export class UserRepository implements IUserRepository {
1105
1353
  async findAll(): Promise<User[]> {
1106
1354
  return Array.from(users.values())
1107
1355
  }
1356
+
1357
+ async exists(id: string): Promise<boolean> {
1358
+ return users.has(id)
1359
+ }
1108
1360
  }
1109
1361
  `;
1110
1362
  }
@@ -1128,7 +1380,7 @@ export class MailService implements IMailService {
1128
1380
  * App Service Provider
1129
1381
  */
1130
1382
 
1131
- import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
1383
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1132
1384
 
1133
1385
  export class AppServiceProvider extends ServiceProvider {
1134
1386
  register(_container: Container): void {
@@ -1148,7 +1400,7 @@ export class AppServiceProvider extends ServiceProvider {
1148
1400
  * Binds repository interfaces to implementations.
1149
1401
  */
1150
1402
 
1151
- import { ServiceProvider, type Container } from 'gravito-core'
1403
+ import { ServiceProvider, type Container } from '@gravito/core'
1152
1404
  import { UserRepository } from '../Persistence/Repositories/UserRepository'
1153
1405
  import { MailService } from '../ExternalServices/MailService'
1154
1406
 
@@ -1171,7 +1423,7 @@ export class RepositoryServiceProvider extends ServiceProvider {
1171
1423
  * User Controller
1172
1424
  */
1173
1425
 
1174
- import type { GravitoContext } from 'gravito-core'
1426
+ import type { GravitoContext } from '@gravito/core'
1175
1427
  import { CreateUserUseCase } from '../../../Application/UseCases/User/CreateUser'
1176
1428
  import { GetUserUseCase } from '../../../Application/UseCases/User/GetUser'
1177
1429
  import { UserRepository } from '../../../Infrastructure/Persistence/Repositories/UserRepository'
@@ -1250,15 +1502,51 @@ export class UserPresenter {
1250
1502
  * Application Bootstrap
1251
1503
  */
1252
1504
 
1253
- import { PlanetCore } from 'gravito-core'
1505
+ import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
1506
+ import { OrbitAtlas } from '@gravito/atlas'
1507
+ import databaseConfig from '../config/database'
1254
1508
  import { AppServiceProvider } from './Infrastructure/Providers/AppServiceProvider'
1255
1509
  import { RepositoryServiceProvider } from './Infrastructure/Providers/RepositoryServiceProvider'
1256
1510
  import { registerApiRoutes } from './Interface/Http/Routes/api'
1257
1511
 
1258
1512
  const core = new PlanetCore({
1259
- config: { APP_NAME: '${context.name}' },
1513
+ config: {
1514
+ APP_NAME: '${context.name}',
1515
+ database: databaseConfig,
1516
+ },
1260
1517
  })
1261
1518
 
1519
+ const defaultCsp = [
1520
+ "default-src 'self'",
1521
+ "script-src 'self' 'unsafe-inline'",
1522
+ "style-src 'self' 'unsafe-inline'",
1523
+ "img-src 'self' data:",
1524
+ "object-src 'none'",
1525
+ "base-uri 'self'",
1526
+ "frame-ancestors 'none'",
1527
+ ].join('; ')
1528
+ const cspValue = process.env.APP_CSP
1529
+ const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
1530
+ const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
1531
+ const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
1532
+ const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
1533
+
1534
+ core.adapter.use(
1535
+ '*',
1536
+ securityHeaders({
1537
+ contentSecurityPolicy: csp,
1538
+ hsts:
1539
+ process.env.NODE_ENV === 'production'
1540
+ ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
1541
+ : false,
1542
+ })
1543
+ )
1544
+ if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
1545
+ core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
1546
+ }
1547
+
1548
+ await core.orbit(new OrbitAtlas())
1549
+
1262
1550
  core.register(new RepositoryServiceProvider())
1263
1551
  core.register(new AppServiceProvider())
1264
1552
 
@@ -1338,6 +1626,32 @@ src/
1338
1626
  Created with \u2764\uFE0F using Gravito Framework
1339
1627
  `;
1340
1628
  }
1629
+ generatePackageJson(context) {
1630
+ const pkg = {
1631
+ name: context.nameKebabCase,
1632
+ version: "0.1.0",
1633
+ type: "module",
1634
+ scripts: {
1635
+ dev: "bun run --watch src/bootstrap.ts",
1636
+ build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
1637
+ start: "bun run dist/bootstrap.js",
1638
+ test: "bun test",
1639
+ typecheck: "tsc --noEmit",
1640
+ "docker:build": `docker build -t ${context.nameKebabCase} .`,
1641
+ "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
1642
+ },
1643
+ dependencies: {
1644
+ "@gravito/core": "workspace:*",
1645
+ "@gravito/enterprise": "workspace:*",
1646
+ ...context.withSpectrum ? { "@gravito/spectrum": "workspace:*" } : {}
1647
+ },
1648
+ devDependencies: {
1649
+ "bun-types": "latest",
1650
+ typescript: "^5.0.0"
1651
+ }
1652
+ };
1653
+ return JSON.stringify(pkg, null, 2);
1654
+ }
1341
1655
  };
1342
1656
 
1343
1657
  // src/generators/DddGenerator.ts
@@ -1465,22 +1779,6 @@ var DddGenerator = class extends BaseGenerator {
1465
1779
  { type: "file", name: "Money.ts", content: this.generateMoneyValueObject() },
1466
1780
  { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
1467
1781
  ]
1468
- },
1469
- {
1470
- type: "directory",
1471
- name: "Events",
1472
- children: [
1473
- { type: "file", name: "DomainEvent.ts", content: this.generateDomainEvent() }
1474
- ]
1475
- },
1476
- {
1477
- type: "directory",
1478
- name: "Primitives",
1479
- children: [
1480
- { type: "file", name: "AggregateRoot.ts", content: this.generateAggregateRoot() },
1481
- { type: "file", name: "Entity.ts", content: this.generateEntity() },
1482
- { type: "file", name: "ValueObject.ts", content: this.generateValueObject() }
1483
- ]
1484
1782
  }
1485
1783
  ]
1486
1784
  },
@@ -1669,7 +1967,7 @@ var DddGenerator = class extends BaseGenerator {
1669
1967
  * Central configuration and initialization of the application.
1670
1968
  */
1671
1969
 
1672
- import { PlanetCore } from 'gravito-core'
1970
+ import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
1673
1971
  import { registerProviders } from './providers'
1674
1972
  import { registerRoutes } from './routes'
1675
1973
 
@@ -1680,6 +1978,35 @@ export async function createApp(): Promise<PlanetCore> {
1680
1978
  },
1681
1979
  })
1682
1980
 
1981
+ const defaultCsp = [
1982
+ "default-src 'self'",
1983
+ "script-src 'self' 'unsafe-inline'",
1984
+ "style-src 'self' 'unsafe-inline'",
1985
+ "img-src 'self' data:",
1986
+ "object-src 'none'",
1987
+ "base-uri 'self'",
1988
+ "frame-ancestors 'none'",
1989
+ ].join('; ')
1990
+ const cspValue = process.env.APP_CSP
1991
+ const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
1992
+ const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
1993
+ const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
1994
+ const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
1995
+
1996
+ core.adapter.use(
1997
+ '*',
1998
+ securityHeaders({
1999
+ contentSecurityPolicy: csp,
2000
+ hsts:
2001
+ process.env.NODE_ENV === 'production'
2002
+ ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
2003
+ : false,
2004
+ })
2005
+ )
2006
+ if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
2007
+ core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
2008
+ }
2009
+
1683
2010
  // Register all service providers
1684
2011
  await registerProviders(core)
1685
2012
 
@@ -1700,7 +2027,7 @@ export async function createApp(): Promise<PlanetCore> {
1700
2027
  * Register all module service providers here.
1701
2028
  */
1702
2029
 
1703
- import type { PlanetCore } from 'gravito-core'
2030
+ import type { PlanetCore } from '@gravito/core'
1704
2031
  import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
1705
2032
  import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
1706
2033
 
@@ -1795,7 +2122,7 @@ export default {
1795
2122
  * ${name} Service Provider
1796
2123
  */
1797
2124
 
1798
- import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
2125
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1799
2126
  import { ${name}Repository } from '../Persistence/${name}Repository'
1800
2127
 
1801
2128
  export class ${name}ServiceProvider extends ServiceProvider {
@@ -1822,13 +2149,16 @@ export class ${name}ServiceProvider extends ServiceProvider {
1822
2149
  build: "bun build ./src/main.ts --outdir ./dist --target bun",
1823
2150
  start: "bun run dist/main.js",
1824
2151
  test: "bun test",
1825
- typecheck: "tsc --noEmit"
2152
+ typecheck: "tsc --noEmit",
2153
+ "docker:build": `docker build -t ${context.nameKebabCase} .`,
2154
+ "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
1826
2155
  },
1827
2156
  dependencies: {
1828
- "gravito-core": "^1.0.0-beta.5"
2157
+ "@gravito/core": "^1.0.0-beta.5",
2158
+ "@gravito/enterprise": "workspace:*"
1829
2159
  },
1830
2160
  devDependencies: {
1831
- "@types/bun": "latest",
2161
+ "bun-types": "latest",
1832
2162
  typescript: "^5.0.0"
1833
2163
  }
1834
2164
  };
@@ -1879,11 +2209,15 @@ export class ${name}ServiceProvider extends ServiceProvider {
1879
2209
  * Shared identifier across all contexts.
1880
2210
  */
1881
2211
 
1882
- export class Id {
1883
- private readonly _value: string
2212
+ import { ValueObject } from '@gravito/enterprise'
1884
2213
 
2214
+ interface IdProps {
2215
+ value: string
2216
+ }
2217
+
2218
+ export class Id extends ValueObject<IdProps> {
1885
2219
  private constructor(value: string) {
1886
- this._value = value
2220
+ super({ value })
1887
2221
  }
1888
2222
 
1889
2223
  static create(): Id {
@@ -1896,15 +2230,11 @@ export class Id {
1896
2230
  }
1897
2231
 
1898
2232
  get value(): string {
1899
- return this._value
1900
- }
1901
-
1902
- equals(other: Id): boolean {
1903
- return this._value === other._value
2233
+ return this.props.value
1904
2234
  }
1905
2235
 
1906
2236
  toString(): string {
1907
- return this._value
2237
+ return this.props.value
1908
2238
  }
1909
2239
  }
1910
2240
  `;
@@ -1914,12 +2244,25 @@ export class Id {
1914
2244
  * Money Value Object
1915
2245
  */
1916
2246
 
1917
- export class Money {
1918
- constructor(
1919
- public readonly amount: number,
1920
- public readonly currency: string = 'USD'
1921
- ) {
2247
+ import { ValueObject } from '@gravito/enterprise'
2248
+
2249
+ interface MoneyProps {
2250
+ amount: number
2251
+ currency: string
2252
+ }
2253
+
2254
+ export class Money extends ValueObject<MoneyProps> {
2255
+ constructor(amount: number, currency: string = 'USD') {
1922
2256
  if (amount < 0) throw new Error('Amount cannot be negative')
2257
+ super({ amount, currency })
2258
+ }
2259
+
2260
+ get amount(): number {
2261
+ return this.props.amount
2262
+ }
2263
+
2264
+ get currency(): string {
2265
+ return this.props.currency
1923
2266
  }
1924
2267
 
1925
2268
  add(other: Money): Money {
@@ -1937,10 +2280,6 @@ export class Money {
1937
2280
  throw new Error('Cannot operate on different currencies')
1938
2281
  }
1939
2282
  }
1940
-
1941
- equals(other: Money): boolean {
1942
- return this.amount === other.amount && this.currency === other.currency
1943
- }
1944
2283
  }
1945
2284
  `;
1946
2285
  }
@@ -1949,11 +2288,15 @@ export class Money {
1949
2288
  * Email Value Object
1950
2289
  */
1951
2290
 
1952
- export class Email {
1953
- private readonly _value: string
2291
+ import { ValueObject } from '@gravito/enterprise'
2292
+
2293
+ interface EmailProps {
2294
+ value: string
2295
+ }
1954
2296
 
2297
+ export class Email extends ValueObject<EmailProps> {
1955
2298
  private constructor(value: string) {
1956
- this._value = value.toLowerCase().trim()
2299
+ super({ value: value.toLowerCase().trim() })
1957
2300
  }
1958
2301
 
1959
2302
  static create(email: string): Email {
@@ -1968,97 +2311,7 @@ export class Email {
1968
2311
  }
1969
2312
 
1970
2313
  get value(): string {
1971
- return this._value
1972
- }
1973
- }
1974
- `;
1975
- }
1976
- generateDomainEvent() {
1977
- return `/**
1978
- * Domain Event Base
1979
- */
1980
-
1981
- export abstract class DomainEvent {
1982
- readonly occurredOn: Date
1983
- readonly eventId: string
1984
-
1985
- constructor() {
1986
- this.occurredOn = new Date()
1987
- this.eventId = crypto.randomUUID()
1988
- }
1989
-
1990
- abstract get eventName(): string
1991
- abstract get aggregateId(): string
1992
- }
1993
- `;
1994
- }
1995
- generateAggregateRoot() {
1996
- return `/**
1997
- * Aggregate Root Base
1998
- */
1999
-
2000
- import type { DomainEvent } from '../Events/DomainEvent'
2001
- import type { Id } from '../ValueObjects/Id'
2002
-
2003
- export abstract class AggregateRoot<T extends Id = Id> {
2004
- private _domainEvents: DomainEvent[] = []
2005
-
2006
- protected constructor(protected readonly _id: T) {}
2007
-
2008
- get id(): T {
2009
- return this._id
2010
- }
2011
-
2012
- get domainEvents(): DomainEvent[] {
2013
- return [...this._domainEvents]
2014
- }
2015
-
2016
- protected addDomainEvent(event: DomainEvent): void {
2017
- this._domainEvents.push(event)
2018
- }
2019
-
2020
- clearDomainEvents(): DomainEvent[] {
2021
- const events = [...this._domainEvents]
2022
- this._domainEvents = []
2023
- return events
2024
- }
2025
- }
2026
- `;
2027
- }
2028
- generateEntity() {
2029
- return `/**
2030
- * Entity Base
2031
- */
2032
-
2033
- import type { Id } from '../ValueObjects/Id'
2034
-
2035
- export abstract class Entity<T extends Id = Id> {
2036
- protected constructor(protected readonly _id: T) {}
2037
-
2038
- get id(): T {
2039
- return this._id
2040
- }
2041
-
2042
- equals(other: Entity<T>): boolean {
2043
- return this._id.equals(other._id)
2044
- }
2045
- }
2046
- `;
2047
- }
2048
- generateValueObject() {
2049
- return `/**
2050
- * Value Object Base
2051
- */
2052
-
2053
- export abstract class ValueObject<T> {
2054
- protected readonly props: T
2055
-
2056
- constructor(props: T) {
2057
- this.props = Object.freeze(props)
2058
- }
2059
-
2060
- equals(other: ValueObject<T>): boolean {
2061
- return JSON.stringify(this.props) === JSON.stringify(other.props)
2314
+ return this.props.value
2062
2315
  }
2063
2316
  }
2064
2317
  `;
@@ -2068,7 +2321,7 @@ export abstract class ValueObject<T> {
2068
2321
  * Event Dispatcher
2069
2322
  */
2070
2323
 
2071
- import type { DomainEvent } from '../../Domain/Events/DomainEvent'
2324
+ import type { DomainEvent } from '@gravito/enterprise'
2072
2325
 
2073
2326
  type EventHandler = (event: DomainEvent) => void | Promise<void>
2074
2327
 
@@ -2104,7 +2357,7 @@ export class EventDispatcher {
2104
2357
  * ${name} Aggregate Root
2105
2358
  */
2106
2359
 
2107
- import { AggregateRoot } from '../../../../../Shared/Domain/Primitives/AggregateRoot'
2360
+ import { AggregateRoot } from '@gravito/enterprise'
2108
2361
  import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2109
2362
  import { ${name}Created } from '../../Events/${name}Created'
2110
2363
  import { ${name}Status } from './${name}Status'
@@ -2115,7 +2368,7 @@ export interface ${name}Props {
2115
2368
  createdAt: Date
2116
2369
  }
2117
2370
 
2118
- export class ${name} extends AggregateRoot {
2371
+ export class ${name} extends AggregateRoot<Id> {
2119
2372
  private props: ${name}Props
2120
2373
 
2121
2374
  private constructor(id: Id, props: ${name}Props) {
@@ -2160,14 +2413,14 @@ export enum ${name}Status {
2160
2413
  * ${name} Created Event
2161
2414
  */
2162
2415
 
2163
- import { DomainEvent } from '../../../../Shared/Domain/Events/DomainEvent'
2416
+ import { DomainEvent } from '@gravito/enterprise'
2164
2417
 
2165
2418
  export class ${name}Created extends DomainEvent {
2166
2419
  constructor(public readonly ${name.toLowerCase()}Id: string) {
2167
2420
  super()
2168
2421
  }
2169
2422
 
2170
- get eventName(): string {
2423
+ override get eventName(): string {
2171
2424
  return '${name.toLowerCase()}.created'
2172
2425
  }
2173
2426
 
@@ -2182,12 +2435,12 @@ export class ${name}Created extends DomainEvent {
2182
2435
  * ${name} Repository Interface
2183
2436
  */
2184
2437
 
2438
+ import { Repository } from '@gravito/enterprise'
2185
2439
  import type { ${name} } from '../Aggregates/${name}/${name}'
2440
+ import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2186
2441
 
2187
- export interface I${name}Repository {
2188
- findById(id: string): Promise<${name} | null>
2189
- save(aggregate: ${name}): Promise<void>
2190
- delete(id: string): Promise<void>
2442
+ export interface I${name}Repository extends Repository<${name}, Id> {
2443
+ // Add specific methods for this repository if needed
2191
2444
  }
2192
2445
  `;
2193
2446
  }
@@ -2196,11 +2449,15 @@ export interface I${name}Repository {
2196
2449
  * Create ${name} Command
2197
2450
  */
2198
2451
 
2199
- export class Create${name}Command {
2452
+ import { Command } from '@gravito/enterprise'
2453
+
2454
+ export class Create${name}Command extends Command {
2200
2455
  constructor(
2201
2456
  // Add command properties
2202
2457
  public readonly id?: string
2203
- ) {}
2458
+ ) {
2459
+ super()
2460
+ }
2204
2461
  }
2205
2462
  `;
2206
2463
  }
@@ -2209,12 +2466,13 @@ export class Create${name}Command {
2209
2466
  * Create ${name} Handler
2210
2467
  */
2211
2468
 
2469
+ import { CommandHandler } from '@gravito/enterprise'
2212
2470
  import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
2213
2471
  import { ${name} } from '../../../Domain/Aggregates/${name}/${name}'
2214
2472
  import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2215
2473
  import type { Create${name}Command } from './Create${name}Command'
2216
2474
 
2217
- export class Create${name}Handler {
2475
+ export class Create${name}Handler implements CommandHandler<Create${name}Command, string> {
2218
2476
  constructor(private repository: I${name}Repository) {}
2219
2477
 
2220
2478
  async handle(command: Create${name}Command): Promise<string> {
@@ -2233,8 +2491,12 @@ export class Create${name}Handler {
2233
2491
  * Get ${name} By Id Query
2234
2492
  */
2235
2493
 
2236
- export class Get${name}ByIdQuery {
2237
- constructor(public readonly id: string) {}
2494
+ import { Query } from '@gravito/enterprise'
2495
+
2496
+ export class Get${name}ByIdQuery extends Query {
2497
+ constructor(public readonly id: string) {
2498
+ super()
2499
+ }
2238
2500
  }
2239
2501
  `;
2240
2502
  }
@@ -2243,15 +2505,16 @@ export class Get${name}ByIdQuery {
2243
2505
  * Get ${name} By Id Handler
2244
2506
  */
2245
2507
 
2508
+ import { QueryHandler } from '@gravito/enterprise'
2246
2509
  import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
2247
2510
  import type { ${name}DTO } from '../../DTOs/${name}DTO'
2248
2511
  import type { Get${name}ByIdQuery } from './Get${name}ByIdQuery'
2249
2512
 
2250
- export class Get${name}ByIdHandler {
2513
+ export class Get${name}ByIdHandler implements QueryHandler<Get${name}ByIdQuery, ${name}DTO | null> {
2251
2514
  constructor(private repository: I${name}Repository) {}
2252
2515
 
2253
2516
  async handle(query: Get${name}ByIdQuery): Promise<${name}DTO | null> {
2254
- const aggregate = await this.repository.findById(query.id)
2517
+ const aggregate = await this.repository.findById(query.id as any) // Simplified for demo
2255
2518
  if (!aggregate) return null
2256
2519
 
2257
2520
  return {
@@ -2283,20 +2546,29 @@ export interface ${name}DTO {
2283
2546
 
2284
2547
  import type { ${name} } from '../../Domain/Aggregates/${name}/${name}'
2285
2548
  import type { I${name}Repository } from '../../Domain/Repositories/I${name}Repository'
2549
+ import type { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2286
2550
 
2287
2551
  const store = new Map<string, ${name}>()
2288
2552
 
2289
2553
  export class ${name}Repository implements I${name}Repository {
2290
- async findById(id: string): Promise<${name} | null> {
2291
- return store.get(id) ?? null
2554
+ async findById(id: Id): Promise<${name} | null> {
2555
+ return store.get(id.value) ?? null
2292
2556
  }
2293
2557
 
2294
2558
  async save(aggregate: ${name}): Promise<void> {
2295
2559
  store.set(aggregate.id.value, aggregate)
2296
2560
  }
2297
2561
 
2298
- async delete(id: string): Promise<void> {
2299
- store.delete(id)
2562
+ async delete(id: Id): Promise<void> {
2563
+ store.delete(id.value)
2564
+ }
2565
+
2566
+ async findAll(): Promise<${name}[]> {
2567
+ return Array.from(store.values())
2568
+ }
2569
+
2570
+ async exists(id: Id): Promise<boolean> {
2571
+ return store.has(id.value)
2300
2572
  }
2301
2573
  }
2302
2574
  `;
@@ -2739,7 +3011,7 @@ export default {
2739
3011
  * can be assigned to specific routes.
2740
3012
  */
2741
3013
 
2742
- import type { GravitoMiddleware } from 'gravito-core'
3014
+ import type { GravitoMiddleware } from '@gravito/core'
2743
3015
 
2744
3016
  /**
2745
3017
  * Global middleware stack.
@@ -2828,7 +3100,7 @@ export abstract class Controller {
2828
3100
  * Home Controller
2829
3101
  */
2830
3102
 
2831
- import type { GravitoContext } from 'gravito-core'
3103
+ import type { GravitoContext } from '@gravito/core'
2832
3104
  import { Controller } from './Controller'
2833
3105
 
2834
3106
  export class HomeController extends Controller {
@@ -2862,7 +3134,7 @@ export class HomeController extends Controller {
2862
3134
  * Protects routes that require authentication.
2863
3135
  */
2864
3136
 
2865
- import type { GravitoContext, GravitoNext } from 'gravito-core'
3137
+ import type { GravitoContext, GravitoNext } from '@gravito/core'
2866
3138
 
2867
3139
  export async function Authenticate(c: GravitoContext, next: GravitoNext) {
2868
3140
  // TODO: Implement authentication check
@@ -2872,6 +3144,7 @@ export async function Authenticate(c: GravitoContext, next: GravitoNext) {
2872
3144
  // }
2873
3145
 
2874
3146
  await next()
3147
+ return undefined
2875
3148
  }
2876
3149
  `;
2877
3150
  }
@@ -2883,7 +3156,7 @@ export async function Authenticate(c: GravitoContext, next: GravitoNext) {
2883
3156
  * Register and bootstrap application services here.
2884
3157
  */
2885
3158
 
2886
- import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
3159
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
2887
3160
 
2888
3161
  export class AppServiceProvider extends ServiceProvider {
2889
3162
  /**
@@ -2911,7 +3184,7 @@ export class AppServiceProvider extends ServiceProvider {
2911
3184
  * Configures and registers application routes.
2912
3185
  */
2913
3186
 
2914
- import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
3187
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
2915
3188
  import { registerRoutes } from '../routes'
2916
3189
 
2917
3190
  export class RouteServiceProvider extends ServiceProvider {
@@ -2939,7 +3212,7 @@ export class RouteServiceProvider extends ServiceProvider {
2939
3212
  * Customize error responses and logging here.
2940
3213
  */
2941
3214
 
2942
- import type { ErrorHandlerContext } from 'gravito-core'
3215
+ import type { ErrorHandlerContext } from '@gravito/core'
2943
3216
 
2944
3217
  /**
2945
3218
  * Report an exception (logging, monitoring, etc.)
@@ -2976,6 +3249,13 @@ export const dontReport: string[] = [
2976
3249
  `;
2977
3250
  }
2978
3251
  generateBootstrap(context) {
3252
+ const spectrumImport = context.withSpectrum ? "import { SpectrumOrbit } from '@gravito/spectrum'\n" : "";
3253
+ const spectrumOrbit = context.withSpectrum ? `
3254
+ // Enable Debug Dashboard
3255
+ if (process.env.APP_DEBUG === 'true') {
3256
+ await core.orbit(new SpectrumOrbit())
3257
+ }
3258
+ ` : "";
2979
3259
  return `/**
2980
3260
  * Application Bootstrap
2981
3261
  *
@@ -2983,8 +3263,10 @@ export const dontReport: string[] = [
2983
3263
  * It initializes the core and registers all providers.
2984
3264
  */
2985
3265
 
2986
- import { PlanetCore } from 'gravito-core'
2987
- import { AppServiceProvider } from './Providers/AppServiceProvider'
3266
+ import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
3267
+ import { OrbitAtlas } from '@gravito/atlas'
3268
+ import databaseConfig from '../config/database'
3269
+ ${spectrumImport}import { AppServiceProvider } from './Providers/AppServiceProvider'
2988
3270
  import { RouteServiceProvider } from './Providers/RouteServiceProvider'
2989
3271
 
2990
3272
  // Load environment variables
@@ -2993,10 +3275,41 @@ import { RouteServiceProvider } from './Providers/RouteServiceProvider'
2993
3275
  // Create application core
2994
3276
  const core = new PlanetCore({
2995
3277
  config: {
2996
- APP_NAME: '${context.name}',
3278
+ APP_NAME: process.env.APP_NAME ?? '${context.name}',
3279
+ database: databaseConfig,
2997
3280
  },
2998
3281
  })
3282
+ const defaultCsp = [
3283
+ "default-src 'self'",
3284
+ "script-src 'self' 'unsafe-inline'",
3285
+ "style-src 'self' 'unsafe-inline'",
3286
+ "img-src 'self' data:",
3287
+ "object-src 'none'",
3288
+ "base-uri 'self'",
3289
+ "frame-ancestors 'none'",
3290
+ ].join('; ')
3291
+ const cspValue = process.env.APP_CSP
3292
+ const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
3293
+ const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
3294
+ const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
3295
+ const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
3296
+
3297
+ core.adapter.use(
3298
+ '*',
3299
+ securityHeaders({
3300
+ contentSecurityPolicy: csp,
3301
+ hsts:
3302
+ process.env.NODE_ENV === 'production'
3303
+ ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
3304
+ : false,
3305
+ })
3306
+ )
3307
+ if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
3308
+ core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
3309
+ }
2999
3310
 
3311
+ await core.orbit(new OrbitAtlas())
3312
+ ${spectrumOrbit}
3000
3313
  // Register service providers
3001
3314
  core.register(new AppServiceProvider())
3002
3315
  core.register(new RouteServiceProvider())
@@ -3167,8 +3480,829 @@ Created with \u2764\uFE0F using Gravito Framework
3167
3480
  }
3168
3481
  };
3169
3482
 
3483
+ // src/generators/SatelliteGenerator.ts
3484
+ var SatelliteGenerator = class extends BaseGenerator {
3485
+ get architectureType() {
3486
+ return "satellite";
3487
+ }
3488
+ get displayName() {
3489
+ return "Gravito Satellite";
3490
+ }
3491
+ get description() {
3492
+ return "A modular plugin for Gravito following DDD and Clean Architecture";
3493
+ }
3494
+ getDirectoryStructure(context) {
3495
+ const name = context.namePascalCase;
3496
+ return [
3497
+ {
3498
+ type: "directory",
3499
+ name: "src",
3500
+ children: [
3501
+ // Domain Layer
3502
+ {
3503
+ type: "directory",
3504
+ name: "Domain",
3505
+ children: [
3506
+ {
3507
+ type: "directory",
3508
+ name: "Entities",
3509
+ children: [
3510
+ { type: "file", name: `${name}.ts`, content: this.generateEntity(name) }
3511
+ ]
3512
+ },
3513
+ {
3514
+ type: "directory",
3515
+ name: "Contracts",
3516
+ children: [
3517
+ {
3518
+ type: "file",
3519
+ name: `I${name}Repository.ts`,
3520
+ content: this.generateRepositoryInterface(name)
3521
+ }
3522
+ ]
3523
+ },
3524
+ { type: "directory", name: "ValueObjects", children: [] },
3525
+ { type: "directory", name: "Events", children: [] }
3526
+ ]
3527
+ },
3528
+ // Application Layer
3529
+ {
3530
+ type: "directory",
3531
+ name: "Application",
3532
+ children: [
3533
+ {
3534
+ type: "directory",
3535
+ name: "UseCases",
3536
+ children: [
3537
+ {
3538
+ type: "file",
3539
+ name: `Create${name}.ts`,
3540
+ content: this.generateUseCase(name)
3541
+ }
3542
+ ]
3543
+ },
3544
+ { type: "directory", name: "DTOs", children: [] }
3545
+ ]
3546
+ },
3547
+ // Infrastructure Layer
3548
+ {
3549
+ type: "directory",
3550
+ name: "Infrastructure",
3551
+ children: [
3552
+ {
3553
+ type: "directory",
3554
+ name: "Persistence",
3555
+ children: [
3556
+ {
3557
+ type: "file",
3558
+ name: `Atlas${name}Repository.ts`,
3559
+ content: this.generateAtlasRepository(name)
3560
+ },
3561
+ { type: "directory", name: "Migrations", children: [] }
3562
+ ]
3563
+ }
3564
+ ]
3565
+ },
3566
+ // Entry Point
3567
+ { type: "file", name: "index.ts", content: this.generateEntryPoint(name) },
3568
+ {
3569
+ type: "file",
3570
+ name: "env.d.ts",
3571
+ content: "interface ImportMeta {\n readonly dir: string\n readonly path: string\n}\n"
3572
+ },
3573
+ { type: "file", name: "manifest.json", content: this.generateManifest(context) }
3574
+ ]
3575
+ },
3576
+ {
3577
+ type: "directory",
3578
+ name: "tests",
3579
+ children: [
3580
+ {
3581
+ type: "file",
3582
+ name: "unit.test.ts",
3583
+ content: `import { describe, it, expect } from "bun:test";
3584
+
3585
+ describe("${name}", () => {
3586
+ it("should work", () => {
3587
+ expect(true).toBe(true);
3588
+ });
3589
+ });`
3590
+ }
3591
+ ]
3592
+ }
3593
+ ];
3594
+ }
3595
+ // ─────────────────────────────────────────────────────────────
3596
+ // Domain Templates
3597
+ // ─────────────────────────────────────────────────────────────
3598
+ generateEntity(name) {
3599
+ return `import { Entity } from '@gravito/enterprise'
3600
+
3601
+ export interface ${name}Props {
3602
+ name: string
3603
+ createdAt: Date
3604
+ }
3605
+
3606
+ export class ${name} extends Entity<string> {
3607
+ constructor(id: string, private props: ${name}Props) {
3608
+ super(id)
3609
+ }
3610
+
3611
+ static create(id: string, name: string): ${name} {
3612
+ return new ${name}(id, {
3613
+ name,
3614
+ createdAt: new Date()
3615
+ })
3616
+ }
3617
+
3618
+ get name() { return this.props.name }
3619
+ }
3620
+ `;
3621
+ }
3622
+ generateRepositoryInterface(name) {
3623
+ return `import { Repository } from '@gravito/enterprise'
3624
+ import { ${name} } from '../Entities/${name}'
3625
+
3626
+ export interface I${name}Repository extends Repository<${name}, string> {
3627
+ // Add custom methods here
3628
+ }
3629
+ `;
3630
+ }
3631
+ // ─────────────────────────────────────────────────────────────
3632
+ // Application Templates
3633
+ // ─────────────────────────────────────────────────────────────
3634
+ generateUseCase(name) {
3635
+ return `import { UseCase } from '@gravito/enterprise'
3636
+ import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
3637
+ import { ${name} } from '../../Domain/Entities/${name}'
3638
+
3639
+ export interface Create${name}Input {
3640
+ name: string
3641
+ }
3642
+
3643
+ export class Create${name} extends UseCase<Create${name}Input, string> {
3644
+ constructor(private repository: I${name}Repository) {
3645
+ super()
3646
+ }
3647
+
3648
+ async execute(input: Create${name}Input): Promise<string> {
3649
+ const id = crypto.randomUUID()
3650
+ const entity = ${name}.create(id, input.name)
3651
+
3652
+ await this.repository.save(entity)
3653
+
3654
+ return id
3655
+ }
3656
+ }
3657
+ `;
3658
+ }
3659
+ // ─────────────────────────────────────────────────────────────
3660
+ // Infrastructure Templates (Dogfooding Atlas)
3661
+ // ─────────────────────────────────────────────────────────────
3662
+ generateAtlasRepository(name) {
3663
+ return `import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
3664
+ import { ${name} } from '../../Domain/Entities/${name}'
3665
+ import { DB } from '@gravito/atlas'
3666
+
3667
+ export class Atlas${name}Repository implements I${name}Repository {
3668
+ async save(entity: ${name}): Promise<void> {
3669
+ // Dogfooding: Use @gravito/atlas for persistence
3670
+ console.log('[Atlas] Saving entity:', entity.id)
3671
+ // await DB.table('${name.toLowerCase()}s').insert({ ... })
3672
+ }
3673
+
3674
+ async findById(id: string): Promise<${name} | null> {
3675
+ return null
3676
+ }
3677
+
3678
+ async findAll(): Promise<${name}[]> {
3679
+ return []
3680
+ }
3681
+
3682
+ async delete(id: string): Promise<void> {}
3683
+
3684
+ async exists(id: string): Promise<boolean> {
3685
+ return false
3686
+ }
3687
+ }
3688
+ `;
3689
+ }
3690
+ // ─────────────────────────────────────────────────────────────
3691
+ // Entry Point & Manifest
3692
+ // ─────────────────────────────────────────────────────────────
3693
+ generateEntryPoint(name) {
3694
+ return `import { ServiceProvider, type Container } from '@gravito/core'
3695
+ import { Atlas${name}Repository } from './Infrastructure/Persistence/Atlas${name}Repository'
3696
+
3697
+ export class ${name}ServiceProvider extends ServiceProvider {
3698
+ register(container: Container): void {
3699
+ // Bind Repository
3700
+ container.singleton('${name.toLowerCase()}.repo', () => new Atlas${name}Repository())
3701
+
3702
+ // Bind UseCases
3703
+ container.singleton('${name.toLowerCase()}.create', () => {
3704
+ return new (require('./Application/UseCases/Create${name}').Create${name})(
3705
+ container.make('${name.toLowerCase()}.repo')
3706
+ )
3707
+ })
3708
+ }
3709
+
3710
+ boot(): void {
3711
+ this.core?.logger.info('\u{1F6F0}\uFE0F Satellite ${name} is operational')
3712
+ }
3713
+ }
3714
+ `;
3715
+ }
3716
+ generateManifest(context) {
3717
+ return JSON.stringify(
3718
+ {
3719
+ name: context.name,
3720
+ id: context.nameKebabCase,
3721
+ version: "0.1.0",
3722
+ description: context.description || "A Gravito Satellite",
3723
+ capabilities: [`create-${context.nameKebabCase}`],
3724
+ requirements: [
3725
+ "cache"
3726
+ // Example requirement
3727
+ ],
3728
+ hooks: [`${context.nameKebabCase}:created`]
3729
+ },
3730
+ null,
3731
+ 2
3732
+ );
3733
+ }
3734
+ generatePackageJson(context) {
3735
+ const isInternal = context.isInternal || false;
3736
+ const depVersion = isInternal ? "workspace:*" : "^1.0.0-beta.1";
3737
+ const pkg = {
3738
+ name: isInternal ? `@gravito/satellite-${context.nameKebabCase}` : `gravito-satellite-${context.nameKebabCase}`,
3739
+ version: "0.1.0",
3740
+ type: "module",
3741
+ main: "dist/index.js",
3742
+ module: "dist/index.mjs",
3743
+ types: "dist/index.d.ts",
3744
+ scripts: {
3745
+ build: "tsup src/index.ts --format cjs,esm --dts",
3746
+ test: "bun test",
3747
+ typecheck: "tsc --noEmit"
3748
+ },
3749
+ dependencies: {
3750
+ "@gravito/core": depVersion,
3751
+ "@gravito/enterprise": depVersion,
3752
+ "@gravito/atlas": depVersion,
3753
+ "@gravito/stasis": depVersion
3754
+ },
3755
+ devDependencies: {
3756
+ tsup: "^8.0.0",
3757
+ typescript: "^5.0.0"
3758
+ }
3759
+ };
3760
+ return JSON.stringify(pkg, null, 2);
3761
+ }
3762
+ generateArchitectureDoc(context) {
3763
+ return `# ${context.name} Satellite Architecture
3764
+
3765
+ This satellite follows the Gravito Satellite Specification v1.0.
3766
+
3767
+ ## Design
3768
+ - **DDD**: Domain logic is separated from framework concerns.
3769
+ - **Dogfooding**: Uses official Gravito modules (@gravito/atlas, @gravito/stasis).
3770
+ - **Decoupled**: Inter-satellite communication happens via Contracts and Events.
3771
+
3772
+ ## Layers
3773
+ - **Domain**: Pure business rules.
3774
+ - **Application**: Orchestration of domain tasks.
3775
+ - **Infrastructure**: Implementation of persistence and external services.
3776
+ - **Interface**: HTTP and Event entry points.
3777
+ `;
3778
+ }
3779
+ };
3780
+
3781
+ // src/LockGenerator.ts
3782
+ var LockGenerator = class {
3783
+ generate(profileName, config, templateName = "basic", templateVersion = "1.0.0") {
3784
+ const lock = {
3785
+ profile: {
3786
+ name: profileName,
3787
+ version: "1.0.0"
3788
+ // This could be dynamic based on profile system version
3789
+ },
3790
+ features: config.features,
3791
+ drivers: config.drivers,
3792
+ manifest: {
3793
+ template: templateName,
3794
+ version: templateVersion
3795
+ },
3796
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3797
+ };
3798
+ return JSON.stringify(lock, null, 2);
3799
+ }
3800
+ };
3801
+
3802
+ // src/ProfileResolver.ts
3803
+ var ProfileResolver = class _ProfileResolver {
3804
+ static DEFAULTS = {
3805
+ core: {
3806
+ drivers: {
3807
+ database: "sqlite",
3808
+ cache: "memory",
3809
+ queue: "sync",
3810
+ storage: "local",
3811
+ session: "file"
3812
+ },
3813
+ features: []
3814
+ },
3815
+ scale: {
3816
+ drivers: {
3817
+ database: "postgresql",
3818
+ cache: "redis",
3819
+ queue: "redis",
3820
+ storage: "s3",
3821
+ session: "redis"
3822
+ },
3823
+ features: ["stream", "nebula"]
3824
+ },
3825
+ enterprise: {
3826
+ drivers: {
3827
+ database: "postgresql",
3828
+ cache: "redis",
3829
+ queue: "redis",
3830
+ storage: "s3",
3831
+ session: "redis"
3832
+ },
3833
+ features: ["stream", "nebula", "monitor", "sentinel", "fortify"]
3834
+ }
3835
+ };
3836
+ resolve(profile = "core", withFeatures = []) {
3837
+ const base = _ProfileResolver.DEFAULTS[profile] || _ProfileResolver.DEFAULTS.core;
3838
+ const config = {
3839
+ drivers: { ...base.drivers },
3840
+ features: [...base.features]
3841
+ };
3842
+ for (const feature of withFeatures) {
3843
+ this.applyFeature(config, feature);
3844
+ }
3845
+ return config;
3846
+ }
3847
+ applyFeature(config, feature) {
3848
+ switch (feature) {
3849
+ case "redis":
3850
+ config.drivers.cache = "redis";
3851
+ config.drivers.queue = "redis";
3852
+ config.drivers.session = "redis";
3853
+ break;
3854
+ case "postgres":
3855
+ case "postgresql":
3856
+ config.drivers.database = "postgresql";
3857
+ break;
3858
+ case "mysql":
3859
+ config.drivers.database = "mysql";
3860
+ break;
3861
+ case "s3":
3862
+ config.drivers.storage = "s3";
3863
+ break;
3864
+ case "r2":
3865
+ config.drivers.storage = "r2";
3866
+ break;
3867
+ case "queue":
3868
+ if (config.drivers.queue === "sync") {
3869
+ config.drivers.queue = "redis";
3870
+ }
3871
+ break;
3872
+ default:
3873
+ if (!config.features.includes(feature)) {
3874
+ config.features.push(feature);
3875
+ }
3876
+ }
3877
+ }
3878
+ /**
3879
+ * Validator for profile names
3880
+ */
3881
+ isValidProfile(profile) {
3882
+ return profile in _ProfileResolver.DEFAULTS;
3883
+ }
3884
+ /**
3885
+ * Validator for feature names
3886
+ */
3887
+ isValidFeature(feature) {
3888
+ const validFeatures = [
3889
+ "redis",
3890
+ "postgres",
3891
+ "postgresql",
3892
+ "mysql",
3893
+ "s3",
3894
+ "r2",
3895
+ "queue",
3896
+ "stream",
3897
+ "nebula",
3898
+ "monitor",
3899
+ "sentinel",
3900
+ "fortify"
3901
+ ];
3902
+ return validFeatures.includes(feature);
3903
+ }
3904
+ };
3905
+
3170
3906
  // src/Scaffold.ts
3171
3907
  var import_node_path3 = __toESM(require("path"), 1);
3908
+
3909
+ // src/generators/ActionDomainGenerator.ts
3910
+ var ActionDomainGenerator = class extends BaseGenerator {
3911
+ get architectureType() {
3912
+ return "action-domain";
3913
+ }
3914
+ get displayName() {
3915
+ return "Action Domain";
3916
+ }
3917
+ get description() {
3918
+ return "Single-responsibility Action pattern for clear business logic separation";
3919
+ }
3920
+ getDirectoryStructure(context) {
3921
+ return [
3922
+ {
3923
+ type: "directory",
3924
+ name: "config",
3925
+ children: [
3926
+ { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
3927
+ { type: "file", name: "database.ts", content: this.generateDatabaseConfig() }
3928
+ ]
3929
+ },
3930
+ {
3931
+ type: "directory",
3932
+ name: "database",
3933
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
3934
+ },
3935
+ {
3936
+ type: "directory",
3937
+ name: "src",
3938
+ children: [
3939
+ {
3940
+ type: "directory",
3941
+ name: "actions",
3942
+ children: [
3943
+ { type: "file", name: "Action.ts", content: this.generateActionBase() },
3944
+ {
3945
+ type: "directory",
3946
+ name: "server",
3947
+ children: [
3948
+ {
3949
+ type: "file",
3950
+ name: "GetServerStatusAction.ts",
3951
+ content: this.generateGetServerStatusAction()
3952
+ }
3953
+ ]
3954
+ }
3955
+ ]
3956
+ },
3957
+ {
3958
+ type: "directory",
3959
+ name: "controllers",
3960
+ children: [
3961
+ {
3962
+ type: "directory",
3963
+ name: "api",
3964
+ children: [
3965
+ {
3966
+ type: "directory",
3967
+ name: "v1",
3968
+ children: [
3969
+ {
3970
+ type: "file",
3971
+ name: "ServerController.ts",
3972
+ content: this.generateServerController()
3973
+ }
3974
+ ]
3975
+ }
3976
+ ]
3977
+ }
3978
+ ]
3979
+ },
3980
+ {
3981
+ type: "directory",
3982
+ name: "types",
3983
+ children: [
3984
+ {
3985
+ type: "directory",
3986
+ name: "requests",
3987
+ children: [
3988
+ {
3989
+ type: "directory",
3990
+ name: "api",
3991
+ children: [
3992
+ {
3993
+ type: "directory",
3994
+ name: "v1",
3995
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
3996
+ }
3997
+ ]
3998
+ }
3999
+ ]
4000
+ },
4001
+ {
4002
+ type: "directory",
4003
+ name: "responses",
4004
+ children: [
4005
+ {
4006
+ type: "directory",
4007
+ name: "api",
4008
+ children: [
4009
+ {
4010
+ type: "directory",
4011
+ name: "v1",
4012
+ children: [
4013
+ {
4014
+ type: "file",
4015
+ name: "ServerStatusResponse.ts",
4016
+ content: this.generateServerStatusResponse()
4017
+ }
4018
+ ]
4019
+ }
4020
+ ]
4021
+ }
4022
+ ]
4023
+ }
4024
+ ]
4025
+ },
4026
+ {
4027
+ type: "directory",
4028
+ name: "models",
4029
+ children: [{ type: "file", name: "User.ts", content: this.generateUserModel() }]
4030
+ },
4031
+ {
4032
+ type: "directory",
4033
+ name: "repositories",
4034
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
4035
+ },
4036
+ {
4037
+ type: "directory",
4038
+ name: "routes",
4039
+ children: [{ type: "file", name: "api.ts", content: this.generateApiRoutes() }]
4040
+ },
4041
+ {
4042
+ type: "directory",
4043
+ name: "providers",
4044
+ children: [
4045
+ {
4046
+ type: "file",
4047
+ name: "AppServiceProvider.ts",
4048
+ content: this.generateAppServiceProvider(context)
4049
+ }
4050
+ ]
4051
+ },
4052
+ { type: "file", name: "bootstrap.ts", content: this.generateBootstrap(context) }
4053
+ ]
4054
+ },
4055
+ {
4056
+ type: "directory",
4057
+ name: "tests",
4058
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
4059
+ }
4060
+ ];
4061
+ }
4062
+ // ─────────────────────────────────────────────────────────────
4063
+ // Config Generators
4064
+ // ─────────────────────────────────────────────────────────────
4065
+ generateAppConfig(context) {
4066
+ return `export default {
4067
+ name: process.env.APP_NAME ?? '${context.name}',
4068
+ env: process.env.APP_ENV ?? 'development',
4069
+ debug: process.env.APP_DEBUG === 'true',
4070
+ url: process.env.APP_URL ?? 'http://localhost:3000',
4071
+ key: process.env.APP_KEY,
4072
+ }
4073
+ `;
4074
+ }
4075
+ generateDatabaseConfig() {
4076
+ return `export default {
4077
+ default: process.env.DB_CONNECTION ?? 'sqlite',
4078
+ connections: {
4079
+ sqlite: {
4080
+ driver: 'sqlite',
4081
+ database: process.env.DB_DATABASE ?? 'database/database.sqlite',
4082
+ },
4083
+ },
4084
+ }
4085
+ `;
4086
+ }
4087
+ // ─────────────────────────────────────────────────────────────
4088
+ // Model Generators
4089
+ // ─────────────────────────────────────────────────────────────
4090
+ generateUserModel() {
4091
+ return `/**
4092
+ * User Model
4093
+ */
4094
+
4095
+ import { Model, column } from '@gravito/atlas'
4096
+
4097
+ export class User extends Model {
4098
+ static table = 'users'
4099
+
4100
+ @column({ isPrimary: true })
4101
+ id!: number
4102
+
4103
+ @column()
4104
+ name!: string
4105
+
4106
+ @column()
4107
+ email!: string
4108
+
4109
+ @column()
4110
+ created_at!: Date
4111
+
4112
+ @column()
4113
+ updated_at!: Date
4114
+ }
4115
+ `;
4116
+ }
4117
+ // ─────────────────────────────────────────────────────────────
4118
+ // Action Generators
4119
+ // ─────────────────────────────────────────────────────────────
4120
+ generateActionBase() {
4121
+ return `/**
4122
+ * Action Base Class
4123
+ *
4124
+ * All business logic actions should extend this class.
4125
+ * It enforces a consistent execution method.
4126
+ */
4127
+
4128
+ export abstract class Action<TInput = unknown, TOutput = unknown> {
4129
+ /**
4130
+ * Execute the business logic.
4131
+ */
4132
+ abstract execute(input: TInput): Promise<TOutput> | TOutput
4133
+ }
4134
+ `;
4135
+ }
4136
+ generateGetServerStatusAction() {
4137
+ return `/**
4138
+ * Get Server Status Action
4139
+ */
4140
+
4141
+ import { Action } from '../Action'
4142
+ import type { ServerStatusResponse } from '../../types/responses/api/v1/ServerStatusResponse'
4143
+
4144
+ export class GetServerStatusAction extends Action<void, ServerStatusResponse> {
4145
+ execute(): Promise<ServerStatusResponse> {
4146
+ return Promise.resolve({
4147
+ status: 'active',
4148
+ timestamp: new Date().toISOString(),
4149
+ service: 'Gravito Hub'
4150
+ })
4151
+ }
4152
+ }
4153
+ `;
4154
+ }
4155
+ // ─────────────────────────────────────────────────────────────
4156
+ // Controller Generators
4157
+ // ─────────────────────────────────────────────────────────────
4158
+ generateServerController() {
4159
+ return `/**
4160
+ * Server Controller
4161
+ */
4162
+
4163
+ import type { GravitoContext } from '@gravito/core'
4164
+ import { GetServerStatusAction } from '../../../actions/server/GetServerStatusAction'
4165
+
4166
+ export class ServerController {
4167
+ /**
4168
+ * GET /v1/server/status
4169
+ */
4170
+ async status(c: GravitoContext) {
4171
+ const action = new GetServerStatusAction()
4172
+ const result = await action.execute()
4173
+
4174
+ return c.json({
4175
+ success: true,
4176
+ data: result
4177
+ })
4178
+ }
4179
+ }
4180
+ `;
4181
+ }
4182
+ // ─────────────────────────────────────────────────────────────
4183
+ // Type Generators
4184
+ // ─────────────────────────────────────────────────────────────
4185
+ generateServerStatusResponse() {
4186
+ return `/**
4187
+ * Server Status Response Type
4188
+ */
4189
+
4190
+ export interface ServerStatusResponse {
4191
+ status: string
4192
+ timestamp: string
4193
+ service: string
4194
+ }
4195
+ `;
4196
+ }
4197
+ // ─────────────────────────────────────────────────────────────
4198
+ // Routes & Bootstrap
4199
+ // ─────────────────────────────────────────────────────────────
4200
+ generateApiRoutes() {
4201
+ return `/**
4202
+ * API Routes Registration
4203
+ */
4204
+
4205
+ import type { Router } from '@gravito/core'
4206
+ import { ServerController } from '../controllers/api/v1/ServerController'
4207
+
4208
+ export function registerApiRoutes(router: Router) {
4209
+ const server = new ServerController()
4210
+
4211
+ router.prefix('/v1').group((group) => {
4212
+ // Server Domain
4213
+ group.get('/server/status', (c) => server.status(c))
4214
+ })
4215
+ }
4216
+ `;
4217
+ }
4218
+ generateAppServiceProvider(context) {
4219
+ return `/**
4220
+ * App Service Provider
4221
+ */
4222
+
4223
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
4224
+
4225
+ export class AppServiceProvider extends ServiceProvider {
4226
+ register(container: Container): void {
4227
+ // Register global services here
4228
+ }
4229
+
4230
+ boot(core: PlanetCore): void {
4231
+ core.logger.info('${context.name} (Action Domain) booted!')
4232
+ }
4233
+ }
4234
+ `;
4235
+ }
4236
+ generateBootstrap(context) {
4237
+ return `/**
4238
+ * Application Entry Point
4239
+ */
4240
+
4241
+ import { PlanetCore, securityHeaders, bodySizeLimit } from '@gravito/core'
4242
+ import { OrbitAtlas } from '@gravito/atlas'
4243
+ import databaseConfig from '../config/database'
4244
+ import { AppServiceProvider } from './providers/AppServiceProvider'
4245
+ import { registerApiRoutes } from './routes/api'
4246
+
4247
+ // Initialize Core
4248
+ const core = new PlanetCore({
4249
+ config: {
4250
+ APP_NAME: '${context.name}',
4251
+ database: databaseConfig
4252
+ },
4253
+ })
4254
+
4255
+ // Middleware
4256
+ core.adapter.use('*', securityHeaders())
4257
+ core.adapter.use('*', bodySizeLimit(1024 * 1024)) // 1MB
4258
+
4259
+ // Install Orbits
4260
+ await core.orbit(new OrbitAtlas())
4261
+
4262
+ // Service Providers
4263
+ core.register(new AppServiceProvider())
4264
+
4265
+ // Bootstrap
4266
+ await core.bootstrap()
4267
+
4268
+ // Routes
4269
+ registerApiRoutes(core.router)
4270
+
4271
+ // Liftoff
4272
+ export default core.liftoff()
4273
+ `;
4274
+ }
4275
+ generateArchitectureDoc(context) {
4276
+ return "# " + context.name + ' - Action Domain Architecture\n\n## Overview\n\nThis project uses the **Action Domain** pattern, designed for high-clarity API implementations.\n\n## Directory Structure\n\n```\nsrc/\n\u251C\u2500\u2500 actions/ # Single Responsibility Business Logic\n\u2502 \u251C\u2500\u2500 Action.ts # Base Action class\n\u2502 \u2514\u2500\u2500 [Domain]/ # Domain-specific actions\n\u251C\u2500\u2500 controllers/ # HTTP Request Handlers\n\u2502 \u2514\u2500\u2500 api/v1/ # API Controllers\n\u251C\u2500\u2500 types/ # TypeScript Definitions\n\u2502 \u251C\u2500\u2500 requests/ # Request Payloads\n\u2502 \u2514\u2500\u2500 responses/ # Response Structures\n\u251C\u2500\u2500 repositories/ # Data Access Layer\n\u251C\u2500\u2500 routes/ # Route Definitions\n\u2514\u2500\u2500 config/ # Configuration\n```\n\n## Core Concepts\n\n### Actions\nEvery business operation is an "Action". An action:\n- Does ONE thing.\n- Takes specific input.\n- Returns specific output.\n- Is framework-agnostic (ideally).\n\n### Controllers\nControllers are thin. They:\n1. Parse the request.\n2. Instantiate/Call the Action.\n3. Return the response.\n\nCreated with \u2764\uFE0F using Gravito Framework\n';
4277
+ }
4278
+ generatePackageJson(context) {
4279
+ const pkg = {
4280
+ name: context.nameKebabCase,
4281
+ version: "0.1.0",
4282
+ type: "module",
4283
+ scripts: {
4284
+ dev: "bun run --watch src/bootstrap.ts",
4285
+ build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
4286
+ start: "bun run dist/bootstrap.js",
4287
+ test: "bun test",
4288
+ typecheck: "tsc --noEmit"
4289
+ },
4290
+ dependencies: {
4291
+ "@gravito/core": "workspace:*",
4292
+ "@gravito/enterprise": "workspace:*",
4293
+ "@gravito/atlas": "workspace:*"
4294
+ // Usually needed for repositories
4295
+ },
4296
+ devDependencies: {
4297
+ "bun-types": "latest",
4298
+ typescript: "^5.0.0"
4299
+ }
4300
+ };
4301
+ return JSON.stringify(pkg, null, 2);
4302
+ }
4303
+ };
4304
+
4305
+ // src/Scaffold.ts
3172
4306
  var Scaffold = class {
3173
4307
  templatesDir;
3174
4308
  verbose;
@@ -3195,6 +4329,16 @@ var Scaffold = class {
3195
4329
  type: "ddd",
3196
4330
  name: "Domain-Driven Design",
3197
4331
  description: "Full DDD with Bounded Contexts and CQRS"
4332
+ },
4333
+ {
4334
+ type: "action-domain",
4335
+ name: "Action Domain",
4336
+ description: "Single Action Controllers pattern for high-clarity APIs"
4337
+ },
4338
+ {
4339
+ type: "satellite",
4340
+ name: "Gravito Satellite",
4341
+ description: "Plug-and-play module for the Gravito ecosystem"
3198
4342
  }
3199
4343
  ];
3200
4344
  }
@@ -3203,15 +4347,36 @@ var Scaffold = class {
3203
4347
  */
3204
4348
  async create(options) {
3205
4349
  const generator = this.createGenerator(options.architecture);
4350
+ const fs3 = await import("fs/promises");
4351
+ const profileResolver = new ProfileResolver();
4352
+ const profileConfig = profileResolver.resolve(options.profile, options.features);
3206
4353
  const context = BaseGenerator.createContext(
3207
4354
  options.name,
3208
4355
  options.targetDir,
3209
4356
  options.architecture,
3210
4357
  options.packageManager ?? "bun",
3211
- options.context ?? {}
4358
+ {
4359
+ ...options.context,
4360
+ withSpectrum: options.withSpectrum ?? false,
4361
+ isInternal: options.isInternal ?? false,
4362
+ profile: options.profile ?? "core",
4363
+ features: options.features ?? [],
4364
+ profileConfig
4365
+ }
3212
4366
  );
3213
4367
  try {
3214
4368
  const filesCreated = await generator.generate(context);
4369
+ const lockGenerator = new LockGenerator();
4370
+ const lockContent = lockGenerator.generate(
4371
+ options.profile ?? "core",
4372
+ profileConfig,
4373
+ "basic",
4374
+ // Default template for now, should come from options if applicable
4375
+ "1.0.0"
4376
+ );
4377
+ const lockPath = import_node_path3.default.resolve(options.targetDir, "gravito.lock.json");
4378
+ await fs3.writeFile(lockPath, lockContent, "utf-8");
4379
+ filesCreated.push(lockPath);
3215
4380
  return {
3216
4381
  success: true,
3217
4382
  targetDir: options.targetDir,
@@ -3241,6 +4406,10 @@ var Scaffold = class {
3241
4406
  return new CleanArchitectureGenerator(config);
3242
4407
  case "ddd":
3243
4408
  return new DddGenerator(config);
4409
+ case "action-domain":
4410
+ return new ActionDomainGenerator(config);
4411
+ case "satellite":
4412
+ return new SatelliteGenerator(config);
3244
4413
  default:
3245
4414
  throw new Error(`Unknown architecture type: ${type}`);
3246
4415
  }
@@ -3264,6 +4433,11 @@ var Scaffold = class {
3264
4433
  CleanArchitectureGenerator,
3265
4434
  DddGenerator,
3266
4435
  EnterpriseMvcGenerator,
4436
+ EnvironmentDetector,
4437
+ FileMerger,
4438
+ LockGenerator,
4439
+ ProfileResolver,
4440
+ SatelliteGenerator,
3267
4441
  Scaffold,
3268
4442
  StubGenerator
3269
4443
  });