@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.js CHANGED
@@ -1,3 +1,102 @@
1
+ // src/EnvironmentDetector.ts
2
+ var EnvironmentDetector = class {
3
+ detect() {
4
+ if (process.env.KUBERNETES_SERVICE_HOST) {
5
+ return {
6
+ platform: "k8s",
7
+ suggestedProfile: "enterprise",
8
+ confidence: "high",
9
+ reason: "Kubernetes environment detected (KUBERNETES_SERVICE_HOST)"
10
+ };
11
+ }
12
+ if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.AWS_EXECUTION_ENV) {
13
+ return {
14
+ platform: "aws",
15
+ suggestedProfile: "scale",
16
+ confidence: "high",
17
+ reason: "AWS Lambda environment detected"
18
+ };
19
+ }
20
+ if (process.env.VERCEL) {
21
+ return {
22
+ platform: "vercel",
23
+ suggestedProfile: "core",
24
+ confidence: "high",
25
+ reason: "Vercel environment detected"
26
+ };
27
+ }
28
+ if (process.env.RAILWAY_ENVIRONMENT || process.env.RENDER) {
29
+ return {
30
+ platform: "unknown",
31
+ // Generic cloud
32
+ suggestedProfile: "scale",
33
+ confidence: "medium",
34
+ reason: "Cloud container environment detected"
35
+ };
36
+ }
37
+ return {
38
+ platform: "unknown",
39
+ suggestedProfile: "core",
40
+ confidence: "low",
41
+ reason: "No specific cloud environment detected, defaulting to Core"
42
+ };
43
+ }
44
+ };
45
+
46
+ // src/utils/deepMerge.ts
47
+ function deepMerge(target, source) {
48
+ if (typeof target !== "object" || target === null) {
49
+ return source;
50
+ }
51
+ if (typeof source !== "object" || source === null) {
52
+ return source;
53
+ }
54
+ if (Array.isArray(target) && Array.isArray(source)) {
55
+ return Array.from(/* @__PURE__ */ new Set([...target, ...source]));
56
+ }
57
+ const output = { ...target };
58
+ for (const key of Object.keys(source)) {
59
+ if (source[key] instanceof Object && key in target) {
60
+ output[key] = deepMerge(target[key], source[key]);
61
+ } else {
62
+ output[key] = source[key];
63
+ }
64
+ }
65
+ return output;
66
+ }
67
+
68
+ // src/FileMerger.ts
69
+ var FileMerger = class {
70
+ /**
71
+ * Merge content of two files based on their type.
72
+ */
73
+ merge(fileName, baseContent, overlayContent) {
74
+ if (fileName.endsWith(".json")) {
75
+ return this.mergeJson(baseContent, overlayContent);
76
+ }
77
+ if (fileName.endsWith(".env") || fileName.endsWith(".env.example")) {
78
+ return this.mergeEnv(baseContent, overlayContent);
79
+ }
80
+ return overlayContent;
81
+ }
82
+ mergeJson(base, overlay) {
83
+ try {
84
+ const baseJson = JSON.parse(base);
85
+ const overlayJson = JSON.parse(overlay);
86
+ const merged = deepMerge(baseJson, overlayJson);
87
+ return JSON.stringify(merged, null, 2);
88
+ } catch (e) {
89
+ console.warn("Failed to merge JSON, returning overlay", e);
90
+ return overlay;
91
+ }
92
+ }
93
+ mergeEnv(base, overlay) {
94
+ return `${base}
95
+ # --- Overlay ---
96
+ ${overlay}`;
97
+ }
98
+ };
99
+
1
100
  // src/generators/BaseGenerator.ts
2
101
  import fs2 from "fs/promises";
3
102
  import path2 from "path";
@@ -155,9 +254,24 @@ var StubGenerator = class {
155
254
  };
156
255
 
157
256
  // src/generators/BaseGenerator.ts
257
+ async function walk(dir) {
258
+ const files = await fs2.readdir(dir);
259
+ const paths = [];
260
+ for (const file of files) {
261
+ const filePath = path2.join(dir, file);
262
+ const stat = await fs2.stat(filePath);
263
+ if (stat.isDirectory()) {
264
+ paths.push(...await walk(filePath));
265
+ } else {
266
+ paths.push(filePath);
267
+ }
268
+ }
269
+ return paths;
270
+ }
158
271
  var BaseGenerator = class {
159
272
  config;
160
273
  stubGenerator;
274
+ fileMerger;
161
275
  filesCreated = [];
162
276
  constructor(config) {
163
277
  this.config = config;
@@ -166,6 +280,7 @@ var BaseGenerator = class {
166
280
  outputDir: ""
167
281
  // Set per-generation
168
282
  });
283
+ this.fileMerger = new FileMerger();
169
284
  }
170
285
  /**
171
286
  * Generate the project scaffold.
@@ -178,6 +293,8 @@ var BaseGenerator = class {
178
293
  const structure = this.getDirectoryStructure(context);
179
294
  await this.createStructure(context.targetDir, structure, context);
180
295
  await this.generateCommonFiles(context);
296
+ await this.applyOverlays(context);
297
+ await this.applyFeatureOverlays(context);
181
298
  return this.filesCreated;
182
299
  }
183
300
  /**
@@ -222,19 +339,69 @@ var BaseGenerator = class {
222
339
  await this.writeFile(context.targetDir, ".env", this.generateEnvExample(context));
223
340
  await this.writeFile(context.targetDir, ".gitignore", this.generateGitignore());
224
341
  await this.writeFile(context.targetDir, "tsconfig.json", this.generateTsConfig());
342
+ await this.writeFile(context.targetDir, "Dockerfile", this.generateDockerfile(context));
343
+ await this.writeFile(context.targetDir, ".dockerignore", this.generateDockerIgnore());
225
344
  await this.writeFile(
226
345
  context.targetDir,
227
346
  "ARCHITECTURE.md",
228
347
  this.generateArchitectureDoc(context)
229
348
  );
230
349
  }
350
+ /**
351
+ * Apply profile-specific overlays
352
+ */
353
+ async applyOverlays(context) {
354
+ const profile = context.profile;
355
+ if (!profile) return;
356
+ const overlayDir = path2.resolve(this.config.templatesDir, "overlays", profile);
357
+ await this.copyOverlayDirectory(overlayDir, context);
358
+ }
359
+ /**
360
+ * Apply feature-specific overlays
361
+ */
362
+ async applyFeatureOverlays(context) {
363
+ const features = context.features || [];
364
+ for (const feature of features) {
365
+ const overlayDir = path2.resolve(this.config.templatesDir, "features", feature);
366
+ await this.copyOverlayDirectory(overlayDir, context);
367
+ }
368
+ }
369
+ /**
370
+ * Helper to copy/merge an overlay directory into the target
371
+ */
372
+ async copyOverlayDirectory(sourceDir, context) {
373
+ try {
374
+ await fs2.access(sourceDir);
375
+ } catch {
376
+ return;
377
+ }
378
+ const files = await walk(sourceDir);
379
+ for (const filePath of files) {
380
+ const relativePath = path2.relative(sourceDir, filePath);
381
+ let content = await fs2.readFile(filePath, "utf-8");
382
+ try {
383
+ content = this.stubGenerator.render(content, context);
384
+ } catch {
385
+ }
386
+ await this.writeFile(context.targetDir, relativePath, content);
387
+ }
388
+ }
231
389
  /**
232
390
  * Write a file and track it.
233
391
  */
234
392
  async writeFile(basePath, relativePath, content) {
235
393
  const fullPath = path2.resolve(basePath, relativePath);
236
394
  await fs2.mkdir(path2.dirname(fullPath), { recursive: true });
237
- await fs2.writeFile(fullPath, content, "utf-8");
395
+ let finalContent = content;
396
+ try {
397
+ const existingContent = await fs2.readFile(fullPath, "utf-8");
398
+ finalContent = this.fileMerger.merge(relativePath, existingContent, content);
399
+ if (finalContent !== content) {
400
+ this.log(`\u{1F504} Merged file: ${relativePath}`);
401
+ }
402
+ } catch {
403
+ }
404
+ await fs2.writeFile(fullPath, finalContent, "utf-8");
238
405
  this.filesCreated.push(fullPath);
239
406
  this.log(`\u{1F4C4} Created file: ${relativePath}`);
240
407
  }
