@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.
- package/dist/index.cjs +1473 -299
- package/dist/index.d.cts +132 -9
- package/dist/index.d.ts +132 -9
- package/dist/index.js +1468 -299
- package/package.json +8 -5
- package/templates/features/otel/.env.example +2 -0
- package/templates/features/otel/config/telemetry.ts +10 -0
- package/templates/features/otel/docker-compose.yml +15 -0
- package/templates/features/otel/package.json +7 -0
- package/templates/features/redis/.env.example +7 -0
- package/templates/features/redis/config/cache.ts +14 -0
- package/templates/features/redis/config/queue.ts +13 -0
- package/templates/features/redis/docker-compose.yml +17 -0
- package/templates/features/redis/package.json +5 -0
- package/templates/overlays/core/.env.example +14 -0
- package/templates/overlays/core/config/cache.ts +26 -0
- package/templates/overlays/core/config/database.ts +38 -0
- package/templates/overlays/core/config/queue.ts +31 -0
- package/templates/overlays/core/package.json +5 -0
- package/templates/overlays/enterprise/.env.example +26 -0
- package/templates/overlays/enterprise/config/cache.ts +31 -0
- package/templates/overlays/enterprise/config/database.ts +34 -0
- package/templates/overlays/enterprise/config/logging.ts +32 -0
- package/templates/overlays/enterprise/config/queue.ts +31 -0
- package/templates/overlays/enterprise/config/security.ts +20 -0
- package/templates/overlays/enterprise/docker-compose.yml +32 -0
- package/templates/overlays/enterprise/package.json +7 -0
- package/templates/overlays/scale/.env.example +22 -0
- package/templates/overlays/scale/config/cache.ts +31 -0
- package/templates/overlays/scale/config/database.ts +34 -0
- package/templates/overlays/scale/config/queue.ts +34 -0
- package/templates/overlays/scale/docker-compose.yml +32 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
#
|
|
319
|
-
|
|
554
|
+
# ============================================================================
|
|
555
|
+
# Database Configuration
|
|
556
|
+
# ============================================================================
|
|
320
557
|
|
|
321
|
-
# Database
|
|
322
|
-
DB_CONNECTION
|
|
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
|
-
#
|
|
326
|
-
|
|
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 '
|
|
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 '
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2157
|
+
"@gravito/core": "^1.0.0-beta.5",
|
|
2158
|
+
"@gravito/enterprise": "workspace:*"
|
|
1829
2159
|
},
|
|
1830
2160
|
devDependencies: {
|
|
1831
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
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
|
-
|
|
1953
|
-
|
|
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
|
-
|
|
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.
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2237
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2987
|
-
import {
|
|
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
|
-
|
|
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
|
});
|