@@ -242,6 +409,20 @@ var BaseGenerator = class {
242
409
  * Generate package.json content.
243
410
  */
244
411
  generatePackageJson(context) {
412
+ const profile = context.profile || "core";
413
+ const baseDependencies = {
414
+ "@gravito/core": "^1.0.0-beta.5",
415
+ "@gravito/atlas": "^1.0.0-beta.5",
416
+ "@gravito/plasma": "^1.0.0-beta.5",
417
+ "@gravito/stream": "^1.0.0-beta.5"
418
+ };
419
+ if (profile === "enterprise" || profile === "scale") {
420
+ baseDependencies["@gravito/quasar"] = "^1.0.0-beta.5";
421
+ baseDependencies["@gravito/horizon"] = "^1.0.0-beta.5";
422
+ }
423
+ if (context.withSpectrum) {
424
+ baseDependencies["@gravito/spectrum"] = "^1.0.0-beta.1";
425
+ }
245
426
  const pkg = {
246
427
  name: context.nameKebabCase,
247
428
  version: "0.1.0",
@@ -251,42 +432,193 @@ var BaseGenerator = class {
251
432
  build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
252
433
  start: "bun run dist/bootstrap.js",
253
434
  test: "bun test",
254
- typecheck: "tsc --noEmit"
255
- },
256
- dependencies: {
257
- "gravito-core": "^1.0.0-beta.5"
435
+ typecheck: "tsc --noEmit",
436
+ "docker:build": `docker build -t ${context.nameKebabCase} .`,
437
+ "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
258
438
  },
439
+ dependencies: baseDependencies,
259
440
  devDependencies: {
260
- "@types/bun": "latest",
441
+ "bun-types": "latest",
261
442
  typescript: "^5.0.0"
262
443
  }
263
444
  };
264
445
  return JSON.stringify(pkg, null, 2);
265
446
  }
447
+ /**
448
+ * Generate Dockerfile content.
449
+ */
450
+ generateDockerfile(context) {
451
+ const entrypoint = context.architecture === "ddd" ? "dist/main.js" : "dist/bootstrap.js";
452
+ return `FROM oven/bun:1.0 AS base
453
+ WORKDIR /usr/src/app
454
+
455
+ # Install dependencies
456
+ FROM base AS install
457
+ RUN mkdir -p /temp/dev
458
+ COPY package.json bun.lockb /temp/dev/
459
+ RUN cd /temp/dev && bun install --frozen-lockfile
460
+
461
+ # Build application
462
+ FROM base AS build
463
+ COPY --from=install /temp/dev/node_modules node_modules
464
+ COPY . .
465
+ ENV NODE_ENV=production
466
+ RUN bun run build
467
+
468
+ # Final production image
469
+ FROM base AS release
470
+ COPY --from=build /usr/src/app/${entrypoint} index.js
471
+ COPY --from=build /usr/src/app/package.json .
472
+
473
+ # Create a non-root user for security
474
+ USER bun
475
+ EXPOSE 3000/tcp
476
+ ENTRYPOINT [ "bun", "run", "index.js" ]
477
+ `;
478
+ }
479
+ /**
480
+ * Generate .dockerignore content.
481
+ */
482
+ generateDockerIgnore() {
483
+ return `node_modules
484
+ dist
485
+ .git
486
+ .env
487
+ *.log
488
+ .vscode
489
+ .idea
490
+ tests
491
+ `;
492
+ }
266
493
  /**
267
494
  * Generate .env.example content.
268
495
  */
269
496
  generateEnvExample(context) {
270
- return `# Application
271
- APP_NAME="${context.name}"
497
+ const profile = context.profile || "core";
498
+ let envContent = `# ============================================================================
499
+ # Application Configuration
500
+ # ============================================================================
501
+
502
+ APP_NAME=${context.name}
272
503
  APP_ENV=development
273
- APP_KEY=
274
504
  APP_DEBUG=true
275
505
  APP_URL=http://localhost:3000
506
+ APP_KEY=
276
507
 
277
- # Server
278
- PORT=3000
508
+ # ============================================================================
509
+ # Database Configuration
510
+ # ============================================================================
279
511
 
280
- # Database
281
- DB_CONNECTION=sqlite
512
+ # Database Connection (sqlite, postgres, mysql)
513
+ DB_CONNECTION=${profile === "core" ? "sqlite" : "postgres"}
514
+
515
+ # SQLite Configuration (when DB_CONNECTION=sqlite)
282
516
  DB_DATABASE=database/database.sqlite
283
517
 
284
- # Cache
285
- CACHE_DRIVER=memory
518
+ # PostgreSQL Configuration (when DB_CONNECTION=postgres)
519
+ ${profile !== "core" ? `DB_HOST=127.0.0.1
520
+ DB_PORT=5432
521
+ DB_DATABASE=${context.name}
522
+ DB_USERNAME=postgres
523
+ DB_PASSWORD=
524
+ DB_SSLMODE=prefer` : `# DB_HOST=127.0.0.1
525
+ # DB_PORT=5432
526
+ # DB_DATABASE=${context.name}
527
+ # DB_USERNAME=postgres
528
+ # DB_PASSWORD=
529
+ # DB_SSLMODE=prefer`}
530
+
531
+ # MySQL Configuration (when DB_CONNECTION=mysql)
532
+ # DB_HOST=127.0.0.1
533
+ # DB_PORT=3306
534
+ # DB_DATABASE=${context.name}
535
+ # DB_USERNAME=root
536
+ # DB_PASSWORD=
537
+
538
+ # ============================================================================
539
+ # Redis Configuration (@gravito/plasma)
540
+ # ============================================================================
541
+
542
+ # Default Redis Connection
543
+ REDIS_CONNECTION=default
544
+ REDIS_HOST=127.0.0.1
545
+ REDIS_PORT=6379
546
+ REDIS_PASSWORD=
547
+ REDIS_DB=0
548
+
549
+ # Redis Connection Options
550
+ REDIS_CONNECT_TIMEOUT=10000
551
+ REDIS_COMMAND_TIMEOUT=5000
552
+ REDIS_KEY_PREFIX=
553
+ REDIS_MAX_RETRIES=3
554
+ REDIS_RETRY_DELAY=1000
555
+
556
+ # Cache-specific Redis Connection (optional, falls back to default)
557
+ # REDIS_CACHE_HOST=127.0.0.1
558
+ # REDIS_CACHE_PORT=6379
559
+ # REDIS_CACHE_PASSWORD=
560
+ REDIS_CACHE_DB=1
561
+
562
+ # Queue-specific Redis Connection (optional, falls back to default)
563
+ # REDIS_QUEUE_HOST=127.0.0.1
564
+ # REDIS_QUEUE_PORT=6379
565
+ # REDIS_QUEUE_PASSWORD=
566
+ REDIS_QUEUE_DB=2
567
+
568
+ # ============================================================================
569
+ # Cache Configuration (@gravito/stasis)
570
+ # ============================================================================
571
+
572
+ # Cache Driver (memory, file, redis)
573
+ CACHE_DRIVER=${profile === "core" ? "memory" : "redis"}
574
+
575
+ # File Cache Path (when CACHE_DRIVER=file)
576
+ CACHE_PATH=storage/framework/cache
577
+
578
+ # Redis Cache Configuration (when CACHE_DRIVER=redis)
579
+ REDIS_CACHE_CONNECTION=cache
580
+ REDIS_CACHE_PREFIX=cache:
581
+
582
+ # ============================================================================
583
+ # Queue Configuration (@gravito/stream)
584
+ # ============================================================================
585
+
586
+ # Queue Connection (sync, memory, database, redis, kafka, sqs, rabbitmq)
587
+ QUEUE_CONNECTION=${profile === "core" ? "sync" : "redis"}
588
+
589
+ # Database Queue Configuration (when QUEUE_CONNECTION=database)
590
+ QUEUE_TABLE=jobs
591
+
592
+ # Redis Queue Configuration (when QUEUE_CONNECTION=redis)
593
+ REDIS_PREFIX=queue:
594
+
595
+ `;
596
+ if (profile === "enterprise" || profile === "scale") {
597
+ envContent += `# Kafka Queue Configuration (when QUEUE_CONNECTION=kafka)
598
+ # KAFKA_BROKERS=localhost:9092
599
+ # KAFKA_CONSUMER_GROUP_ID=gravito-workers
600
+ # KAFKA_CLIENT_ID=${context.name}
601
+
602
+ # AWS SQS Queue Configuration (when QUEUE_CONNECTION=sqs)
603
+ # AWS_REGION=us-east-1
604
+ # SQS_QUEUE_URL_PREFIX=
605
+ # SQS_VISIBILITY_TIMEOUT=30
606
+ # SQS_WAIT_TIME_SECONDS=20
607
+
608
+ # RabbitMQ Queue Configuration (when QUEUE_CONNECTION=rabbitmq)
609
+ # RABBITMQ_URL=amqp://localhost
610
+ # RABBITMQ_EXCHANGE=gravito.events
611
+ # RABBITMQ_EXCHANGE_TYPE=fanout
612
+
613
+ `;
614
+ }
615
+ envContent += `# ============================================================================
616
+ # Logging Configuration
617
+ # ============================================================================
286
618
 
287
- # Logging
288
619
  LOG_LEVEL=debug
289
620
  `;
621
+ return envContent;
290
622
  }
291
623
  /**
292
624
  * Generate .gitignore content.
@@ -338,6 +670,9 @@ coverage/
338
670
  strict: true,
339
671
  skipLibCheck: true,
340
672
  declaration: true,
673
+ experimentalDecorators: true,
674
+ emitDecoratorMetadata: true,
675
+ types: ["bun-types"],
341
676
  outDir: "./dist",
342
677
  rootDir: "./src",
343
678
  baseUrl: ".",
@@ -407,6 +742,11 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
407
742
  { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
408
743
  ]
409
744
  },
745
+ {
746
+ type: "directory",
747
+ name: "database",
748
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
749
+ },
410
750
  {
411
751
  type: "directory",
412
752
  name: "src",
@@ -419,16 +759,12 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
419
759
  {
420
760
  type: "directory",
421
761
  name: "Entities",
422
- children: [
423
- { type: "file", name: "Entity.ts", content: this.generateBaseEntity() },
424
- { type: "file", name: "User.ts", content: this.generateUserEntity() }
425
- ]
762
+ children: [{ type: "file", name: "User.ts", content: this.generateUserEntity() }]
426
763
  },
427
764
  {
428
765
  type: "directory",
429
766
  name: "ValueObjects",
430
767
  children: [
431
- { type: "file", name: "ValueObject.ts", content: this.generateBaseValueObject() },
432
768
  { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
433
769
  ]
434
770
  },
@@ -446,13 +782,7 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
446
782
  {
447
783
  type: "directory",
448
784
  name: "Exceptions",
449
- children: [
450
- {
451
- type: "file",
452
- name: "DomainException.ts",
453
- content: this.generateDomainException()
454
- }
455
- ]
785
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
456
786
  }
457
787
  ]
458
788
  },
@@ -673,42 +1003,6 @@ var CleanArchitectureGenerator = class extends BaseGenerator {
673
1003
  console: { driver: 'console', level: process.env.LOG_LEVEL ?? 'debug' },
674
1004
  },
675
1005
  }
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
1006
  `;
713
1007
  }
714
1008
  generateUserEntity() {
@@ -719,7 +1013,7 @@ export abstract class Entity<T> {
719
1013
  * Contains business logic related to users.
720
1014
  */
721
1015
 
722
- import { Entity } from './Entity'
1016
+ import { Entity } from '@gravito/enterprise'
723
1017
  import { Email } from '../ValueObjects/Email'
724
1018
 
725
1019
  export interface UserProps {
@@ -769,30 +1063,6 @@ export class User extends Entity<string> {
769
1063
  this.props.updatedAt = new Date()
770
1064
  }
771
1065
  }
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
1066
  `;
797
1067
  }
798
1068
  generateEmailValueObject() {
@@ -802,7 +1072,7 @@ export abstract class ValueObject<T> {
802
1072
  * Encapsulates email validation and comparison.
803
1073
  */
804
1074
 
805
- import { ValueObject } from './ValueObject'
1075
+ import { ValueObject } from '@gravito/enterprise'
806
1076
 
807
1077
  interface EmailProps {
808
1078
  value: string
@@ -843,43 +1113,11 @@ export class Email extends ValueObject<EmailProps> {
843
1113
  * Implementations are in Infrastructure layer.
844
1114
  */
845
1115
 
1116
+ import type { Repository } from '@gravito/enterprise'
846
1117
  import type { User } from '../Entities/User'
847
1118
 
848
- export interface IUserRepository {
849
- findById(id: string): Promise<User | null>
1119
+ export interface IUserRepository extends Repository<User, string> {
850
1120
  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
1121
  }
884
1122
  `;
885
1123
  }
@@ -893,6 +1131,7 @@ export class InvalidValueException extends DomainException {
893
1131
  * Application service for creating new users.
894
1132
  */
895
1133
 
1134
+ import { UseCase } from '@gravito/enterprise'
896
1135
  import { User } from '../../../Domain/Entities/User'
897
1136
  import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
898
1137
  import type { UserDTO } from '../../DTOs/UserDTO'
@@ -906,8 +1145,10 @@ export interface CreateUserOutput {
906
1145
  user: UserDTO
907
1146
  }
908
1147
 
909
- export class CreateUserUseCase {
910
- constructor(private userRepository: IUserRepository) {}
1148
+ export class CreateUserUseCase extends UseCase<CreateUserInput, CreateUserOutput> {
1149
+ constructor(private userRepository: IUserRepository) {
1150
+ super()
1151
+ }
911
1152
 
912
1153
  async execute(input: CreateUserInput): Promise<CreateUserOutput> {
913
1154
  // Check if email already exists
@@ -944,8 +1185,8 @@ export class CreateUserUseCase {
944
1185
  * Get User Use Case
945
1186
  */
946
1187
 
1188
+ import { UseCase } from '@gravito/enterprise'
947
1189
  import type { IUserRepository } from '../../../Domain/Interfaces/IUserRepository'
948
- import { EntityNotFoundException } from '../../../Domain/Exceptions/DomainException'
949
1190
  import type { UserDTO } from '../../DTOs/UserDTO'
950
1191
 
951
1192
  export interface GetUserInput {
@@ -956,14 +1197,16 @@ export interface GetUserOutput {
956
1197
  user: UserDTO
957
1198
  }
958
1199
 
959
- export class GetUserUseCase {
960
- constructor(private userRepository: IUserRepository) {}
1200
+ export class GetUserUseCase extends UseCase<GetUserInput, GetUserOutput> {
1201
+ constructor(private userRepository: IUserRepository) {
1202
+ super()
1203
+ }
961
1204
 
962
1205
  async execute(input: GetUserInput): Promise<GetUserOutput> {
963
1206
  const user = await this.userRepository.findById(input.id)
964
1207
 
965
1208
  if (!user) {
966
- throw new EntityNotFoundException('User', input.id)
1209
+ throw new Error(\`User with id \${input.id} not found\`)
967
1210
  }
968
1211
 
969
1212
  return {
@@ -1064,6 +1307,10 @@ export class UserRepository implements IUserRepository {
1064
1307
  async findAll(): Promise<User[]> {
1065
1308
  return Array.from(users.values())
1066
1309
  }
1310
+
1311
+ async exists(id: string): Promise<boolean> {
1312
+ return users.has(id)
1313
+ }
1067
1314
  }
1068
1315
  `;
1069
1316
  }
@@ -1087,7 +1334,7 @@ export class MailService implements IMailService {
1087
1334
  * App Service Provider
1088
1335
  */
1089
1336
 
1090
- import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
1337
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1091
1338
 
1092
1339
  export class AppServiceProvider extends ServiceProvider {
1093
1340
  register(_container: Container): void {
@@ -1107,7 +1354,7 @@ export class AppServiceProvider extends ServiceProvider {
1107
1354
  * Binds repository interfaces to implementations.
1108
1355
  */
1109
1356
 
1110
- import { ServiceProvider, type Container } from 'gravito-core'
1357
+ import { ServiceProvider, type Container } from '@gravito/core'
1111
1358
  import { UserRepository } from '../Persistence/Repositories/UserRepository'
1112
1359
  import { MailService } from '../ExternalServices/MailService'
1113
1360
 
@@ -1130,7 +1377,7 @@ export class RepositoryServiceProvider extends ServiceProvider {
1130
1377
  * User Controller
1131
1378
  */
1132
1379
 
1133
- import type { GravitoContext } from 'gravito-core'
1380
+ import type { GravitoContext } from '@gravito/core'
1134
1381
  import { CreateUserUseCase } from '../../../Application/UseCases/User/CreateUser'
1135
1382
  import { GetUserUseCase } from '../../../Application/UseCases/User/GetUser'
1136
1383
  import { UserRepository } from '../../../Infrastructure/Persistence/Repositories/UserRepository'
@@ -1209,15 +1456,51 @@ export class UserPresenter {
1209
1456
  * Application Bootstrap
1210
1457
  */
1211
1458
 
1212
- import { PlanetCore } from 'gravito-core'
1459
+ import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
1460
+ import { OrbitAtlas } from '@gravito/atlas'
1461
+ import databaseConfig from '../config/database'
1213
1462
  import { AppServiceProvider } from './Infrastructure/Providers/AppServiceProvider'
1214
1463
  import { RepositoryServiceProvider } from './Infrastructure/Providers/RepositoryServiceProvider'
1215
1464
  import { registerApiRoutes } from './Interface/Http/Routes/api'
1216
1465
 
1217
1466
  const core = new PlanetCore({
1218
- config: { APP_NAME: '${context.name}' },
1467
+ config: {
1468
+ APP_NAME: '${context.name}',
1469
+ database: databaseConfig,
1470
+ },
1219
1471
  })
1220
1472
 
1473
+ const defaultCsp = [
1474
+ "default-src 'self'",
1475
+ "script-src 'self' 'unsafe-inline'",
1476
+ "style-src 'self' 'unsafe-inline'",
1477
+ "img-src 'self' data:",
1478
+ "object-src 'none'",
1479
+ "base-uri 'self'",
1480
+ "frame-ancestors 'none'",
1481
+ ].join('; ')
1482
+ const cspValue = process.env.APP_CSP
1483
+ const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
1484
+ const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
1485
+ const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
1486
+ const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
1487
+
1488
+ core.adapter.use(
1489
+ '*',
1490
+ securityHeaders({
1491
+ contentSecurityPolicy: csp,
1492
+ hsts:
1493
+ process.env.NODE_ENV === 'production'
1494
+ ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
1495
+ : false,
1496
+ })
1497
+ )
1498
+ if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
1499
+ core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
1500
+ }
1501
+
1502
+ await core.orbit(new OrbitAtlas())
1503
+
1221
1504
  core.register(new RepositoryServiceProvider())
1222
1505
  core.register(new AppServiceProvider())
1223
1506
 
@@ -1297,6 +1580,32 @@ src/
1297
1580
  Created with \u2764\uFE0F using Gravito Framework
1298
1581
  `;
1299
1582
  }
1583
+ generatePackageJson(context) {
1584
+ const pkg = {
1585
+ name: context.nameKebabCase,
1586
+ version: "0.1.0",
1587
+ type: "module",
1588
+ scripts: {
1589
+ dev: "bun run --watch src/bootstrap.ts",
1590
+ build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
1591
+ start: "bun run dist/bootstrap.js",
1592
+ test: "bun test",
1593
+ typecheck: "tsc --noEmit",
1594
+ "docker:build": `docker build -t ${context.nameKebabCase} .`,
1595
+ "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
1596
+ },
1597
+ dependencies: {
1598
+ "@gravito/core": "workspace:*",
1599
+ "@gravito/enterprise": "workspace:*",
1600
+ ...context.withSpectrum ? { "@gravito/spectrum": "workspace:*" } : {}
1601
+ },
1602
+ devDependencies: {
1603
+ "bun-types": "latest",
1604
+ typescript: "^5.0.0"
1605
+ }
1606
+ };
1607
+ return JSON.stringify(pkg, null, 2);
1608
+ }
1300
1609
  };
1301
1610
 
1302
1611
  // src/generators/DddGenerator.ts
@@ -1424,22 +1733,6 @@ var DddGenerator = class extends BaseGenerator {
1424
1733
  { type: "file", name: "Money.ts", content: this.generateMoneyValueObject() },
1425
1734
  { type: "file", name: "Email.ts", content: this.generateEmailValueObject() }
1426
1735
  ]
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
1736
  }
1444
1737
  ]
1445
1738
  },
@@ -1628,7 +1921,7 @@ var DddGenerator = class extends BaseGenerator {
1628
1921
  * Central configuration and initialization of the application.
1629
1922
  */
1630
1923
 
1631
- import { PlanetCore } from 'gravito-core'
1924
+ import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
1632
1925
  import { registerProviders } from './providers'
1633
1926
  import { registerRoutes } from './routes'
1634
1927
 
@@ -1639,6 +1932,35 @@ export async function createApp(): Promise<PlanetCore> {
1639
1932
  },
1640
1933
  })
1641
1934
 
1935
+ const defaultCsp = [
1936
+ "default-src 'self'",
1937
+ "script-src 'self' 'unsafe-inline'",
1938
+ "style-src 'self' 'unsafe-inline'",
1939
+ "img-src 'self' data:",
1940
+ "object-src 'none'",
1941
+ "base-uri 'self'",
1942
+ "frame-ancestors 'none'",
1943
+ ].join('; ')
1944
+ const cspValue = process.env.APP_CSP
1945
+ const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
1946
+ const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
1947
+ const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
1948
+ const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
1949
+
1950
+ core.adapter.use(
1951
+ '*',
1952
+ securityHeaders({
1953
+ contentSecurityPolicy: csp,
1954
+ hsts:
1955
+ process.env.NODE_ENV === 'production'
1956
+ ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
1957
+ : false,
1958
+ })
1959
+ )
1960
+ if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
1961
+ core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
1962
+ }
1963
+
1642
1964
  // Register all service providers
1643
1965
  await registerProviders(core)
1644
1966
 
@@ -1659,7 +1981,7 @@ export async function createApp(): Promise<PlanetCore> {
1659
1981
  * Register all module service providers here.
1660
1982
  */
1661
1983
 
1662
- import type { PlanetCore } from 'gravito-core'
1984
+ import type { PlanetCore } from '@gravito/core'
1663
1985
  import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
1664
1986
  import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
1665
1987
 
@@ -1754,7 +2076,7 @@ export default {
1754
2076
  * ${name} Service Provider
1755
2077
  */
1756
2078
 
1757
- import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
2079
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
1758
2080
  import { ${name}Repository } from '../Persistence/${name}Repository'
1759
2081
 
1760
2082
  export class ${name}ServiceProvider extends ServiceProvider {
@@ -1781,13 +2103,16 @@ export class ${name}ServiceProvider extends ServiceProvider {
1781
2103
  build: "bun build ./src/main.ts --outdir ./dist --target bun",
1782
2104
  start: "bun run dist/main.js",
1783
2105
  test: "bun test",
1784
- typecheck: "tsc --noEmit"
2106
+ typecheck: "tsc --noEmit",
2107
+ "docker:build": `docker build -t ${context.nameKebabCase} .`,
2108
+ "docker:run": `docker run -it -p 3000:3000 ${context.nameKebabCase}`
1785
2109
  },
1786
2110
  dependencies: {
1787
- "gravito-core": "^1.0.0-beta.5"
2111
+ "@gravito/core": "^1.0.0-beta.5",
2112
+ "@gravito/enterprise": "workspace:*"
1788
2113
  },
1789
2114
  devDependencies: {
1790
- "@types/bun": "latest",
2115
+ "bun-types": "latest",
1791
2116
  typescript: "^5.0.0"
1792
2117
  }
1793
2118
  };
@@ -1838,11 +2163,15 @@ export class ${name}ServiceProvider extends ServiceProvider {
1838
2163
  * Shared identifier across all contexts.
1839
2164
  */
1840
2165
 
1841
- export class Id {
1842
- private readonly _value: string
2166
+ import { ValueObject } from '@gravito/enterprise'
1843
2167
 
2168
+ interface IdProps {
2169
+ value: string
2170
+ }
2171
+
2172
+ export class Id extends ValueObject<IdProps> {
1844
2173
  private constructor(value: string) {
1845
- this._value = value
2174
+ super({ value })
1846
2175
  }
1847
2176
 
1848
2177
  static create(): Id {
@@ -1855,15 +2184,11 @@ export class Id {
1855
2184
  }
1856
2185
 
1857
2186
  get value(): string {
1858
- return this._value
1859
- }
1860
-
1861
- equals(other: Id): boolean {
1862
- return this._value === other._value
2187
+ return this.props.value
1863
2188
  }
1864
2189
 
1865
2190
  toString(): string {
1866
- return this._value
2191
+ return this.props.value
1867
2192
  }
1868
2193
  }
1869
2194
  `;
@@ -1873,12 +2198,25 @@ export class Id {
1873
2198
  * Money Value Object
1874
2199
  */
1875
2200
 
1876
- export class Money {
1877
- constructor(
1878
- public readonly amount: number,
1879
- public readonly currency: string = 'USD'
1880
- ) {
2201
+ import { ValueObject } from '@gravito/enterprise'
2202
+
2203
+ interface MoneyProps {
2204
+ amount: number
2205
+ currency: string
2206
+ }
2207
+
2208
+ export class Money extends ValueObject<MoneyProps> {
2209
+ constructor(amount: number, currency: string = 'USD') {
1881
2210
  if (amount < 0) throw new Error('Amount cannot be negative')
2211
+ super({ amount, currency })
2212
+ }
2213
+
2214
+ get amount(): number {
2215
+ return this.props.amount
2216
+ }
2217
+
2218
+ get currency(): string {
2219
+ return this.props.currency
1882
2220
  }
1883
2221
 
1884
2222
  add(other: Money): Money {
@@ -1896,10 +2234,6 @@ export class Money {
1896
2234
  throw new Error('Cannot operate on different currencies')
1897
2235
  }
1898
2236
  }
1899
-
1900
- equals(other: Money): boolean {
1901
- return this.amount === other.amount && this.currency === other.currency
1902
- }
1903
2237
  }
1904
2238
  `;
1905
2239
  }
@@ -1908,11 +2242,15 @@ export class Money {
1908
2242
  * Email Value Object
1909
2243
  */
1910
2244
 
1911
- export class Email {
1912
- private readonly _value: string
2245
+ import { ValueObject } from '@gravito/enterprise'
2246
+
2247
+ interface EmailProps {
2248
+ value: string
2249
+ }
1913
2250
 
2251
+ export class Email extends ValueObject<EmailProps> {
1914
2252
  private constructor(value: string) {
1915
- this._value = value.toLowerCase().trim()
2253
+ super({ value: value.toLowerCase().trim() })
1916
2254
  }
1917
2255
 
1918
2256
  static create(email: string): Email {
@@ -1927,97 +2265,7 @@ export class Email {
1927
2265
  }
1928
2266
 
1929
2267
  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)
2268
+ return this.props.value
2021
2269
  }
2022
2270
  }
2023
2271
  `;
@@ -2027,7 +2275,7 @@ export abstract class ValueObject<T> {
2027
2275
  * Event Dispatcher
2028
2276
  */
2029
2277
 
2030
- import type { DomainEvent } from '../../Domain/Events/DomainEvent'
2278
+ import type { DomainEvent } from '@gravito/enterprise'
2031
2279
 
2032
2280
  type EventHandler = (event: DomainEvent) => void | Promise<void>
2033
2281
 
@@ -2063,7 +2311,7 @@ export class EventDispatcher {
2063
2311
  * ${name} Aggregate Root
2064
2312
  */
2065
2313
 
2066
- import { AggregateRoot } from '../../../../../Shared/Domain/Primitives/AggregateRoot'
2314
+ import { AggregateRoot } from '@gravito/enterprise'
2067
2315
  import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2068
2316
  import { ${name}Created } from '../../Events/${name}Created'
2069
2317
  import { ${name}Status } from './${name}Status'
@@ -2074,7 +2322,7 @@ export interface ${name}Props {
2074
2322
  createdAt: Date
2075
2323
  }
2076
2324
 
2077
- export class ${name} extends AggregateRoot {
2325
+ export class ${name} extends AggregateRoot<Id> {
2078
2326
  private props: ${name}Props
2079
2327
 
2080
2328
  private constructor(id: Id, props: ${name}Props) {
@@ -2119,14 +2367,14 @@ export enum ${name}Status {
2119
2367
  * ${name} Created Event
2120
2368
  */
2121
2369
 
2122
- import { DomainEvent } from '../../../../Shared/Domain/Events/DomainEvent'
2370
+ import { DomainEvent } from '@gravito/enterprise'
2123
2371
 
2124
2372
  export class ${name}Created extends DomainEvent {
2125
2373
  constructor(public readonly ${name.toLowerCase()}Id: string) {
2126
2374
  super()
2127
2375
  }
2128
2376
 
2129
- get eventName(): string {
2377
+ override get eventName(): string {
2130
2378
  return '${name.toLowerCase()}.created'
2131
2379
  }
2132
2380
 
@@ -2141,12 +2389,12 @@ export class ${name}Created extends DomainEvent {
2141
2389
  * ${name} Repository Interface
2142
2390
  */
2143
2391
 
2392
+ import { Repository } from '@gravito/enterprise'
2144
2393
  import type { ${name} } from '../Aggregates/${name}/${name}'
2394
+ import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2145
2395
 
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>
2396
+ export interface I${name}Repository extends Repository<${name}, Id> {
2397
+ // Add specific methods for this repository if needed
2150
2398
  }
2151
2399
  `;
2152
2400
  }
@@ -2155,11 +2403,15 @@ export interface I${name}Repository {
2155
2403
  * Create ${name} Command
2156
2404
  */
2157
2405
 
2158
- export class Create${name}Command {
2406
+ import { Command } from '@gravito/enterprise'
2407
+
2408
+ export class Create${name}Command extends Command {
2159
2409
  constructor(
2160
2410
  // Add command properties
2161
2411
  public readonly id?: string
2162
- ) {}
2412
+ ) {
2413
+ super()
2414
+ }
2163
2415
  }
2164
2416
  `;
2165
2417
  }
@@ -2168,12 +2420,13 @@ export class Create${name}Command {
2168
2420
  * Create ${name} Handler
2169
2421
  */
2170
2422
 
2423
+ import { CommandHandler } from '@gravito/enterprise'
2171
2424
  import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
2172
2425
  import { ${name} } from '../../../Domain/Aggregates/${name}/${name}'
2173
2426
  import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2174
2427
  import type { Create${name}Command } from './Create${name}Command'
2175
2428
 
2176
- export class Create${name}Handler {
2429
+ export class Create${name}Handler implements CommandHandler<Create${name}Command, string> {
2177
2430
  constructor(private repository: I${name}Repository) {}
2178
2431
 
2179
2432
  async handle(command: Create${name}Command): Promise<string> {
@@ -2192,8 +2445,12 @@ export class Create${name}Handler {
2192
2445
  * Get ${name} By Id Query
2193
2446
  */
2194
2447
 
2195
- export class Get${name}ByIdQuery {
2196
- constructor(public readonly id: string) {}
2448
+ import { Query } from '@gravito/enterprise'
2449
+
2450
+ export class Get${name}ByIdQuery extends Query {
2451
+ constructor(public readonly id: string) {
2452
+ super()
2453
+ }
2197
2454
  }
2198
2455
  `;
2199
2456
  }
@@ -2202,15 +2459,16 @@ export class Get${name}ByIdQuery {
2202
2459
  * Get ${name} By Id Handler
2203
2460
  */
2204
2461
 
2462
+ import { QueryHandler } from '@gravito/enterprise'
2205
2463
  import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
2206
2464
  import type { ${name}DTO } from '../../DTOs/${name}DTO'
2207
2465
  import type { Get${name}ByIdQuery } from './Get${name}ByIdQuery'
2208
2466
 
2209
- export class Get${name}ByIdHandler {
2467
+ export class Get${name}ByIdHandler implements QueryHandler<Get${name}ByIdQuery, ${name}DTO | null> {
2210
2468
  constructor(private repository: I${name}Repository) {}
2211
2469
 
2212
2470
  async handle(query: Get${name}ByIdQuery): Promise<${name}DTO | null> {
2213
- const aggregate = await this.repository.findById(query.id)
2471
+ const aggregate = await this.repository.findById(query.id as any) // Simplified for demo
2214
2472
  if (!aggregate) return null
2215
2473
 
2216
2474
  return {
@@ -2242,20 +2500,29 @@ export interface ${name}DTO {
2242
2500
 
2243
2501
  import type { ${name} } from '../../Domain/Aggregates/${name}/${name}'
2244
2502
  import type { I${name}Repository } from '../../Domain/Repositories/I${name}Repository'
2503
+ import type { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2245
2504
 
2246
2505
  const store = new Map<string, ${name}>()
2247
2506
 
2248
2507
  export class ${name}Repository implements I${name}Repository {
2249
- async findById(id: string): Promise<${name} | null> {
2250
- return store.get(id) ?? null
2508
+ async findById(id: Id): Promise<${name} | null> {
2509
+ return store.get(id.value) ?? null
2251
2510
  }
2252
2511
 
2253
2512
  async save(aggregate: ${name}): Promise<void> {
2254
2513
  store.set(aggregate.id.value, aggregate)
2255
2514
  }
2256
2515
 
2257
- async delete(id: string): Promise<void> {
2258
- store.delete(id)
2516
+ async delete(id: Id): Promise<void> {
2517
+ store.delete(id.value)
2518
+ }
2519
+
2520
+ async findAll(): Promise<${name}[]> {
2521
+ return Array.from(store.values())
2522
+ }
2523
+
2524
+ async exists(id: Id): Promise<boolean> {
2525
+ return store.has(id.value)
2259
2526
  }
2260
2527
  }
2261
2528
  `;
@@ -2698,7 +2965,7 @@ export default {
2698
2965
  * can be assigned to specific routes.
2699
2966
  */
2700
2967
 
2701
- import type { GravitoMiddleware } from 'gravito-core'
2968
+ import type { GravitoMiddleware } from '@gravito/core'
2702
2969
 
2703
2970
  /**
2704
2971
  * Global middleware stack.
@@ -2787,7 +3054,7 @@ export abstract class Controller {
2787
3054
  * Home Controller
2788
3055
  */
2789
3056
 
2790
- import type { GravitoContext } from 'gravito-core'
3057
+ import type { GravitoContext } from '@gravito/core'
2791
3058
  import { Controller } from './Controller'
2792
3059
 
2793
3060
  export class HomeController extends Controller {
@@ -2821,7 +3088,7 @@ export class HomeController extends Controller {
2821
3088
  * Protects routes that require authentication.
2822
3089
  */
2823
3090
 
2824
- import type { GravitoContext, GravitoNext } from 'gravito-core'
3091
+ import type { GravitoContext, GravitoNext } from '@gravito/core'
2825
3092
 
2826
3093
  export async function Authenticate(c: GravitoContext, next: GravitoNext) {
2827
3094
  // TODO: Implement authentication check
@@ -2831,6 +3098,7 @@ export async function Authenticate(c: GravitoContext, next: GravitoNext) {
2831
3098
  // }
2832
3099
 
2833
3100
  await next()
3101
+ return undefined
2834
3102
  }
2835
3103
  `;
2836
3104
  }
@@ -2842,7 +3110,7 @@ export async function Authenticate(c: GravitoContext, next: GravitoNext) {
2842
3110
  * Register and bootstrap application services here.
2843
3111
  */
2844
3112
 
2845
- import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
3113
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
2846
3114
 
2847
3115
  export class AppServiceProvider extends ServiceProvider {
2848
3116
  /**
@@ -2870,7 +3138,7 @@ export class AppServiceProvider extends ServiceProvider {
2870
3138
  * Configures and registers application routes.
2871
3139
  */
2872
3140
 
2873
- import { ServiceProvider, type Container, type PlanetCore } from 'gravito-core'
3141
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
2874
3142
  import { registerRoutes } from '../routes'
2875
3143
 
2876
3144
  export class RouteServiceProvider extends ServiceProvider {
@@ -2898,7 +3166,7 @@ export class RouteServiceProvider extends ServiceProvider {
2898
3166
  * Customize error responses and logging here.
2899
3167
  */
2900
3168
 
2901
- import type { ErrorHandlerContext } from 'gravito-core'
3169
+ import type { ErrorHandlerContext } from '@gravito/core'
2902
3170
 
2903
3171
  /**
2904
3172
  * Report an exception (logging, monitoring, etc.)
@@ -2935,6 +3203,13 @@ export const dontReport: string[] = [
2935
3203
  `;
2936
3204
  }
2937
3205
  generateBootstrap(context) {
3206
+ const spectrumImport = context.withSpectrum ? "import { SpectrumOrbit } from '@gravito/spectrum'\n" : "";
3207
+ const spectrumOrbit = context.withSpectrum ? `
3208
+ // Enable Debug Dashboard
3209
+ if (process.env.APP_DEBUG === 'true') {
3210
+ await core.orbit(new SpectrumOrbit())
3211
+ }
3212
+ ` : "";
2938
3213
  return `/**
2939
3214
  * Application Bootstrap
2940
3215
  *
@@ -2942,8 +3217,10 @@ export const dontReport: string[] = [
2942
3217
  * It initializes the core and registers all providers.
2943
3218
  */
2944
3219
 
2945
- import { PlanetCore } from 'gravito-core'
2946
- import { AppServiceProvider } from './Providers/AppServiceProvider'
3220
+ import { bodySizeLimit, PlanetCore, securityHeaders } from '@gravito/core'
3221
+ import { OrbitAtlas } from '@gravito/atlas'
3222
+ import databaseConfig from '../config/database'
3223
+ ${spectrumImport}import { AppServiceProvider } from './Providers/AppServiceProvider'
2947
3224
  import { RouteServiceProvider } from './Providers/RouteServiceProvider'
2948
3225
 
2949
3226
  // Load environment variables
@@ -2952,10 +3229,41 @@ import { RouteServiceProvider } from './Providers/RouteServiceProvider'
2952
3229
  // Create application core
2953
3230
  const core = new PlanetCore({
2954
3231
  config: {
2955
- APP_NAME: '${context.name}',
3232
+ APP_NAME: process.env.APP_NAME ?? '${context.name}',
3233
+ database: databaseConfig,
2956
3234
  },
2957
3235
  })
3236
+ const defaultCsp = [
3237
+ "default-src 'self'",
3238
+ "script-src 'self' 'unsafe-inline'",
3239
+ "style-src 'self' 'unsafe-inline'",
3240
+ "img-src 'self' data:",
3241
+ "object-src 'none'",
3242
+ "base-uri 'self'",
3243
+ "frame-ancestors 'none'",
3244
+ ].join('; ')
3245
+ const cspValue = process.env.APP_CSP
3246
+ const csp = cspValue === 'false' ? false : (cspValue ?? defaultCsp)
3247
+ const hstsMaxAge = Number.parseInt(process.env.APP_HSTS_MAX_AGE ?? '15552000', 10)
3248
+ const bodyLimit = Number.parseInt(process.env.APP_BODY_LIMIT ?? '1048576', 10)
3249
+ const requireLength = process.env.APP_BODY_REQUIRE_LENGTH === 'true'
3250
+
3251
+ core.adapter.use(
3252
+ '*',
3253
+ securityHeaders({
3254
+ contentSecurityPolicy: csp,
3255
+ hsts:
3256
+ process.env.NODE_ENV === 'production'
3257
+ ? { maxAge: Number.isNaN(hstsMaxAge) ? 15552000 : hstsMaxAge, includeSubDomains: true }
3258
+ : false,
3259
+ })
3260
+ )
3261
+ if (!Number.isNaN(bodyLimit) && bodyLimit > 0) {
3262
+ core.adapter.use('*', bodySizeLimit(bodyLimit, { requireContentLength: requireLength }))
3263
+ }
2958
3264
 
3265
+ await core.orbit(new OrbitAtlas())
3266
+ ${spectrumOrbit}
2959
3267
  // Register service providers
2960
3268
  core.register(new AppServiceProvider())
2961
3269
  core.register(new RouteServiceProvider())
@@ -3126,8 +3434,829 @@ Created with \u2764\uFE0F using Gravito Framework
3126
3434
  }
3127
3435
  };
3128
3436
 
3437
+ // src/generators/SatelliteGenerator.ts
3438
+ var SatelliteGenerator = class extends BaseGenerator {
3439
+ get architectureType() {
3440
+ return "satellite";
3441
+ }
3442
+ get displayName() {
3443
+ return "Gravito Satellite";
3444
+ }
3445
+ get description() {
3446
+ return "A modular plugin for Gravito following DDD and Clean Architecture";
3447
+ }
3448
+ getDirectoryStructure(context) {
3449
+ const name = context.namePascalCase;
3450
+ return [
3451
+ {
3452
+ type: "directory",
3453
+ name: "src",
3454
+ children: [
3455
+ // Domain Layer
3456
+ {
3457
+ type: "directory",
3458
+ name: "Domain",
3459
+ children: [
3460
+ {
3461
+ type: "directory",
3462
+ name: "Entities",
3463
+ children: [
3464
+ { type: "file", name: `${name}.ts`, content: this.generateEntity(name) }
3465
+ ]
3466
+ },
3467
+ {
3468
+ type: "directory",
3469
+ name: "Contracts",
3470
+ children: [
3471
+ {
3472
+ type: "file",
3473
+ name: `I${name}Repository.ts`,
3474
+ content: this.generateRepositoryInterface(name)
3475
+ }
3476
+ ]
3477
+ },
3478
+ { type: "directory", name: "ValueObjects", children: [] },
3479
+ { type: "directory", name: "Events", children: [] }
3480
+ ]
3481
+ },
3482
+ // Application Layer
3483
+ {
3484
+ type: "directory",
3485
+ name: "Application",
3486
+ children: [
3487
+ {
3488
+ type: "directory",
3489
+ name: "UseCases",
3490
+ children: [
3491
+ {
3492
+ type: "file",
3493
+ name: `Create${name}.ts`,
3494
+ content: this.generateUseCase(name)
3495
+ }
3496
+ ]
3497
+ },
3498
+ { type: "directory", name: "DTOs", children: [] }
3499
+ ]
3500
+ },
3501
+ // Infrastructure Layer
3502
+ {
3503
+ type: "directory",
3504
+ name: "Infrastructure",
3505
+ children: [
3506
+ {
3507
+ type: "directory",
3508
+ name: "Persistence",
3509
+ children: [
3510
+ {
3511
+ type: "file",
3512
+ name: `Atlas${name}Repository.ts`,
3513
+ content: this.generateAtlasRepository(name)
3514
+ },
3515
+ { type: "directory", name: "Migrations", children: [] }
3516
+ ]
3517
+ }
3518
+ ]
3519
+ },
3520
+ // Entry Point
3521
+ { type: "file", name: "index.ts", content: this.generateEntryPoint(name) },
3522
+ {
3523
+ type: "file",
3524
+ name: "env.d.ts",
3525
+ content: "interface ImportMeta {\n readonly dir: string\n readonly path: string\n}\n"
3526
+ },
3527
+ { type: "file", name: "manifest.json", content: this.generateManifest(context) }
3528
+ ]
3529
+ },
3530
+ {
3531
+ type: "directory",
3532
+ name: "tests",
3533
+ children: [
3534
+ {
3535
+ type: "file",
3536
+ name: "unit.test.ts",
3537
+ content: `import { describe, it, expect } from "bun:test";
3538
+
3539
+ describe("${name}", () => {
3540
+ it("should work", () => {
3541
+ expect(true).toBe(true);
3542
+ });
3543
+ });`
3544
+ }
3545
+ ]
3546
+ }
3547
+ ];
3548
+ }
3549
+ // ─────────────────────────────────────────────────────────────
3550
+ // Domain Templates
3551
+ // ─────────────────────────────────────────────────────────────
3552
+ generateEntity(name) {
3553
+ return `import { Entity } from '@gravito/enterprise'
3554
+
3555
+ export interface ${name}Props {
3556
+ name: string
3557
+ createdAt: Date
3558
+ }
3559
+
3560
+ export class ${name} extends Entity<string> {
3561
+ constructor(id: string, private props: ${name}Props) {
3562
+ super(id)
3563
+ }
3564
+
3565
+ static create(id: string, name: string): ${name} {
3566
+ return new ${name}(id, {
3567
+ name,
3568
+ createdAt: new Date()
3569
+ })
3570
+ }
3571
+
3572
+ get name() { return this.props.name }
3573
+ }
3574
+ `;
3575
+ }
3576
+ generateRepositoryInterface(name) {
3577
+ return `import { Repository } from '@gravito/enterprise'
3578
+ import { ${name} } from '../Entities/${name}'
3579
+
3580
+ export interface I${name}Repository extends Repository<${name}, string> {
3581
+ // Add custom methods here
3582
+ }
3583
+ `;
3584
+ }
3585
+ // ─────────────────────────────────────────────────────────────
3586
+ // Application Templates
3587
+ // ─────────────────────────────────────────────────────────────
3588
+ generateUseCase(name) {
3589
+ return `import { UseCase } from '@gravito/enterprise'
3590
+ import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
3591
+ import { ${name} } from '../../Domain/Entities/${name}'
3592
+
3593
+ export interface Create${name}Input {
3594
+ name: string
3595
+ }
3596
+
3597
+ export class Create${name} extends UseCase<Create${name}Input, string> {
3598
+ constructor(private repository: I${name}Repository) {
3599
+ super()
3600
+ }
3601
+
3602
+ async execute(input: Create${name}Input): Promise<string> {
3603
+ const id = crypto.randomUUID()
3604
+ const entity = ${name}.create(id, input.name)
3605
+
3606
+ await this.repository.save(entity)
3607
+
3608
+ return id
3609
+ }
3610
+ }
3611
+ `;
3612
+ }
3613
+ // ─────────────────────────────────────────────────────────────
3614
+ // Infrastructure Templates (Dogfooding Atlas)
3615
+ // ─────────────────────────────────────────────────────────────
3616
+ generateAtlasRepository(name) {
3617
+ return `import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
3618
+ import { ${name} } from '../../Domain/Entities/${name}'
3619
+ import { DB } from '@gravito/atlas'
3620
+
3621
+ export class Atlas${name}Repository implements I${name}Repository {
3622
+ async save(entity: ${name}): Promise<void> {
3623
+ // Dogfooding: Use @gravito/atlas for persistence
3624
+ console.log('[Atlas] Saving entity:', entity.id)
3625
+ // await DB.table('${name.toLowerCase()}s').insert({ ... })
3626
+ }
3627
+
3628
+ async findById(id: string): Promise<${name} | null> {
3629
+ return null
3630
+ }
3631
+
3632
+ async findAll(): Promise<${name}[]> {
3633
+ return []
3634
+ }
3635
+
3636
+ async delete(id: string): Promise<void> {}
3637
+
3638
+ async exists(id: string): Promise<boolean> {
3639
+ return false
3640
+ }
3641
+ }
3642
+ `;
3643
+ }
3644
+ // ─────────────────────────────────────────────────────────────
3645
+ // Entry Point & Manifest
3646
+ // ─────────────────────────────────────────────────────────────
3647
+ generateEntryPoint(name) {
3648
+ return `import { ServiceProvider, type Container } from '@gravito/core'
3649
+ import { Atlas${name}Repository } from './Infrastructure/Persistence/Atlas${name}Repository'
3650
+
3651
+ export class ${name}ServiceProvider extends ServiceProvider {
3652
+ register(container: Container): void {
3653
+ // Bind Repository
3654
+ container.singleton('${name.toLowerCase()}.repo', () => new Atlas${name}Repository())
3655
+
3656
+ // Bind UseCases
3657
+ container.singleton('${name.toLowerCase()}.create', () => {
3658
+ return new (require('./Application/UseCases/Create${name}').Create${name})(
3659
+ container.make('${name.toLowerCase()}.repo')
3660
+ )
3661
+ })
3662
+ }
3663
+
3664
+ boot(): void {
3665
+ this.core?.logger.info('\u{1F6F0}\uFE0F Satellite ${name} is operational')
3666
+ }
3667
+ }
3668
+ `;
3669
+ }
3670
+ generateManifest(context) {
3671
+ return JSON.stringify(
3672
+ {
3673
+ name: context.name,
3674
+ id: context.nameKebabCase,
3675
+ version: "0.1.0",
3676
+ description: context.description || "A Gravito Satellite",
3677
+ capabilities: [`create-${context.nameKebabCase}`],
3678
+ requirements: [
3679
+ "cache"
3680
+ // Example requirement
3681
+ ],
3682
+ hooks: [`${context.nameKebabCase}:created`]
3683
+ },
3684
+ null,
3685
+ 2
3686
+ );
3687
+ }
3688
+ generatePackageJson(context) {
3689
+ const isInternal = context.isInternal || false;
3690
+ const depVersion = isInternal ? "workspace:*" : "^1.0.0-beta.1";
3691
+ const pkg = {
3692
+ name: isInternal ? `@gravito/satellite-${context.nameKebabCase}` : `gravito-satellite-${context.nameKebabCase}`,
3693
+ version: "0.1.0",
3694
+ type: "module",
3695
+ main: "dist/index.js",
3696
+ module: "dist/index.mjs",
3697
+ types: "dist/index.d.ts",
3698
+ scripts: {
3699
+ build: "tsup src/index.ts --format cjs,esm --dts",
3700
+ test: "bun test",
3701
+ typecheck: "tsc --noEmit"
3702
+ },
3703
+ dependencies: {
3704
+ "@gravito/core": depVersion,
3705
+ "@gravito/enterprise": depVersion,
3706
+ "@gravito/atlas": depVersion,
3707
+ "@gravito/stasis": depVersion
3708
+ },
3709
+ devDependencies: {
3710
+ tsup: "^8.0.0",
3711
+ typescript: "^5.0.0"
3712
+ }
3713
+ };
3714
+ return JSON.stringify(pkg, null, 2);
3715
+ }
3716
+ generateArchitectureDoc(context) {
3717
+ return `# ${context.name} Satellite Architecture
3718
+
3719
+ This satellite follows the Gravito Satellite Specification v1.0.
3720
+
3721
+ ## Design
3722
+ - **DDD**: Domain logic is separated from framework concerns.
3723
+ - **Dogfooding**: Uses official Gravito modules (@gravito/atlas, @gravito/stasis).
3724
+ - **Decoupled**: Inter-satellite communication happens via Contracts and Events.
3725
+
3726
+ ## Layers
3727
+ - **Domain**: Pure business rules.
3728
+ - **Application**: Orchestration of domain tasks.
3729
+ - **Infrastructure**: Implementation of persistence and external services.
3730
+ - **Interface**: HTTP and Event entry points.
3731
+ `;
3732
+ }
3733
+ };
3734
+
3735
+ // src/LockGenerator.ts
3736
+ var LockGenerator = class {
3737
+ generate(profileName, config, templateName = "basic", templateVersion = "1.0.0") {
3738
+ const lock = {
3739
+ profile: {
3740
+ name: profileName,
3741
+ version: "1.0.0"
3742
+ // This could be dynamic based on profile system version
3743
+ },
3744
+ features: config.features,
3745
+ drivers: config.drivers,
3746
+ manifest: {
3747
+ template: templateName,
3748
+ version: templateVersion
3749
+ },
3750
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3751
+ };
3752
+ return JSON.stringify(lock, null, 2);
3753
+ }
3754
+ };
3755
+
3756
+ // src/ProfileResolver.ts
3757
+ var ProfileResolver = class _ProfileResolver {
3758
+ static DEFAULTS = {
3759
+ core: {
3760
+ drivers: {
3761
+ database: "sqlite",
3762
+ cache: "memory",
3763
+ queue: "sync",
3764
+ storage: "local",
3765
+ session: "file"
3766
+ },
3767
+ features: []
3768
+ },
3769
+ scale: {
3770
+ drivers: {
3771
+ database: "postgresql",
3772
+ cache: "redis",
3773
+ queue: "redis",
3774
+ storage: "s3",
3775
+ session: "redis"
3776
+ },
3777
+ features: ["stream", "nebula"]
3778
+ },
3779
+ enterprise: {
3780
+ drivers: {
3781
+ database: "postgresql",
3782
+ cache: "redis",
3783
+ queue: "redis",
3784
+ storage: "s3",
3785
+ session: "redis"
3786
+ },
3787
+ features: ["stream", "nebula", "monitor", "sentinel", "fortify"]
3788
+ }
3789
+ };
3790
+ resolve(profile = "core", withFeatures = []) {
3791
+ const base = _ProfileResolver.DEFAULTS[profile] || _ProfileResolver.DEFAULTS.core;
3792
+ const config = {
3793
+ drivers: { ...base.drivers },
3794
+ features: [...base.features]
3795
+ };
3796
+ for (const feature of withFeatures) {
3797
+ this.applyFeature(config, feature);
3798
+ }
3799
+ return config;
3800
+ }
3801
+ applyFeature(config, feature) {
3802
+ switch (feature) {
3803
+ case "redis":
3804
+ config.drivers.cache = "redis";
3805
+ config.drivers.queue = "redis";
3806
+ config.drivers.session = "redis";
3807
+ break;
3808
+ case "postgres":
3809
+ case "postgresql":
3810
+ config.drivers.database = "postgresql";
3811
+ break;
3812
+ case "mysql":
3813
+ config.drivers.database = "mysql";
3814
+ break;
3815
+ case "s3":
3816
+ config.drivers.storage = "s3";
3817
+ break;
3818
+ case "r2":
3819
+ config.drivers.storage = "r2";
3820
+ break;
3821
+ case "queue":
3822
+ if (config.drivers.queue === "sync") {
3823
+ config.drivers.queue = "redis";
3824
+ }
3825
+ break;
3826
+ default:
3827
+ if (!config.features.includes(feature)) {
3828
+ config.features.push(feature);
3829
+ }
3830
+ }
3831
+ }
3832
+ /**
3833
+ * Validator for profile names
3834
+ */
3835
+ isValidProfile(profile) {
3836
+ return profile in _ProfileResolver.DEFAULTS;
3837
+ }
3838
+ /**
3839
+ * Validator for feature names
3840
+ */
3841
+ isValidFeature(feature) {
3842
+ const validFeatures = [
3843
+ "redis",
3844
+ "postgres",
3845
+ "postgresql",
3846
+ "mysql",
3847
+ "s3",
3848
+ "r2",
3849
+ "queue",
3850
+ "stream",
3851
+ "nebula",
3852
+ "monitor",
3853
+ "sentinel",
3854
+ "fortify"
3855
+ ];
3856
+ return validFeatures.includes(feature);
3857
+ }
3858
+ };
3859
+
3129
3860
  // src/Scaffold.ts
3130
3861
  import path3 from "path";
3862
+
3863
+ // src/generators/ActionDomainGenerator.ts
3864
+ var ActionDomainGenerator = class extends BaseGenerator {
3865
+ get architectureType() {
3866
+ return "action-domain";
3867
+ }
3868
+ get displayName() {
3869
+ return "Action Domain";
3870
+ }
3871
+ get description() {
3872
+ return "Single-responsibility Action pattern for clear business logic separation";
3873
+ }
3874
+ getDirectoryStructure(context) {
3875
+ return [
3876
+ {
3877
+ type: "directory",
3878
+ name: "config",
3879
+ children: [
3880
+ { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
3881
+ { type: "file", name: "database.ts", content: this.generateDatabaseConfig() }
3882
+ ]
3883
+ },
3884
+ {
3885
+ type: "directory",
3886
+ name: "database",
3887
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
3888
+ },
3889
+ {
3890
+ type: "directory",
3891
+ name: "src",
3892
+ children: [
3893
+ {
3894
+ type: "directory",
3895
+ name: "actions",
3896
+ children: [
3897
+ { type: "file", name: "Action.ts", content: this.generateActionBase() },
3898
+ {
3899
+ type: "directory",
3900
+ name: "server",
3901
+ children: [
3902
+ {
3903
+ type: "file",
3904
+ name: "GetServerStatusAction.ts",
3905
+ content: this.generateGetServerStatusAction()
3906
+ }
3907
+ ]
3908
+ }
3909
+ ]
3910
+ },
3911
+ {
3912
+ type: "directory",
3913
+ name: "controllers",
3914
+ children: [
3915
+ {
3916
+ type: "directory",
3917
+ name: "api",
3918
+ children: [
3919
+ {
3920
+ type: "directory",
3921
+ name: "v1",
3922
+ children: [
3923
+ {
3924
+ type: "file",
3925
+ name: "ServerController.ts",
3926
+ content: this.generateServerController()
3927
+ }
3928
+ ]
3929
+ }
3930
+ ]
3931
+ }
3932
+ ]
3933
+ },
3934
+ {
3935
+ type: "directory",
3936
+ name: "types",
3937
+ children: [
3938
+ {
3939
+ type: "directory",
3940
+ name: "requests",
3941
+ children: [
3942
+ {
3943
+ type: "directory",
3944
+ name: "api",
3945
+ children: [
3946
+ {
3947
+ type: "directory",
3948
+ name: "v1",
3949
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
3950
+ }
3951
+ ]
3952
+ }
3953
+ ]
3954
+ },
3955
+ {
3956
+ type: "directory",
3957
+ name: "responses",
3958
+ children: [
3959
+ {
3960
+ type: "directory",
3961
+ name: "api",
3962
+ children: [
3963
+ {
3964
+ type: "directory",
3965
+ name: "v1",
3966
+ children: [
3967
+ {
3968
+ type: "file",
3969
+ name: "ServerStatusResponse.ts",
3970
+ content: this.generateServerStatusResponse()
3971
+ }
3972
+ ]
3973
+ }
3974
+ ]
3975
+ }
3976
+ ]
3977
+ }
3978
+ ]
3979
+ },
3980
+ {
3981
+ type: "directory",
3982
+ name: "models",
3983
+ children: [{ type: "file", name: "User.ts", content: this.generateUserModel() }]
3984
+ },
3985
+ {
3986
+ type: "directory",
3987
+ name: "repositories",
3988
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
3989
+ },
3990
+ {
3991
+ type: "directory",
3992
+ name: "routes",
3993
+ children: [{ type: "file", name: "api.ts", content: this.generateApiRoutes() }]
3994
+ },
3995
+ {
3996
+ type: "directory",
3997
+ name: "providers",
3998
+ children: [
3999
+ {
4000
+ type: "file",
4001
+ name: "AppServiceProvider.ts",
4002
+ content: this.generateAppServiceProvider(context)
4003
+ }
4004
+ ]
4005
+ },
4006
+ { type: "file", name: "bootstrap.ts", content: this.generateBootstrap(context) }
4007
+ ]
4008
+ },
4009
+ {
4010
+ type: "directory",
4011
+ name: "tests",
4012
+ children: [{ type: "file", name: ".gitkeep", content: "" }]
4013
+ }
4014
+ ];
4015
+ }
4016
+ // ─────────────────────────────────────────────────────────────
4017
+ // Config Generators
4018
+ // ─────────────────────────────────────────────────────────────
4019
+ generateAppConfig(context) {
4020
+ return `export default {
4021
+ name: process.env.APP_NAME ?? '${context.name}',
4022
+ env: process.env.APP_ENV ?? 'development',
4023
+ debug: process.env.APP_DEBUG === 'true',
4024
+ url: process.env.APP_URL ?? 'http://localhost:3000',
4025
+ key: process.env.APP_KEY,
4026
+ }
4027
+ `;
4028
+ }
4029
+ generateDatabaseConfig() {
4030
+ return `export default {
4031
+ default: process.env.DB_CONNECTION ?? 'sqlite',
4032
+ connections: {
4033
+ sqlite: {
4034
+ driver: 'sqlite',
4035
+ database: process.env.DB_DATABASE ?? 'database/database.sqlite',
4036
+ },
4037
+ },
4038
+ }
4039
+ `;
4040
+ }
4041
+ // ─────────────────────────────────────────────────────────────
4042
+ // Model Generators
4043
+ // ─────────────────────────────────────────────────────────────
4044
+ generateUserModel() {
4045
+ return `/**
4046
+ * User Model
4047
+ */
4048
+
4049
+ import { Model, column } from '@gravito/atlas'
4050
+
4051
+ export class User extends Model {
4052
+ static table = 'users'
4053
+
4054
+ @column({ isPrimary: true })
4055
+ id!: number
4056
+
4057
+ @column()
4058
+ name!: string
4059
+
4060
+ @column()
4061
+ email!: string
4062
+
4063
+ @column()
4064
+ created_at!: Date
4065
+
4066
+ @column()
4067
+ updated_at!: Date
4068
+ }
4069
+ `;
4070
+ }
4071
+ // ─────────────────────────────────────────────────────────────
4072
+ // Action Generators
4073
+ // ─────────────────────────────────────────────────────────────
4074
+ generateActionBase() {
4075
+ return `/**
4076
+ * Action Base Class
4077
+ *
4078
+ * All business logic actions should extend this class.
4079
+ * It enforces a consistent execution method.
4080
+ */
4081
+
4082
+ export abstract class Action<TInput = unknown, TOutput = unknown> {
4083
+ /**
4084
+ * Execute the business logic.
4085
+ */
4086
+ abstract execute(input: TInput): Promise<TOutput> | TOutput
4087
+ }
4088
+ `;
4089
+ }
4090
+ generateGetServerStatusAction() {
4091
+ return `/**
4092
+ * Get Server Status Action
4093
+ */
4094
+
4095
+ import { Action } from '../Action'
4096
+ import type { ServerStatusResponse } from '../../types/responses/api/v1/ServerStatusResponse'
4097
+
4098
+ export class GetServerStatusAction extends Action<void, ServerStatusResponse> {
4099
+ execute(): Promise<ServerStatusResponse> {
4100
+ return Promise.resolve({
4101
+ status: 'active',
4102
+ timestamp: new Date().toISOString(),
4103
+ service: 'Gravito Hub'
4104
+ })
4105
+ }
4106
+ }
4107
+ `;
4108
+ }
4109
+ // ─────────────────────────────────────────────────────────────
4110
+ // Controller Generators
4111
+ // ─────────────────────────────────────────────────────────────
4112
+ generateServerController() {
4113
+ return `/**
4114
+ * Server Controller
4115
+ */
4116
+
4117
+ import type { GravitoContext } from '@gravito/core'
4118
+ import { GetServerStatusAction } from '../../../actions/server/GetServerStatusAction'
4119
+
4120
+ export class ServerController {
4121
+ /**
4122
+ * GET /v1/server/status
4123
+ */
4124
+ async status(c: GravitoContext) {
4125
+ const action = new GetServerStatusAction()
4126
+ const result = await action.execute()
4127
+
4128
+ return c.json({
4129
+ success: true,
4130
+ data: result
4131
+ })
4132
+ }
4133
+ }
4134
+ `;
4135
+ }
4136
+ // ─────────────────────────────────────────────────────────────
4137
+ // Type Generators
4138
+ // ─────────────────────────────────────────────────────────────
4139
+ generateServerStatusResponse() {
4140
+ return `/**
4141
+ * Server Status Response Type
4142
+ */
4143
+
4144
+ export interface ServerStatusResponse {
4145
+ status: string
4146
+ timestamp: string
4147
+ service: string
4148
+ }
4149
+ `;
4150
+ }
4151
+ // ─────────────────────────────────────────────────────────────
4152
+ // Routes & Bootstrap
4153
+ // ─────────────────────────────────────────────────────────────
4154
+ generateApiRoutes() {
4155
+ return `/**
4156
+ * API Routes Registration
4157
+ */
4158
+
4159
+ import type { Router } from '@gravito/core'
4160
+ import { ServerController } from '../controllers/api/v1/ServerController'
4161
+
4162
+ export function registerApiRoutes(router: Router) {
4163
+ const server = new ServerController()
4164
+
4165
+ router.prefix('/v1').group((group) => {
4166
+ // Server Domain
4167
+ group.get('/server/status', (c) => server.status(c))
4168
+ })
4169
+ }
4170
+ `;
4171
+ }
4172
+ generateAppServiceProvider(context) {
4173
+ return `/**
4174
+ * App Service Provider
4175
+ */
4176
+
4177
+ import { ServiceProvider, type Container, type PlanetCore } from '@gravito/core'
4178
+
4179
+ export class AppServiceProvider extends ServiceProvider {
4180
+ register(container: Container): void {
4181
+ // Register global services here
4182
+ }
4183
+
4184
+ boot(core: PlanetCore): void {
4185
+ core.logger.info('${context.name} (Action Domain) booted!')
4186
+ }
4187
+ }
4188
+ `;
4189
+ }
4190
+ generateBootstrap(context) {
4191
+ return `/**
4192
+ * Application Entry Point
4193
+ */
4194
+
4195
+ import { PlanetCore, securityHeaders, bodySizeLimit } from '@gravito/core'
4196
+ import { OrbitAtlas } from '@gravito/atlas'
4197
+ import databaseConfig from '../config/database'
4198
+ import { AppServiceProvider } from './providers/AppServiceProvider'
4199
+ import { registerApiRoutes } from './routes/api'
4200
+
4201
+ // Initialize Core
4202
+ const core = new PlanetCore({
4203
+ config: {
4204
+ APP_NAME: '${context.name}',
4205
+ database: databaseConfig
4206
+ },
4207
+ })
4208
+
4209
+ // Middleware
4210
+ core.adapter.use('*', securityHeaders())
4211
+ core.adapter.use('*', bodySizeLimit(1024 * 1024)) // 1MB
4212
+
4213
+ // Install Orbits
4214
+ await core.orbit(new OrbitAtlas())
4215
+
4216
+ // Service Providers
4217
+ core.register(new AppServiceProvider())
4218
+
4219
+ // Bootstrap
4220
+ await core.bootstrap()
4221
+
4222
+ // Routes
4223
+ registerApiRoutes(core.router)
4224
+
4225
+ // Liftoff
4226
+ export default core.liftoff()
4227
+ `;
4228
+ }
4229
+ generateArchitectureDoc(context) {
4230
+ 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';
4231
+ }
4232
+ generatePackageJson(context) {
4233
+ const pkg = {
4234
+ name: context.nameKebabCase,
4235
+ version: "0.1.0",
4236
+ type: "module",
4237
+ scripts: {
4238
+ dev: "bun run --watch src/bootstrap.ts",
4239
+ build: "bun build ./src/bootstrap.ts --outdir ./dist --target bun",
4240
+ start: "bun run dist/bootstrap.js",
4241
+ test: "bun test",
4242
+ typecheck: "tsc --noEmit"
4243
+ },
4244
+ dependencies: {
4245
+ "@gravito/core": "workspace:*",
4246
+ "@gravito/enterprise": "workspace:*",
4247
+ "@gravito/atlas": "workspace:*"
4248
+ // Usually needed for repositories
4249
+ },
4250
+ devDependencies: {
4251
+ "bun-types": "latest",
4252
+ typescript: "^5.0.0"
4253
+ }
4254
+ };
4255
+ return JSON.stringify(pkg, null, 2);
4256
+ }
4257
+ };
4258
+
4259
+ // src/Scaffold.ts
3131
4260
  var Scaffold = class {
3132
4261
  templatesDir;
3133
4262
  verbose;
@@ -3154,6 +4283,16 @@ var Scaffold = class {
3154
4283
  type: "ddd",
3155
4284
  name: "Domain-Driven Design",
3156
4285
  description: "Full DDD with Bounded Contexts and CQRS"
4286
+ },
4287
+ {
4288
+ type: "action-domain",
4289
+ name: "Action Domain",
4290
+ description: "Single Action Controllers pattern for high-clarity APIs"
4291
+ },
4292
+ {
4293
+ type: "satellite",
4294
+ name: "Gravito Satellite",
4295
+ description: "Plug-and-play module for the Gravito ecosystem"
3157
4296
  }
3158
4297
  ];
3159
4298
  }
@@ -3162,15 +4301,36 @@ var Scaffold = class {
3162
4301
  */
3163
4302
  async create(options) {
3164
4303
  const generator = this.createGenerator(options.architecture);
4304
+ const fs3 = await import("fs/promises");
4305
+ const profileResolver = new ProfileResolver();
4306
+ const profileConfig = profileResolver.resolve(options.profile, options.features);
3165
4307
  const context = BaseGenerator.createContext(
3166
4308
  options.name,
3167
4309
  options.targetDir,
3168
4310
  options.architecture,
3169
4311
  options.packageManager ?? "bun",
3170
- options.context ?? {}
4312
+ {
4313
+ ...options.context,
4314
+ withSpectrum: options.withSpectrum ?? false,
4315
+ isInternal: options.isInternal ?? false,
4316
+ profile: options.profile ?? "core",
4317
+ features: options.features ?? [],
4318
+ profileConfig
4319
+ }
3171
4320
  );
3172
4321
  try {
3173
4322
  const filesCreated = await generator.generate(context);
4323
+ const lockGenerator = new LockGenerator();
4324
+ const lockContent = lockGenerator.generate(
4325
+ options.profile ?? "core",
4326
+ profileConfig,
4327
+ "basic",
4328
+ // Default template for now, should come from options if applicable
4329
+ "1.0.0"
4330
+ );
4331
+ const lockPath = path3.resolve(options.targetDir, "gravito.lock.json");
4332
+ await fs3.writeFile(lockPath, lockContent, "utf-8");
4333
+ filesCreated.push(lockPath);
3174
4334
  return {
3175
4335
  success: true,
3176
4336
  targetDir: options.targetDir,
@@ -3200,6 +4360,10 @@ var Scaffold = class {
3200
4360
  return new CleanArchitectureGenerator(config);
3201
4361
  case "ddd":
3202
4362
  return new DddGenerator(config);
4363
+ case "action-domain":
4364
+ return new ActionDomainGenerator(config);
4365
+ case "satellite":
4366
+ return new SatelliteGenerator(config);
3203
4367
  default:
3204
4368
  throw new Error(`Unknown architecture type: ${type}`);
3205
4369
  }
@@ -3222,6 +4386,11 @@ export {
3222
4386
  CleanArchitectureGenerator,
3223
4387
  DddGenerator,
3224
4388
  EnterpriseMvcGenerator,
4389
+ EnvironmentDetector,
4390
+ FileMerger,
4391
+ LockGenerator,
4392
+ ProfileResolver,
4393
+ SatelliteGenerator,
3225
4394
  Scaffold,
3226
4395
  StubGenerator
3227
4396
  };