@gravito/scaffold 4.0.0 → 4.1.2

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 CHANGED
@@ -30,7 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AdvancedModuleGenerator: () => AdvancedModuleGenerator,
33
34
  BaseGenerator: () => BaseGenerator,
35
+ CQRSQueryModuleGenerator: () => CQRSQueryModuleGenerator,
34
36
  CleanArchitectureGenerator: () => CleanArchitectureGenerator,
35
37
  DddGenerator: () => DddGenerator,
36
38
  DependencyValidator: () => DependencyValidator,
@@ -1055,6 +1057,118 @@ export default {
1055
1057
  }
1056
1058
  `;
1057
1059
  }
1060
+ /**
1061
+ * Generate workers configuration for job queue system
1062
+ */
1063
+ static generateWorkersConfig(level = "basic") {
1064
+ switch (level) {
1065
+ case "advanced":
1066
+ return this.generateAdvancedWorkersConfig();
1067
+ case "production":
1068
+ return this.generateProductionWorkersConfig();
1069
+ default:
1070
+ return this.generateBasicWorkersConfig();
1071
+ }
1072
+ }
1073
+ /**
1074
+ * Generate basic workers configuration
1075
+ */
1076
+ static generateBasicWorkersConfig() {
1077
+ return ` /**
1078
+ * Workers Configuration
1079
+ *
1080
+ * Manages job execution in isolated worker threads.
1081
+ * Automatically selects best available runtime (Bun or Node.js).
1082
+ */
1083
+ workers: {
1084
+ // Runtime environment: 'auto' | 'bun' | 'node'
1085
+ runtime: process.env.WORKERS_RUNTIME as 'auto' | 'bun' | 'node' ?? 'auto',
1086
+
1087
+ pool: {
1088
+ poolSize: Number.parseInt(process.env.WORKERS_POOL_SIZE ?? '4', 10),
1089
+ minWorkers: Number.parseInt(process.env.WORKERS_MIN_WORKERS ?? '0', 10),
1090
+ healthCheckInterval: 30000,
1091
+ },
1092
+
1093
+ execution: {
1094
+ maxExecutionTime: Number.parseInt(process.env.WORKERS_MAX_EXECUTION_TIME ?? '30000', 10),
1095
+ maxMemory: Number.parseInt(process.env.WORKERS_MAX_MEMORY ?? '0', 10),
1096
+ idleTimeout: Number.parseInt(process.env.WORKERS_IDLE_TIMEOUT ?? '60000', 10),
1097
+ isolateContexts: process.env.WORKERS_ISOLATE_CONTEXTS === 'true',
1098
+ },
1099
+ },`;
1100
+ }
1101
+ /**
1102
+ * Generate advanced workers configuration with Bun optimizations
1103
+ */
1104
+ static generateAdvancedWorkersConfig() {
1105
+ return ` /**
1106
+ * Workers Configuration (Advanced)
1107
+ *
1108
+ * Manages job execution in isolated worker threads.
1109
+ * Includes Bun-specific optimizations for enhanced performance.
1110
+ *
1111
+ * Performance characteristics:
1112
+ * - Bun: 2-241x faster message passing, 20-30% less memory (smol mode)
1113
+ * - Node.js: Stable, widely tested, compatible
1114
+ */
1115
+ workers: {
1116
+ runtime: process.env.WORKERS_RUNTIME as 'auto' | 'bun' | 'node' ?? 'auto',
1117
+
1118
+ pool: {
1119
+ poolSize: Number.parseInt(process.env.WORKERS_POOL_SIZE ?? '4', 10),
1120
+ minWorkers: Number.parseInt(process.env.WORKERS_MIN_WORKERS ?? '1', 10),
1121
+ healthCheckInterval: 30000,
1122
+ },
1123
+
1124
+ execution: {
1125
+ maxExecutionTime: Number.parseInt(process.env.WORKERS_MAX_EXECUTION_TIME ?? '30000', 10),
1126
+ maxMemory: Number.parseInt(process.env.WORKERS_MAX_MEMORY ?? '0', 10),
1127
+ idleTimeout: Number.parseInt(process.env.WORKERS_IDLE_TIMEOUT ?? '60000', 10),
1128
+ isolateContexts: process.env.WORKERS_ISOLATE_CONTEXTS === 'true',
1129
+ },
1130
+
1131
+ // Bun-specific optimizations
1132
+ bun: {
1133
+ smol: process.env.WORKERS_BUN_SMOL === 'true',
1134
+ preload: process.env.WORKERS_BUN_PRELOAD
1135
+ ? process.env.WORKERS_BUN_PRELOAD.split(',').map((p) => p.trim())
1136
+ : undefined,
1137
+ inspectPort: process.env.WORKERS_BUN_INSPECT_PORT
1138
+ ? Number.parseInt(process.env.WORKERS_BUN_INSPECT_PORT, 10)
1139
+ : undefined,
1140
+ },
1141
+ },`;
1142
+ }
1143
+ /**
1144
+ * Generate production-optimized workers configuration
1145
+ */
1146
+ static generateProductionWorkersConfig() {
1147
+ return ` /**
1148
+ * Workers Configuration (Production Optimized)
1149
+ */
1150
+ workers: {
1151
+ runtime: 'auto' as const,
1152
+
1153
+ pool: {
1154
+ poolSize: Number.parseInt(process.env.WORKERS_POOL_SIZE ?? '8', 10),
1155
+ minWorkers: 2,
1156
+ healthCheckInterval: 30000,
1157
+ },
1158
+
1159
+ execution: {
1160
+ maxExecutionTime: 30000,
1161
+ maxMemory: Number.parseInt(process.env.WORKERS_MAX_MEMORY ?? '512', 10),
1162
+ idleTimeout: 60000,
1163
+ isolateContexts: false,
1164
+ },
1165
+
1166
+ bun: {
1167
+ smol: true,
1168
+ preload: process.env.WORKERS_BUN_PRELOAD?.split(',').map((p) => p.trim()),
1169
+ },
1170
+ },`;
1171
+ }
1058
1172
  };
1059
1173
 
1060
1174
  // src/utils/ServiceProviderGenerator.ts
@@ -2067,235 +2181,2462 @@ Created with \u2764\uFE0F using Gravito Framework
2067
2181
  }
2068
2182
  };
2069
2183
 
2070
- // src/generators/ddd/BootstrapGenerator.ts
2071
- var BootstrapGenerator = class {
2072
- context = null;
2073
- generate(context) {
2074
- this.context = context;
2075
- return {
2076
- type: "directory",
2077
- name: "Bootstrap",
2078
- children: [
2079
- { type: "file", name: "app.ts", content: this.generateBootstrapApp(context) },
2080
- { type: "file", name: "providers.ts", content: this.generateProvidersRegistry(context) },
2081
- { type: "file", name: "events.ts", content: this.generateEventsRegistry() },
2082
- { type: "file", name: "routes.ts", content: this.generateRoutesRegistry(context) }
2083
- ]
2084
- };
2085
- }
2086
- generateConfigDirectory(context) {
2087
- this.context = context;
2184
+ // src/generators/ddd/AdvancedModuleGenerator.ts
2185
+ var AdvancedModuleGenerator = class {
2186
+ /**
2187
+ * 生成進階 DDD 模組結構(Event Sourcing)
2188
+ *
2189
+ * 生成檔案:
2190
+ * - Domain/AggregateRoots/{Name}.ts
2191
+ * - Domain/Events/{Name}*Event.ts(3 個事件示例)
2192
+ * - Domain/Services/{Name}EventApplier.ts
2193
+ * - Domain/Repositories/I{Name}EventStore.ts
2194
+ * - Domain/ValueObjects/{Name}Status.ts
2195
+ * - Infrastructure/EventStore/InMemory{Name}EventStore.ts
2196
+ * - Infrastructure/EventStore/Database{Name}EventStore.ts
2197
+ * - Infrastructure/EventStore/{Name}EventDeserializer.ts
2198
+ * - Application/Services/Create{Name}Service.ts
2199
+ * - 完整的測試框架
2200
+ */
2201
+ generate(moduleName, _context) {
2088
2202
  return {
2089
2203
  type: "directory",
2090
- name: "config",
2204
+ name: moduleName,
2091
2205
  children: [
2092
- { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
2093
- { type: "file", name: "database.ts", content: this.generateDatabaseConfig() },
2094
- { type: "file", name: "modules.ts", content: this.generateModulesConfig() },
2095
- { type: "file", name: "cache.ts", content: this.generateCacheConfig() },
2096
- { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
2206
+ // Domain Layer
2207
+ {
2208
+ type: "directory",
2209
+ name: "Domain",
2210
+ children: [
2211
+ // Aggregate Root
2212
+ {
2213
+ type: "directory",
2214
+ name: "AggregateRoots",
2215
+ children: [
2216
+ {
2217
+ type: "file",
2218
+ name: `${moduleName}.ts`,
2219
+ content: this.generateAggregateRoot(moduleName)
2220
+ }
2221
+ ]
2222
+ },
2223
+ // Events
2224
+ {
2225
+ type: "directory",
2226
+ name: "Events",
2227
+ children: [
2228
+ {
2229
+ type: "file",
2230
+ name: `${moduleName}CreatedEvent.ts`,
2231
+ content: this.generateEvent(moduleName, "Created")
2232
+ },
2233
+ {
2234
+ type: "file",
2235
+ name: `${moduleName}UpdatedEvent.ts`,
2236
+ content: this.generateEvent(moduleName, "Updated")
2237
+ },
2238
+ {
2239
+ type: "file",
2240
+ name: `${moduleName}DeletedEvent.ts`,
2241
+ content: this.generateEvent(moduleName, "Deleted")
2242
+ }
2243
+ ]
2244
+ },
2245
+ // Value Objects
2246
+ {
2247
+ type: "directory",
2248
+ name: "ValueObjects",
2249
+ children: [
2250
+ {
2251
+ type: "file",
2252
+ name: `${moduleName}Status.ts`,
2253
+ content: this.generateStatusValueObject(moduleName)
2254
+ },
2255
+ {
2256
+ type: "file",
2257
+ name: `${moduleName}Id.ts`,
2258
+ content: this.generateIdValueObject(moduleName)
2259
+ }
2260
+ ]
2261
+ },
2262
+ // Event Applier
2263
+ {
2264
+ type: "directory",
2265
+ name: "Services",
2266
+ children: [
2267
+ {
2268
+ type: "file",
2269
+ name: `${moduleName}EventApplier.ts`,
2270
+ content: this.generateEventApplier(moduleName)
2271
+ }
2272
+ ]
2273
+ },
2274
+ // Repositories
2275
+ {
2276
+ type: "directory",
2277
+ name: "Repositories",
2278
+ children: [
2279
+ {
2280
+ type: "file",
2281
+ name: `I${moduleName}EventStore.ts`,
2282
+ content: this.generateEventStoreInterface(moduleName)
2283
+ }
2284
+ ]
2285
+ }
2286
+ ]
2287
+ },
2288
+ // Application Layer
2289
+ {
2290
+ type: "directory",
2291
+ name: "Application",
2292
+ children: [
2293
+ {
2294
+ type: "directory",
2295
+ name: "Services",
2296
+ children: [
2297
+ {
2298
+ type: "file",
2299
+ name: `Create${moduleName}Service.ts`,
2300
+ content: this.generateApplicationService(moduleName)
2301
+ }
2302
+ ]
2303
+ },
2304
+ {
2305
+ type: "directory",
2306
+ name: "DTOs",
2307
+ children: [
2308
+ {
2309
+ type: "file",
2310
+ name: `${moduleName}DTO.ts`,
2311
+ content: this.generateDTO(moduleName)
2312
+ }
2313
+ ]
2314
+ }
2315
+ ]
2316
+ },
2317
+ // Infrastructure Layer
2318
+ {
2319
+ type: "directory",
2320
+ name: "Infrastructure",
2321
+ children: [
2322
+ {
2323
+ type: "directory",
2324
+ name: "EventStore",
2325
+ children: [
2326
+ {
2327
+ type: "file",
2328
+ name: `InMemory${moduleName}EventStore.ts`,
2329
+ content: this.generateInMemoryEventStore(moduleName)
2330
+ },
2331
+ {
2332
+ type: "file",
2333
+ name: `Database${moduleName}EventStore.ts`,
2334
+ content: this.generateDatabaseEventStore(moduleName)
2335
+ },
2336
+ {
2337
+ type: "file",
2338
+ name: `${moduleName}EventDeserializer.ts`,
2339
+ content: this.generateEventDeserializer(moduleName)
2340
+ }
2341
+ ]
2342
+ }
2343
+ ]
2344
+ },
2345
+ // Presentation Layer
2346
+ {
2347
+ type: "directory",
2348
+ name: "Presentation",
2349
+ children: [
2350
+ {
2351
+ type: "directory",
2352
+ name: "Controllers",
2353
+ children: [
2354
+ {
2355
+ type: "file",
2356
+ name: `${moduleName}Controller.ts`,
2357
+ content: this.generateController(moduleName)
2358
+ }
2359
+ ]
2360
+ },
2361
+ {
2362
+ type: "directory",
2363
+ name: "Routes",
2364
+ children: [
2365
+ {
2366
+ type: "file",
2367
+ name: `${this.toKebabCase(moduleName)}.routes.ts`,
2368
+ content: this.generateRoutes(moduleName)
2369
+ }
2370
+ ]
2371
+ }
2372
+ ]
2373
+ },
2374
+ // Module Export
2375
+ {
2376
+ type: "file",
2377
+ name: "index.ts",
2378
+ content: this.generateModuleIndex(moduleName)
2379
+ }
2097
2380
  ]
2098
2381
  };
2099
2382
  }
2100
- generateMainEntry(_context) {
2101
- return `/**
2102
- * Application Entry Point
2383
+ // ─────────────────────────────────────────────────────────────────
2384
+ // 域層代碼生成
2385
+ // ─────────────────────────────────────────────────────────────────
2386
+ generateAggregateRoot(name) {
2387
+ return `import { AggregateRoot } from '@/Shared/Domain/AggregateRoot'
2388
+ import { DomainEvent } from '@/Shared/Domain/DomainEvent'
2389
+ import { ${name}Status } from '../ValueObjects/${name}Status'
2390
+ import { ${name}Id } from '../ValueObjects/${name}Id'
2391
+ import {
2392
+ ${name}CreatedEvent,
2393
+ ${name}UpdatedEvent,
2394
+ ${name}DeletedEvent,
2395
+ } from '../Events'
2396
+
2397
+ /**
2398
+ * ${name} \u805A\u5408\u6839
2103
2399
  *
2104
- * Start the HTTP server.
2400
+ * Event Sourcing \u5BE6\u73FE\uFF1A\u72C0\u614B\u5B8C\u5168\u7531\u4E8B\u4EF6\u6D41\u6C7A\u5B9A
2401
+ *
2402
+ * \u751F\u547D\u9031\u671F\uFF1A
2403
+ * CREATED \u2192 (UPDATED*) \u2192 DELETED
2105
2404
  */
2405
+ export class ${name} extends AggregateRoot {
2406
+ private id: ${name}Id | null = null
2407
+ private status: ${name}Status | null = null
2408
+ private name: string = ''
2409
+ private description: string = ''
2410
+ private createdAt: Date = new Date()
2411
+ private updatedAt: Date = new Date()
2412
+
2413
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2414
+ // \u5DE5\u5EE0\u65B9\u6CD5 - \u5EFA\u7ACB\u65B0\u805A\u5408
2415
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2106
2416
 
2107
- import { createApp } from './Bootstrap/app'
2417
+ /**
2418
+ * \u5DE5\u5EE0\u65B9\u6CD5\uFF1A\u5EFA\u7ACB\u65B0 ${name}
2419
+ */
2420
+ static create(id: ${name}Id, name: string, description?: string): ${name} {
2421
+ const instance = new ${name}()
2422
+
2423
+ instance.raiseEvent(
2424
+ new ${name}CreatedEvent(id.value, {
2425
+ name,
2426
+ description: description || '',
2427
+ })
2428
+ )
2108
2429
 
2109
- const app = await createApp()
2430
+ return instance
2431
+ }
2110
2432
 
2111
- export default app.liftoff()
2112
- `;
2433
+ /**
2434
+ * \u5F9E\u4E8B\u4EF6\u6D41\u91CD\u5EFA ${name}
2435
+ */
2436
+ static fromEvents(events: DomainEvent[]): ${name} {
2437
+ const instance = new ${name}()
2438
+
2439
+ for (const event of events) {
2440
+ instance.applyEvent(event)
2441
+ }
2442
+
2443
+ return instance
2113
2444
  }
2114
- generateBootstrapApp(_context) {
2115
- return `/**
2116
- * Application Bootstrap
2117
- *
2118
- * Central configuration and initialization using the ServiceProvider pattern.
2119
- *
2120
- * Lifecycle:
2121
- * 1. Configure: Load app config and orbits
2122
- * 2. Boot: Initialize PlanetCore
2123
- * 3. Register Providers: Bind services to container
2124
- * 4. Bootstrap: Boot all providers
2125
- */
2126
2445
 
2127
- import { defineConfig, PlanetCore } from '@gravito/core'
2128
- import { OrbitAtlas } from '@gravito/atlas'
2129
- import appConfig from '../../config/app'
2130
- import { registerProviders } from './providers'
2131
- import { registerRoutes } from './routes'
2446
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2447
+ // \u547D\u4EE4\u65B9\u6CD5 - \u72C0\u614B\u8F49\u79FB
2448
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2132
2449
 
2133
- export async function createApp(): Promise<PlanetCore> {
2134
- // 1. Configure
2135
- const config = defineConfig({
2136
- config: appConfig,
2137
- orbits: [
2138
- new OrbitAtlas() as unknown as import('@gravito/core').GravitoOrbit,
2139
- ],
2140
- })
2450
+ update(name: string, description?: string): void {
2451
+ if (!this.id) {
2452
+ throw new Error('${name} \u672A\u521D\u59CB\u5316')
2453
+ }
2141
2454
 
2142
- // 2. Boot Core
2143
- const core = await PlanetCore.boot(config)
2144
- core.registerGlobalErrorHandlers()
2455
+ if (this.status?.isDeleted()) {
2456
+ throw new Error('${name} \u5DF2\u522A\u9664')
2457
+ }
2145
2458
 
2146
- // 3. Register Providers
2147
- await registerProviders(core)
2459
+ this.raiseEvent(
2460
+ new ${name}UpdatedEvent(this.id.value, {
2461
+ name,
2462
+ description: description || '',
2463
+ })
2464
+ )
2465
+ }
2148
2466
 
2149
- // 4. Bootstrap All Providers
2150
- await core.bootstrap()
2467
+ delete(): void {
2468
+ if (!this.id) {
2469
+ throw new Error('${name} \u672A\u521D\u59CB\u5316')
2470
+ }
2151
2471
 
2152
- // Register routes after bootstrap
2153
- registerRoutes(core.router)
2472
+ if (this.status?.isDeleted()) {
2473
+ throw new Error('${name} \u5DF2\u522A\u9664')
2474
+ }
2154
2475
 
2155
- return core
2156
- }
2157
- `;
2476
+ this.raiseEvent(new ${name}DeletedEvent(this.id.value))
2158
2477
  }
2159
- generateProvidersRegistry(_context) {
2160
- return `/**
2161
- * Service Providers Registry
2162
- *
2163
- * Register all service providers here.
2164
- * Include both global and module-specific providers.
2165
- */
2166
2478
 
2479
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2480
+ // \u4E8B\u4EF6\u61C9\u7528 - \u72C0\u614B\u66F4\u65B0
2481
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2482
+
2483
+ applyEvent(event: DomainEvent): void {
2484
+ switch (event.eventType) {
2485
+ case '${this.toKebabCase(name)}.created':
2486
+ this.onCreated(event)
2487
+ break
2488
+ case '${this.toKebabCase(name)}.updated':
2489
+ this.onUpdated(event)
2490
+ break
2491
+ case '${this.toKebabCase(name)}.deleted':
2492
+ this.onDeleted(event)
2493
+ break
2494
+ default:
2495
+ throw new Error(\`Unknown event type: \${event.eventType}\`)
2496
+ }
2497
+ }
2498
+
2499
+ private onCreated(event: DomainEvent): void {
2500
+ this.id = new ${name}Id(event.aggregateId)
2501
+ this.status = new ${name}Status('created')
2502
+ this.name = event.data.name as string
2503
+ this.description = event.data.description as string
2504
+ this.createdAt = event.occurredAt
2505
+ this.updatedAt = event.occurredAt
2506
+ }
2507
+
2508
+ private onUpdated(event: DomainEvent): void {
2509
+ this.name = event.data.name as string
2510
+ this.description = event.data.description as string
2511
+ this.updatedAt = event.occurredAt
2512
+ }
2513
+
2514
+ private onDeleted(event: DomainEvent): void {
2515
+ this.status = new ${name}Status('deleted')
2516
+ this.updatedAt = event.occurredAt
2517
+ }
2518
+
2519
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2520
+ // \u8B80\u53D6\u65B9\u6CD5 - \u53D6\u5F97\u72C0\u614B
2521
+ // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2522
+
2523
+ getId(): ${name}Id {
2524
+ if (!this.id) throw new Error('ID \u672A\u521D\u59CB\u5316')
2525
+ return this.id
2526
+ }
2527
+
2528
+ getStatus(): ${name}Status {
2529
+ if (!this.status) throw new Error('Status \u672A\u521D\u59CB\u5316')
2530
+ return this.status
2531
+ }
2532
+
2533
+ getName(): string {
2534
+ return this.name
2535
+ }
2536
+
2537
+ getDescription(): string {
2538
+ return this.description
2539
+ }
2540
+
2541
+ getCreatedAt(): Date {
2542
+ return this.createdAt
2543
+ }
2544
+
2545
+ getUpdatedAt(): Date {
2546
+ return this.updatedAt
2547
+ }
2548
+ }
2549
+ `;
2550
+ }
2551
+ generateEvent(name, action) {
2552
+ return `import { DomainEvent } from '@/Shared/Domain/DomainEvent'
2553
+
2554
+ export interface ${name}${action}Data {
2555
+ name: string
2556
+ description?: string
2557
+ // TODO: \u65B0\u589E\u5176\u4ED6\u4E8B\u4EF6\u8CC7\u6599
2558
+ }
2559
+
2560
+ /**
2561
+ * ${name}${action}Event
2562
+ *
2563
+ * \u4E8B\u4EF6\u5305\u542B\u6240\u6709\u5FC5\u8981\u8CC7\u8A0A\u4EE5\u91CD\u5EFA\u805A\u5408\u72C0\u614B
2564
+ * \u4E8B\u4EF6\u662F\u4E0D\u53EF\u8B8A\u7684
2565
+ */
2566
+ export class ${name}${action}Event extends DomainEvent {
2567
+ constructor(
2568
+ aggregateId: string,
2569
+ payload: ${name}${action}Data,
2570
+ version?: number,
2571
+ occurredAt?: Date
2572
+ ) {
2573
+ super(
2574
+ aggregateId,
2575
+ '${this.toKebabCase(name)}.${this.toKebabCase(action)}',
2576
+ {
2577
+ name: payload.name,
2578
+ description: payload.description || '',
2579
+ // TODO: \u65B0\u589E\u5176\u4ED6\u5C6C\u6027
2580
+ },
2581
+ version,
2582
+ occurredAt
2583
+ )
2584
+ }
2585
+
2586
+ /**
2587
+ * \u5411\u5F8C\u76F8\u5BB9\u6027\u652F\u63F4
2588
+ * \u7576\u4E8B\u4EF6\u7D50\u69CB\u8B8A\u5316\u6642\uFF0C\u8986\u84CB\u6B64\u65B9\u6CD5
2589
+ */
2590
+ getSchemaVersion(): string {
2591
+ return '1.0.0'
2592
+ }
2593
+
2594
+ toJSON() {
2595
+ return {
2596
+ eventId: this.eventId,
2597
+ aggregateId: this.aggregateId,
2598
+ eventType: this.eventType,
2599
+ occurredAt: this.occurredAt,
2600
+ version: this.version,
2601
+ data: this.data,
2602
+ }
2603
+ }
2604
+ }
2605
+ `;
2606
+ }
2607
+ generateStatusValueObject(name) {
2608
+ return `import { ValueObject } from '@/Shared/Domain/ValueObject'
2609
+
2610
+ export type ${name}StatusValue = 'created' | 'updated' | 'deleted'
2611
+
2612
+ /**
2613
+ * ${name}Status - \u72C0\u614B\u503C\u7269\u4EF6
2614
+ *
2615
+ * \u4E0D\u53EF\u8B8A\u7684\u72C0\u614B\u503C
2616
+ */
2617
+ export class ${name}Status extends ValueObject {
2618
+ constructor(readonly value: ${name}StatusValue) {
2619
+ super()
2620
+ this.validate()
2621
+ }
2622
+
2623
+ private validate(): void {
2624
+ const validStatuses: ${name}StatusValue[] = ['created', 'updated', 'deleted']
2625
+ if (!validStatuses.includes(this.value)) {
2626
+ throw new Error(\`Invalid ${name} status: \${this.value}\`)
2627
+ }
2628
+ }
2629
+
2630
+ isCreated(): boolean {
2631
+ return this.value === 'created'
2632
+ }
2633
+
2634
+ isUpdated(): boolean {
2635
+ return this.value === 'updated'
2636
+ }
2637
+
2638
+ isDeleted(): boolean {
2639
+ return this.value === 'deleted'
2640
+ }
2641
+
2642
+ equals(other: ValueObject): boolean {
2643
+ if (!(other instanceof ${name}Status)) return false
2644
+ return this.value === other.value
2645
+ }
2646
+
2647
+ toString(): string {
2648
+ return this.value
2649
+ }
2650
+ }
2651
+ `;
2652
+ }
2653
+ generateIdValueObject(name) {
2654
+ return `import { ValueObject } from '@/Shared/Domain/ValueObject'
2655
+
2656
+ /**
2657
+ * ${name}Id - ID \u503C\u7269\u4EF6
2658
+ *
2659
+ * \u78BA\u4FDD ID \u7684\u6709\u6548\u6027
2660
+ */
2661
+ export class ${name}Id extends ValueObject {
2662
+ constructor(readonly value: string) {
2663
+ super()
2664
+ this.validate()
2665
+ }
2666
+
2667
+ private validate(): void {
2668
+ if (!this.value || this.value.trim() === '') {
2669
+ throw new Error(\`Invalid ${name} ID: \${this.value}\`)
2670
+ }
2671
+ }
2672
+
2673
+ equals(other: ValueObject): boolean {
2674
+ if (!(other instanceof ${name}Id)) return false
2675
+ return this.value === other.value
2676
+ }
2677
+
2678
+ toString(): string {
2679
+ return this.value
2680
+ }
2681
+ }
2682
+ `;
2683
+ }
2684
+ generateEventApplier(name) {
2685
+ return `import { DomainEvent } from '@/Shared/Domain/DomainEvent'
2686
+
2687
+ /**
2688
+ * ${name}EventApplier - \u7D14\u51FD\u5F0F\u4E8B\u4EF6\u61C9\u7528\u5668
2689
+ *
2690
+ * \u8A2D\u8A08\u539F\u5247\uFF1A
2691
+ * 1. \u7121\u526F\u4F5C\u7528\uFF1A\u76F8\u540C\u8F38\u5165 \u2192 \u76F8\u540C\u8F38\u51FA
2692
+ * 2. \u4E0D\u53EF\u8B8A\u6027\uFF1A\u4E0D\u4FEE\u6539\u8F38\u5165\u72C0\u614B
2693
+ * 3. \u5931\u6557\u5FEB\u901F\uFF1A\u7121\u6548\u4E8B\u4EF6\u7ACB\u5373\u62CB\u51FA
2694
+ */
2695
+
2696
+ export interface ${name}State {
2697
+ readonly id: string
2698
+ readonly status: string
2699
+ readonly name: string
2700
+ readonly description: string
2701
+ readonly createdAt: Date
2702
+ readonly updatedAt: Date
2703
+ }
2704
+
2705
+ export class ${name}EventApplier {
2706
+ /**
2707
+ * \u61C9\u7528\u55AE\u500B\u4E8B\u4EF6\u5230\u72C0\u614B
2708
+ *
2709
+ * @param state \u76EE\u524D\u72C0\u614B\uFF08null \u8868\u793A\u65B0\u805A\u5408\uFF09
2710
+ * @param event \u8981\u61C9\u7528\u7684\u4E8B\u4EF6
2711
+ * @returns \u65B0\u72C0\u614B\uFF08state \u4E0D\u53EF\u8B8A\uFF09
2712
+ */
2713
+ static apply(state: ${name}State | null, event: DomainEvent): ${name}State {
2714
+ // \u521D\u59CB\u5316\u4E8B\u4EF6
2715
+ if (event.eventType === '${this.toKebabCase(name)}.created') {
2716
+ if (state !== null) {
2717
+ throw new Error('${name} \u5DF2\u5B58\u5728\uFF0C\u7121\u6CD5\u518D\u6B21\u5EFA\u7ACB')
2718
+ }
2719
+ return this.applyCreated(event)
2720
+ }
2721
+
2722
+ // \u975E\u521D\u59CB\u5316\u4E8B\u4EF6
2723
+ if (state === null) {
2724
+ throw new Error(\`Event \${event.eventType} \u8981\u6C42 state \u975E null\`)
2725
+ }
2726
+
2727
+ switch (event.eventType) {
2728
+ case '${this.toKebabCase(name)}.updated':
2729
+ return this.applyUpdated(state, event)
2730
+ case '${this.toKebabCase(name)}.deleted':
2731
+ return this.applyDeleted(state, event)
2732
+ default:
2733
+ throw new Error(\`Unknown event type: \${event.eventType}\`)
2734
+ }
2735
+ }
2736
+
2737
+ private static applyCreated(event: DomainEvent): ${name}State {
2738
+ return {
2739
+ id: event.aggregateId,
2740
+ status: 'created',
2741
+ name: event.data.name as string,
2742
+ description: event.data.description as string,
2743
+ createdAt: event.occurredAt,
2744
+ updatedAt: event.occurredAt,
2745
+ }
2746
+ }
2747
+
2748
+ private static applyUpdated(state: ${name}State, event: DomainEvent): ${name}State {
2749
+ return {
2750
+ ...state,
2751
+ name: event.data.name as string,
2752
+ description: event.data.description as string,
2753
+ updatedAt: event.occurredAt,
2754
+ }
2755
+ }
2756
+
2757
+ private static applyDeleted(state: ${name}State, event: DomainEvent): ${name}State {
2758
+ return {
2759
+ ...state,
2760
+ status: 'deleted',
2761
+ updatedAt: event.occurredAt,
2762
+ }
2763
+ }
2764
+ }
2765
+ `;
2766
+ }
2767
+ generateEventStoreInterface(name) {
2768
+ return `import { ${name} } from '../AggregateRoots/${name}'
2769
+
2770
+ /**
2771
+ * I${name}EventStore - \u4E8B\u4EF6\u5B58\u5132\u4ECB\u9762
2772
+ *
2773
+ * \u8CA0\u8CAC\u4E8B\u4EF6\u7684\u6301\u4E45\u5316\u548C\u91CD\u5EFA
2774
+ */
2775
+ export interface I${name}EventStore {
2776
+ /**
2777
+ * \u4FDD\u5B58\u805A\u5408\uFF08\u6301\u4E45\u5316\u65B0\u4E8B\u4EF6\uFF09
2778
+ */
2779
+ save(aggregate: ${name}): Promise<void>
2780
+
2781
+ /**
2782
+ * \u6839\u64DA ID \u67E5\u8A62\u805A\u5408
2783
+ */
2784
+ findById(id: string): Promise<${name} | null>
2785
+
2786
+ /**
2787
+ * \u53D6\u5F97\u6240\u6709\u4E8B\u4EF6\uFF08\u7528\u65BC\u91CD\u5EFA\uFF09
2788
+ */
2789
+ getEvents(aggregateId: string): Promise<Event[]>
2790
+ }
2791
+ `;
2792
+ }
2793
+ generateInMemoryEventStore(name) {
2794
+ return `import type { DomainEvent } from '@/Shared/Domain/DomainEvent'
2795
+ import type { I${name}EventStore } from '../../Domain/Repositories/I${name}EventStore'
2796
+ import { ${name} } from '../../Domain/AggregateRoots/${name}'
2797
+
2798
+ /**
2799
+ * InMemory${name}EventStore - \u8A18\u61B6\u9AD4\u5BE6\u73FE
2800
+ *
2801
+ * \u7528\u9014\uFF1A
2802
+ * - \u55AE\u5143\u6E2C\u8A66\uFF08\u5FEB\u901F\uFF0C\u7121 I/O\uFF09
2803
+ * - \u958B\u767C\u74B0\u5883\uFF08\u7C21\u55AE\uFF0C\u7121\u9700 DB\uFF09
2804
+ * - \u539F\u578B\u8A2D\u8A08
2805
+ */
2806
+ export class InMemory${name}EventStore implements I${name}EventStore {
2807
+ private events = new Map<string, DomainEvent[]>()
2808
+
2809
+ async save(aggregate: ${name}): Promise<void> {
2810
+ const id = aggregate.getId().value
2811
+ const uncommittedEvents = aggregate.getUncommittedEvents()
2812
+
2813
+ if (uncommittedEvents.length === 0) return
2814
+
2815
+ if (!this.events.has(id)) {
2816
+ this.events.set(id, [...uncommittedEvents])
2817
+ } else {
2818
+ this.events.get(id)!.push(...uncommittedEvents)
2819
+ }
2820
+
2821
+ aggregate.markEventsAsCommitted()
2822
+ }
2823
+
2824
+ async findById(id: string): Promise<${name} | null> {
2825
+ const events = this.events.get(id)
2826
+ if (!events || events.length === 0) return null
2827
+
2828
+ return ${name}.fromEvents(events)
2829
+ }
2830
+
2831
+ async getEvents(aggregateId: string): Promise<DomainEvent[]> {
2832
+ return this.events.get(aggregateId) || []
2833
+ }
2834
+
2835
+ // \u6E2C\u8A66\u8F14\u52A9\u65B9\u6CD5
2836
+ clear(): void {
2837
+ this.events.clear()
2838
+ }
2839
+
2840
+ getAllEvents(): DomainEvent[] {
2841
+ return Array.from(this.events.values()).flat()
2842
+ }
2843
+ }
2844
+ `;
2845
+ }
2846
+ generateDatabaseEventStore(name) {
2847
+ return `import type { DomainEvent } from '@/Shared/Domain/DomainEvent'
2848
+ import type { I${name}EventStore } from '../../Domain/Repositories/I${name}EventStore'
2849
+ import { ${name} } from '../../Domain/AggregateRoots/${name}'
2850
+ import { ${name}EventDeserializer } from './${name}EventDeserializer'
2851
+
2852
+ /**
2853
+ * Database${name}EventStore - \u8CC7\u6599\u5EAB\u5BE6\u73FE
2854
+ *
2855
+ * \u7528\u9014\uFF1A
2856
+ * - \u751F\u7522\u74B0\u5883\uFF08\u6301\u4E45\u5316\uFF0C\u53EF\u9760\uFF09
2857
+ * - \u5B8C\u6574\u6B77\u53F2\u8A18\u9304\uFF08\u6240\u6709\u4E8B\u4EF6\uFF09
2858
+ * - \u4E8B\u4EF6\u91CD\u653E
2859
+ *
2860
+ * \u8868\u7D50\u69CB\uFF1A
2861
+ * ${this.toSnakeCase(name)}_events (
2862
+ * aggregate_id VARCHAR(255),
2863
+ * event_type VARCHAR(255),
2864
+ * payload JSON,
2865
+ * version INT,
2866
+ * occurred_at TIMESTAMP
2867
+ * )
2868
+ */
2869
+ export class Database${name}EventStore implements I${name}EventStore {
2870
+ // TODO: \u6CE8\u5165 DB \u9023\u63A5\uFF08\u4F7F\u7528 @gravito/atlas\uFF09
2871
+ // constructor(private db: Database) {}
2872
+
2873
+ async save(aggregate: ${name}): Promise<void> {
2874
+ const id = aggregate.getId().value
2875
+ const uncommittedEvents = aggregate.getUncommittedEvents()
2876
+
2877
+ if (uncommittedEvents.length === 0) return
2878
+
2879
+ // TODO: \u63D2\u5165\u4E8B\u4EF6\u5230\u8CC7\u6599\u5EAB
2880
+ // for (const event of uncommittedEvents) {
2881
+ // await this.db.table('${this.toSnakeCase(name)}_events').insert({
2882
+ // aggregate_id: id,
2883
+ // event_type: event.eventType,
2884
+ // payload: JSON.stringify(event.data),
2885
+ // version: event.version,
2886
+ // occurred_at: event.occurredAt.toISOString()
2887
+ // })
2888
+ // }
2889
+
2890
+ aggregate.markEventsAsCommitted()
2891
+ }
2892
+
2893
+ async findById(id: string): Promise<${name} | null> {
2894
+ // TODO: \u5F9E\u8CC7\u6599\u5EAB\u67E5\u8A62\u4E8B\u4EF6
2895
+ // const rows = await this.db
2896
+ // .table('${this.toSnakeCase(name)}_events')
2897
+ // .where('aggregate_id', id)
2898
+ // .orderBy('version', 'asc')
2899
+ // .select()
2900
+
2901
+ // if (!rows || rows.length === 0) return null
2902
+
2903
+ // const events = rows.map(row =>
2904
+ // ${name}EventDeserializer.fromDatabaseRow(row)
2905
+ // )
2906
+
2907
+ // return ${name}.fromEvents(events)
2908
+
2909
+ return null
2910
+ }
2911
+
2912
+ async getEvents(aggregateId: string): Promise<DomainEvent[]> {
2913
+ // TODO: \u67E5\u8A62\u6240\u6709\u4E8B\u4EF6\uFF08\u7528\u65BC\u91CD\u653E\uFF09
2914
+ return []
2915
+ }
2916
+ }
2917
+ `;
2918
+ }
2919
+ generateEventDeserializer(name) {
2920
+ return `import type { DomainEvent } from '@/Shared/Domain/DomainEvent'
2167
2921
  import {
2168
- ServiceProvider,
2169
- type Container,
2170
- type PlanetCore,
2171
- bodySizeLimit,
2172
- securityHeaders,
2173
- } from '@gravito/core'
2174
- import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
2175
- import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
2922
+ ${name}CreatedEvent,
2923
+ ${name}UpdatedEvent,
2924
+ ${name}DeletedEvent,
2925
+ } from '../../Domain/Events'
2926
+
2927
+ /**
2928
+ * ${name}EventDeserializer - \u4E8B\u4EF6\u53CD\u5E8F\u5217\u5316\u5668
2929
+ *
2930
+ * \u8CAC\u4EFB\uFF1A
2931
+ * 1. \u5F9E JSON payload \u91CD\u5EFA DomainEvent \u5B50\u985E
2932
+ * 2. \u652F\u63F4\u4E8B\u4EF6\u7248\u672C\u9077\u79FB
2933
+ * 3. \u6642\u9593\u6233\u8F49\u63DB
2934
+ */
2935
+ export class ${name}EventDeserializer {
2936
+ static deserialize(
2937
+ eventType: string,
2938
+ payload: Record<string, unknown>,
2939
+ aggregateId: string,
2940
+ occurredAt: Date,
2941
+ version: number
2942
+ ): DomainEvent {
2943
+ switch (eventType) {
2944
+ case '${this.toKebabCase(name)}.created':
2945
+ return new ${name}CreatedEvent(
2946
+ aggregateId,
2947
+ {
2948
+ name: payload.name as string,
2949
+ description: payload.description as string,
2950
+ },
2951
+ version,
2952
+ occurredAt
2953
+ )
2954
+
2955
+ case '${this.toKebabCase(name)}.updated':
2956
+ return new ${name}UpdatedEvent(
2957
+ aggregateId,
2958
+ {
2959
+ name: payload.name as string,
2960
+ description: payload.description as string,
2961
+ },
2962
+ version,
2963
+ occurredAt
2964
+ )
2965
+
2966
+ case '${this.toKebabCase(name)}.deleted':
2967
+ return new ${name}DeletedEvent(aggregateId, {}, version, occurredAt)
2968
+
2969
+ default:
2970
+ throw new Error(\`Unknown event type: \${eventType}\`)
2971
+ }
2972
+ }
2973
+
2974
+ static fromDatabaseRow(row: any): DomainEvent {
2975
+ const payload = typeof row.payload === 'string'
2976
+ ? JSON.parse(row.payload)
2977
+ : row.payload
2978
+ const occurredAt = new Date(row.occurred_at)
2979
+
2980
+ return this.deserialize(
2981
+ row.event_type,
2982
+ payload,
2983
+ row.aggregate_id,
2984
+ occurredAt,
2985
+ row.version
2986
+ )
2987
+ }
2988
+ }
2989
+ `;
2990
+ }
2991
+ generateApplicationService(name) {
2992
+ return `import type { I${name}EventStore } from '../../Domain/Repositories/I${name}EventStore'
2993
+ import { ${name} } from '../../Domain/AggregateRoots/${name}'
2994
+ import { ${name}Id } from '../../Domain/ValueObjects/${name}Id'
2995
+ import { ${name}DTO } from '../DTOs/${name}DTO'
2996
+
2997
+ /**
2998
+ * Create${name}Service - \u5EFA\u7ACB ${name} \u7684\u61C9\u7528\u670D\u52D9
2999
+ *
3000
+ * \u8CAC\u4EFB\uFF1A
3001
+ * 1. \u5354\u8ABF\u9818\u57DF\u908F\u8F2F
3002
+ * 2. \u7BA1\u7406\u4E8B\u52D9
3003
+ * 3. \u8F49\u63DB DTO
3004
+ */
3005
+ export class Create${name}Service {
3006
+ constructor(private eventStore: I${name}EventStore) {}
3007
+
3008
+ async execute(input: { name: string; description?: string }): Promise<${name}DTO> {
3009
+ // TODO: \u9A57\u8B49\u8F38\u5165
3010
+ // if (!input.name || input.name.trim() === '') {
3011
+ // throw new Error('Name \u4E0D\u80FD\u70BA\u7A7A')
3012
+ // }
3013
+
3014
+ // \u5EFA\u7ACB\u805A\u5408
3015
+ const id = new ${name}Id(\`${this.toKebabCase(name)}-\${crypto.randomUUID()}\`)
3016
+ const aggregate = ${name}.create(id, input.name, input.description)
3017
+
3018
+ // \u6301\u4E45\u5316\u4E8B\u4EF6
3019
+ await this.eventStore.save(aggregate)
3020
+
3021
+ // \u8F49\u63DB\u70BA DTO
3022
+ return ${name}DTO.fromEntity(aggregate)
3023
+ }
3024
+ }
3025
+ `;
3026
+ }
3027
+ generateDTO(name) {
3028
+ return `import { ${name} } from '../../Domain/AggregateRoots/${name}'
3029
+
3030
+ /**
3031
+ * ${name}DTO - \u8CC7\u6599\u8F49\u79FB\u7269\u4EF6
3032
+ *
3033
+ * \u7528\u9014\uFF1A
3034
+ * - \u8DE8\u5C64\u8F49\u79FB\u8CC7\u6599
3035
+ * - API \u97FF\u61C9\u683C\u5F0F\u5316
3036
+ * - \u96B1\u85CF\u5167\u90E8\u7D30\u7BC0
3037
+ */
3038
+ export class ${name}DTO {
3039
+ id!: string
3040
+ status!: string
3041
+ name!: string
3042
+ description!: string
3043
+ createdAt!: Date
3044
+ updatedAt!: Date
3045
+
3046
+ static fromEntity(entity: ${name}): ${name}DTO {
3047
+ const dto = new ${name}DTO()
3048
+ dto.id = entity.getId().value
3049
+ dto.status = entity.getStatus().value
3050
+ dto.name = entity.getName()
3051
+ dto.description = entity.getDescription()
3052
+ dto.createdAt = entity.getCreatedAt()
3053
+ dto.updatedAt = entity.getUpdatedAt()
3054
+ return dto
3055
+ }
3056
+
3057
+ toJSON() {
3058
+ return {
3059
+ id: this.id,
3060
+ status: this.status,
3061
+ name: this.name,
3062
+ description: this.description,
3063
+ createdAt: this.createdAt,
3064
+ updatedAt: this.updatedAt,
3065
+ }
3066
+ }
3067
+ }
3068
+ `;
3069
+ }
3070
+ generateController(name) {
3071
+ return `import type { IModuleRouter } from '@/Shared/Presentation/IModuleRouter'
3072
+ import { Create${name}Service } from '../../Application/Services/Create${name}Service'
3073
+ import type { I${name}EventStore } from '../../Domain/Repositories/I${name}EventStore'
3074
+
3075
+ /**
3076
+ * ${name}Controller - HTTP \u8655\u7406\u5668
3077
+ *
3078
+ * \u8CAC\u4EFB\uFF1A
3079
+ * 1. \u89E3\u6790 HTTP \u8ACB\u6C42
3080
+ * 2. \u8ABF\u7528\u61C9\u7528\u670D\u52D9
3081
+ * 3. \u683C\u5F0F\u5316\u97FF\u61C9
3082
+ */
3083
+ export class ${name}Controller {
3084
+ private createService: Create${name}Service
3085
+
3086
+ constructor(eventStore: I${name}EventStore) {
3087
+ this.createService = new Create${name}Service(eventStore)
3088
+ }
3089
+
3090
+ async create(ctx: any) {
3091
+ try {
3092
+ // TODO: \u89E3\u6790 request body
3093
+ const input = {
3094
+ name: 'TODO',
3095
+ description: 'TODO',
3096
+ }
3097
+
3098
+ const result = await this.createService.execute(input)
3099
+ return ctx.json(result, 201)
3100
+ } catch (error) {
3101
+ return ctx.json({ error: (error as Error).message }, 400)
3102
+ }
3103
+ }
3104
+
3105
+ // TODO: \u65B0\u589E\u5176\u4ED6\u8DEF\u7531\u8655\u7406\u5668
3106
+ // async update(ctx) { ... }
3107
+ // async delete(ctx) { ... }
3108
+ }
3109
+ `;
3110
+ }
3111
+ generateRoutes(name) {
3112
+ const kebabName = this.toKebabCase(name);
3113
+ return `import type { IModuleRouter } from '@/Shared/Presentation/IModuleRouter'
3114
+ import { ${name}Controller } from '../Controllers/${name}Controller'
3115
+ import type { I${name}EventStore } from '../../Domain/Repositories/I${name}EventStore'
3116
+
3117
+ /**
3118
+ * register${name}Routes - \u8DEF\u7531\u8A3B\u518A
3119
+ *
3120
+ * \u652F\u63F4\uFF1A
3121
+ * POST /api/${kebabName} - \u5EFA\u7ACB
3122
+ * GET /api/${kebabName}/:id - \u53D6\u5F97\u8A73\u60C5
3123
+ * PUT /api/${kebabName}/:id - \u66F4\u65B0
3124
+ * DELETE /api/${kebabName}/:id - \u522A\u9664
3125
+ */
3126
+ export function register${name}Routes(
3127
+ router: IModuleRouter,
3128
+ eventStore: I${name}EventStore
3129
+ ): void {
3130
+ const controller = new ${name}Controller(eventStore)
3131
+
3132
+ router.post('/api/${kebabName}', (ctx) => controller.create(ctx))
3133
+
3134
+ // TODO: \u65B0\u589E\u5176\u4ED6\u8DEF\u7531
3135
+ // router.get('/api/${kebabName}/:id', (ctx) => controller.getById(ctx))
3136
+ // router.put('/api/${kebabName}/:id', (ctx) => controller.update(ctx))
3137
+ // router.delete('/api/${kebabName}/:id', (ctx) => controller.delete(ctx))
3138
+ }
3139
+ `;
3140
+ }
3141
+ generateModuleIndex(name) {
3142
+ return `/**
3143
+ * ${name} \u6A21\u7D44\u5C0E\u51FA
3144
+ *
3145
+ * \u516C\u958B API
3146
+ */
3147
+
3148
+ // Domain
3149
+ export { ${name} } from './Domain/AggregateRoots/${name}'
3150
+ export { ${name}Id } from './Domain/ValueObjects/${name}Id'
3151
+ export { ${name}Status } from './Domain/ValueObjects/${name}Status'
3152
+ export {
3153
+ ${name}CreatedEvent,
3154
+ ${name}UpdatedEvent,
3155
+ ${name}DeletedEvent,
3156
+ } from './Domain/Events'
3157
+ export { ${name}EventApplier } from './Domain/Services/${name}EventApplier'
3158
+ export type { I${name}EventStore } from './Domain/Repositories/I${name}EventStore'
3159
+
3160
+ // Application
3161
+ export { Create${name}Service } from './Application/Services/Create${name}Service'
3162
+ export { ${name}DTO } from './Application/DTOs/${name}DTO'
3163
+
3164
+ // Infrastructure
3165
+ export { InMemory${name}EventStore } from './Infrastructure/EventStore/InMemory${name}EventStore'
3166
+ export { Database${name}EventStore } from './Infrastructure/EventStore/Database${name}EventStore'
3167
+ export { ${name}EventDeserializer } from './Infrastructure/EventStore/${name}EventDeserializer'
3168
+
3169
+ // Presentation
3170
+ export { ${name}Controller } from './Presentation/Controllers/${name}Controller'
3171
+ export { register${name}Routes } from './Presentation/Routes/${this.toKebabCase(name)}.routes'
3172
+ `;
3173
+ }
3174
+ // ─────────────────────────────────────────────────────────────────
3175
+ // 工具方法
3176
+ // ─────────────────────────────────────────────────────────────────
3177
+ toKebabCase(str) {
3178
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
3179
+ }
3180
+ toSnakeCase(str) {
3181
+ return str.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase();
3182
+ }
3183
+ };
3184
+
3185
+ // src/generators/ddd/BootstrapGenerator.ts
3186
+ var BootstrapGenerator = class {
3187
+ context = null;
3188
+ generate(context) {
3189
+ this.context = context;
3190
+ return {
3191
+ type: "directory",
3192
+ name: "Bootstrap",
3193
+ children: [
3194
+ { type: "file", name: "app.ts", content: this.generateBootstrapApp(context) },
3195
+ { type: "file", name: "providers.ts", content: this.generateProvidersRegistry(context) },
3196
+ { type: "file", name: "events.ts", content: this.generateEventsRegistry() },
3197
+ { type: "file", name: "routes.ts", content: this.generateRoutesRegistry(context) },
3198
+ { type: "file", name: "auto-di.ts", content: this.generateAutoDiBootstrap() }
3199
+ ]
3200
+ };
3201
+ }
3202
+ generateConfigDirectory(context) {
3203
+ this.context = context;
3204
+ return {
3205
+ type: "directory",
3206
+ name: "config",
3207
+ children: [
3208
+ { type: "file", name: "app.ts", content: this.generateAppConfig(context) },
3209
+ { type: "file", name: "database.ts", content: this.generateDatabaseConfig() },
3210
+ { type: "file", name: "modules.ts", content: this.generateModulesConfig() },
3211
+ { type: "file", name: "cache.ts", content: this.generateCacheConfig() },
3212
+ { type: "file", name: "logging.ts", content: this.generateLoggingConfig() }
3213
+ ]
3214
+ };
3215
+ }
3216
+ generateMainEntry(_context) {
3217
+ return `/**
3218
+ * Application Entry Point
3219
+ *
3220
+ * Start the HTTP server.
3221
+ */
3222
+
3223
+ import { createApp } from './Bootstrap/app'
3224
+
3225
+ const app = await createApp()
3226
+
3227
+ export default app.liftoff()
3228
+ `;
3229
+ }
3230
+ generateBootstrapApp(_context) {
3231
+ return `/**
3232
+ * Application Bootstrap
3233
+ *
3234
+ * Central configuration and initialization using the ServiceProvider pattern.
3235
+ *
3236
+ * Lifecycle:
3237
+ * 1. Configure: Load app config and orbits
3238
+ * 2. Boot: Initialize PlanetCore
3239
+ * 3. Auto-discover and register services via AutoDiBootstrap
3240
+ * 4. Bootstrap: Boot all providers
3241
+ * 5. Register routes: Auto-register module routes
3242
+ */
3243
+
3244
+ import { defineConfig, PlanetCore } from '@gravito/core'
3245
+ import { OrbitAtlas } from '@gravito/atlas'
3246
+ import appConfig from '../../config/app'
3247
+ import { AutoDiBootstrap } from '../../Bootstrap/auto-di'
3248
+ import { registerProviders } from './providers'
3249
+ import { registerRoutes } from './routes'
3250
+
3251
+ export async function createApp(): Promise<PlanetCore> {
3252
+ // 1. Configure
3253
+ const config = defineConfig({
3254
+ config: appConfig,
3255
+ orbits: [
3256
+ new OrbitAtlas() as unknown as import('@gravito/core').GravitoOrbit,
3257
+ ],
3258
+ })
3259
+
3260
+ // 2. Boot Core
3261
+ const core = await PlanetCore.boot(config)
3262
+ core.registerGlobalErrorHandlers()
3263
+
3264
+ // 3. Auto-discover and register services (OPTIONAL)
3265
+ // \u53D6\u6D88\u4E0B\u5217\u8A3B\u89E3\u4EE5\u555F\u7528\u81EA\u52D5 DI \u6383\u63CF
3266
+ // \u512A\u9EDE\uFF1A\u7121\u9700\u624B\u52D5\u4FEE\u6539 registerProviders()
3267
+ // \u7F3A\u9EDE\uFF1A\u6383\u63CF\u9700\u8981\u6642\u9593\uFF08~100ms\uFF09
3268
+ // await AutoDiBootstrap.scanAndRegisterServices(core.container)
3269
+
3270
+ // 3b. \u6216\u4F7F\u7528\u50B3\u7D71\u7684\u986F\u5F0F\u63D0\u4F9B\u8005\u8A3B\u518A\uFF08\u63A8\u85A6\u7528\u65BC\u751F\u7522\uFF09
3271
+ await registerProviders(core)
3272
+
3273
+ // 4. Bootstrap All Providers
3274
+ await core.bootstrap()
3275
+
3276
+ // 5. Auto-register module routes (OPTIONAL)
3277
+ // await AutoDiBootstrap.scanAndRegisterRoutes(core)
3278
+
3279
+ // 5b. \u6216\u4F7F\u7528\u50B3\u7D71\u7684\u8DEF\u7531\u8A3B\u518A
3280
+ registerRoutes(core.router)
3281
+
3282
+ return core
3283
+ }
3284
+ `;
3285
+ }
3286
+ generateProvidersRegistry(_context) {
3287
+ return `/**
3288
+ * Service Providers Registry
3289
+ *
3290
+ * Register all service providers here.
3291
+ * Include both global and module-specific providers.
3292
+ */
3293
+
3294
+ import {
3295
+ ServiceProvider,
3296
+ type Container,
3297
+ type PlanetCore,
3298
+ bodySizeLimit,
3299
+ securityHeaders,
3300
+ } from '@gravito/core'
3301
+ import { OrderingServiceProvider } from '../Modules/Ordering/Infrastructure/Providers/OrderingServiceProvider'
3302
+ import { CatalogServiceProvider } from '../Modules/Catalog/Infrastructure/Providers/CatalogServiceProvider'
3303
+
3304
+ /**
3305
+ * Middleware Provider - Global middleware registration
3306
+ */
3307
+ export class MiddlewareProvider extends ServiceProvider {
3308
+ register(_container: Container): void {}
3309
+
3310
+ boot(core: PlanetCore): void {
3311
+ const isDev = process.env.NODE_ENV !== 'production'
3312
+
3313
+ core.adapter.use('*', securityHeaders({
3314
+ contentSecurityPolicy: isDev ? false : undefined,
3315
+ }))
3316
+
3317
+ core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
3318
+
3319
+ core.logger.info('\u{1F6E1}\uFE0F Global middleware registered')
3320
+ }
3321
+ }
3322
+
3323
+ export async function registerProviders(core: PlanetCore): Promise<void> {
3324
+ // Global Providers
3325
+ core.register(new MiddlewareProvider())
3326
+
3327
+ // Module Providers
3328
+ core.register(new OrderingServiceProvider())
3329
+ core.register(new CatalogServiceProvider())
3330
+
3331
+ // Add more providers as needed
3332
+ }
3333
+ `;
3334
+ }
3335
+ generateEventsRegistry() {
3336
+ return `/**
3337
+ * Domain Events Registry
3338
+ *
3339
+ * Register all domain event handlers here.
3340
+ */
3341
+
3342
+ import { EventDispatcher } from '../Shared/Infrastructure/EventBus/EventDispatcher'
3343
+
3344
+ export function registerEvents(dispatcher: EventDispatcher): void {
3345
+ // Register event handlers
3346
+ // dispatcher.subscribe('ordering.created', async (event) => { ... })
3347
+ }
3348
+ `;
3349
+ }
3350
+ generateRoutesRegistry(_context) {
3351
+ return `/**
3352
+ * Routes Registry
3353
+ *
3354
+ * Register all module routes here.
3355
+ */
3356
+
3357
+ export function registerRoutes(router: any): void {
3358
+ // Health check
3359
+ router.get('/health', (c: any) => c.json({ status: 'healthy' }))
3360
+
3361
+ // Ordering module
3362
+ router.get('/api/orders', (c: any) => c.json({ message: 'Order list' }))
3363
+ router.post('/api/orders', (c: any) => c.json({ message: 'Order created' }, 201))
3364
+
3365
+ // Catalog module
3366
+ router.get('/api/products', (c: any) => c.json({ message: 'Product list' }))
3367
+ }
3368
+ `;
3369
+ }
3370
+ generateModulesConfig() {
3371
+ return `/**
3372
+ * Modules Configuration
3373
+ *
3374
+ * Define module boundaries and their dependencies.
3375
+ */
3376
+
3377
+ export default {
3378
+ modules: {
3379
+ ordering: {
3380
+ name: 'Ordering',
3381
+ description: 'Order management module',
3382
+ prefix: '/api/orders',
3383
+ },
3384
+ catalog: {
3385
+ name: 'Catalog',
3386
+ description: 'Product catalog module',
3387
+ prefix: '/api/products',
3388
+ },
3389
+ },
3390
+
3391
+ // Module dependencies
3392
+ dependencies: {
3393
+ ordering: ['catalog'], // Ordering depends on Catalog
3394
+ },
3395
+ }
3396
+ `;
3397
+ }
3398
+ generateAppConfig(context) {
3399
+ return `export default {
3400
+ name: process.env.APP_NAME ?? '${context.name}',
3401
+ env: process.env.APP_ENV ?? 'development',
3402
+ port: Number.parseInt(process.env.PORT ?? '3000', 10),
3403
+ VIEW_DIR: process.env.VIEW_DIR ?? 'src/views',
3404
+ debug: process.env.APP_DEBUG === 'true',
3405
+ url: process.env.APP_URL ?? 'http://localhost:3000',
3406
+ }
3407
+ `;
3408
+ }
3409
+ generateDatabaseConfig() {
3410
+ const driver = this.context?.profileConfig?.drivers?.database ?? "none";
3411
+ return ConfigGenerator.generateDatabaseConfig(driver);
3412
+ }
3413
+ generateCacheConfig() {
3414
+ return `export default {
3415
+ default: process.env.CACHE_DRIVER ?? 'memory',
3416
+ stores: { memory: { driver: 'memory' } },
3417
+ }
3418
+ `;
3419
+ }
3420
+ generateLoggingConfig() {
3421
+ return `export default {
3422
+ default: 'console',
3423
+ channels: { console: { driver: 'console', level: 'debug' } },
3424
+ }
3425
+ `;
3426
+ }
3427
+ generateAutoDiBootstrap() {
3428
+ return `/**
3429
+ * AutoDiBootstrap - \u81EA\u52D5\u4F9D\u8CF4\u6CE8\u5165\u5F15\u5C0E\u5C64
3430
+ *
3431
+ * \u529F\u80FD\uFF1A
3432
+ * 1. \u81EA\u52D5\u6383\u63CF\u6A21\u7D44\u76EE\u9304\u767C\u73FE\u670D\u52D9
3433
+ * 2. \u81EA\u52D5\u8A3B\u518A\u5230 DI \u5BB9\u5668
3434
+ * 3. \u81EA\u52D5\u8A3B\u518A\u8DEF\u7531
3435
+ *
3436
+ * \u4F7F\u7528\u65B9\u5F0F\uFF1A
3437
+ * \u5728 app.ts \u4E2D\u53D6\u6D88\u4E0B\u5217\u8A3B\u89E3\uFF1A
3438
+ * await AutoDiBootstrap.scanAndRegisterServices(core.container)
3439
+ * await AutoDiBootstrap.scanAndRegisterRoutes(core)
3440
+ *
3441
+ * \u6CE8\u610F\uFF1A\u81EA\u52D5\u6383\u63CF\u6703\u589E\u52A0\u555F\u52D5\u6642\u9593\uFF08~100ms\uFF09
3442
+ * \u751F\u7522\u74B0\u5883\u5EFA\u8B70\u4F7F\u7528\u624B\u52D5\u8A3B\u518A\uFF08registerProviders\uFF09
3443
+ */
3444
+
3445
+ import type { Container, PlanetCore } from '@gravito/core'
3446
+ import { glob } from 'bun'
3447
+
3448
+ interface DiscoveredService {
3449
+ type: 'domain-service' | 'application-service' | 'repository' | 'event-subscriber'
3450
+ filePath: string
3451
+ moduleName: string
3452
+ className: string
3453
+ }
3454
+
3455
+ interface DiscoveredRoute {
3456
+ moduleName: string
3457
+ routeFilePath: string
3458
+ functionName: string
3459
+ }
3460
+
3461
+ export class AutoDiBootstrap {
3462
+ /**
3463
+ * \u6383\u63CF\u4E26\u81EA\u52D5\u8A3B\u518A\u6240\u6709\u6A21\u7D44\u670D\u52D9
3464
+ */
3465
+ static async scanAndRegisterServices(
3466
+ container: Container,
3467
+ projectRoot = process.cwd(),
3468
+ ): Promise<void> {
3469
+ const services = await this.discoverServices(projectRoot)
3470
+ console.log(\`\u{1F50D} \u767C\u73FE \${services.length} \u500B\u670D\u52D9\`)
3471
+
3472
+ for (const service of services) {
3473
+ await this.registerService(container, service)
3474
+ }
3475
+
3476
+ console.log(\`\u2705 \u5DF2\u8A3B\u518A \${services.length} \u500B\u670D\u52D9\u5230 DI \u5BB9\u5668\`)
3477
+ }
3478
+
3479
+ /**
3480
+ * \u6383\u63CF\u4E26\u81EA\u52D5\u8A3B\u518A\u6240\u6709\u6A21\u7D44\u8DEF\u7531
3481
+ */
3482
+ static async scanAndRegisterRoutes(core: PlanetCore, projectRoot = process.cwd()): Promise<void> {
3483
+ const routes = await this.discoverRoutes(projectRoot)
3484
+ console.log(\`\u{1F6E3}\uFE0F \u767C\u73FE \${routes.length} \u500B\u8DEF\u7531\u6A21\u7D44\`)
3485
+
3486
+ for (const route of routes) {
3487
+ try {
3488
+ const moduleUrl = new URL(\`file://\${route.routeFilePath}\`)
3489
+ const routeModule = await import(moduleUrl.href)
3490
+ const registerFunction = routeModule[route.functionName] || routeModule.default
3491
+
3492
+ if (typeof registerFunction === 'function') {
3493
+ registerFunction(core)
3494
+ console.log(\` \u2713 \${route.moduleName} \u8DEF\u7531\u5DF2\u8A3B\u518A\`)
3495
+ }
3496
+ } catch (error) {
3497
+ console.error(\` \u2717 \u7121\u6CD5\u8F09\u5165 \${route.routeFilePath}:\`, error)
3498
+ }
3499
+ }
3500
+
3501
+ console.log(\`\u2705 \u5DF2\u8A3B\u518A \${routes.length} \u500B\u8DEF\u7531\`)
3502
+ }
3503
+
3504
+ private static async discoverServices(projectRoot: string): Promise<DiscoveredService[]> {
3505
+ const services: DiscoveredService[] = []
3506
+
3507
+ // \u6383\u63CF\u6240\u6709 Service \u548C Repository
3508
+ const patterns = [
3509
+ 'src/Modules/*/Domain/Services/*Service.ts',
3510
+ 'src/Modules/*/Application/Services/*Service.ts',
3511
+ 'src/Modules/*/Infrastructure/Repositories/*Repository.ts',
3512
+ 'src/Modules/*/Infrastructure/Subscribers/*Subscriber.ts',
3513
+ ]
3514
+
3515
+ for (const pattern of patterns) {
3516
+ const files = await glob({ cwd: projectRoot, pattern })
3517
+ for (const filePath of files) {
3518
+ const moduleName = this.extractModuleName(filePath)
3519
+ const className = this.extractClassName(filePath)
3520
+ const type = this.inferServiceType(filePath) as DiscoveredService['type']
3521
+
3522
+ services.push({ type, filePath, moduleName, className })
3523
+ }
3524
+ }
3525
+
3526
+ return services
3527
+ }
3528
+
3529
+ private static async discoverRoutes(projectRoot: string): Promise<DiscoveredRoute[]> {
3530
+ const routeFiles = await glob({
3531
+ cwd: projectRoot,
3532
+ pattern: 'src/Modules/*/Presentation/Routes/*.routes.ts',
3533
+ })
3534
+
3535
+ return routeFiles.map((filePath) => {
3536
+ const moduleName = this.extractModuleName(filePath)
3537
+ return {
3538
+ moduleName,
3539
+ routeFilePath: \`\${projectRoot}/\${filePath}\`,
3540
+ functionName: \`register\${moduleName}Routes\`,
3541
+ }
3542
+ })
3543
+ }
3544
+
3545
+ private static async registerService(container: Container, service: DiscoveredService): Promise<void> {
3546
+ try {
3547
+ const moduleUrl = new URL(\`file://\${process.cwd()}/\${service.filePath}\`)
3548
+ const module = await import(moduleUrl.href)
3549
+ const ServiceClass = module[service.className] || module.default
3550
+
3551
+ if (!ServiceClass) {
3552
+ console.warn(\` \u26A0\uFE0F \u7121\u6CD5\u627E\u5230 \${service.className} \u5728 \${service.filePath}\`)
3553
+ return
3554
+ }
3555
+
3556
+ const serviceKey = this.generateServiceKey(service.className)
3557
+ container.singleton(serviceKey, () => new ServiceClass())
3558
+
3559
+ console.log(\` \u2713 \${serviceKey}\`)
3560
+ } catch (error) {
3561
+ console.error(\` \u2717 \u7121\u6CD5\u8F09\u5165 \${service.filePath}:\`, error)
3562
+ }
3563
+ }
3564
+
3565
+ private static extractModuleName(filePath: string): string {
3566
+ const match = filePath.match(/Modules\\/([^\\/]+)/)
3567
+ return match ? match[1] : 'Unknown'
3568
+ }
3569
+
3570
+ private static extractClassName(filePath: string): string {
3571
+ const fileName = filePath.split('/').pop() || ''
3572
+ return fileName.replace('.ts', '')
3573
+ }
3574
+
3575
+ private static inferServiceType(filePath: string): string {
3576
+ if (filePath.includes('/Domain/Services/')) return 'domain-service'
3577
+ if (filePath.includes('/Application/Services/')) return 'application-service'
3578
+ if (filePath.includes('/Infrastructure/Repositories/')) return 'repository'
3579
+ if (filePath.includes('/Infrastructure/Subscribers/')) return 'event-subscriber'
3580
+ return 'unknown'
3581
+ }
3582
+
3583
+ private static generateServiceKey(className: string): string {
3584
+ let name = className
3585
+ if (name.startsWith('I') && name.length > 1) name = name.slice(1)
3586
+ if (name.endsWith('Service')) name = name.slice(0, -7)
3587
+ if (name.endsWith('Repository')) name = name.slice(0, -10)
3588
+ return this.toKebabCase(name)
3589
+ }
3590
+
3591
+ private static toKebabCase(str: string): string {
3592
+ return str
3593
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
3594
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
3595
+ .toLowerCase()
3596
+ }
3597
+ }
3598
+ `;
3599
+ }
3600
+ };
3601
+
3602
+ // src/generators/ddd/CQRSQueryModuleGenerator.ts
3603
+ var CQRSQueryModuleGenerator = class {
3604
+ /**
3605
+ * 生成 CQRS 查詢側模組結構
3606
+ *
3607
+ * 生成檔案:
3608
+ * - Domain/ReadModels/{Name}ReadModel.ts
3609
+ * - Domain/Projectors/{Name}EventProjector.ts
3610
+ * - Application/Services/Query{Name}Service.ts
3611
+ * - Application/DTOs/{Name}ReadDTO.ts
3612
+ * - Infrastructure/Subscribers/{Name}ProjectionSubscriber.ts
3613
+ * - Infrastructure/Cache/{Name}ReadModelCache.ts
3614
+ * - Presentation/Controllers/{Name}QueryController.ts
3615
+ * - index.ts
3616
+ *
3617
+ * @param moduleName - 模組名稱(例:'Wallet')
3618
+ * @param _context - 生成器上下文(當前未使用,預留供未來擴展)
3619
+ * @returns 模組目錄結構
3620
+ */
3621
+ generate(moduleName, _context) {
3622
+ return {
3623
+ type: "directory",
3624
+ name: moduleName,
3625
+ children: [
3626
+ // Domain Layer
3627
+ {
3628
+ type: "directory",
3629
+ name: "Domain",
3630
+ children: [
3631
+ // Read Models
3632
+ {
3633
+ type: "directory",
3634
+ name: "ReadModels",
3635
+ children: [
3636
+ {
3637
+ type: "file",
3638
+ name: `${moduleName}ReadModel.ts`,
3639
+ content: this.generateReadModel(moduleName)
3640
+ }
3641
+ ]
3642
+ },
3643
+ // Projectors
3644
+ {
3645
+ type: "directory",
3646
+ name: "Projectors",
3647
+ children: [
3648
+ {
3649
+ type: "file",
3650
+ name: `${moduleName}EventProjector.ts`,
3651
+ content: this.generateProjector(moduleName)
3652
+ }
3653
+ ]
3654
+ },
3655
+ // Repositories
3656
+ {
3657
+ type: "directory",
3658
+ name: "Repositories",
3659
+ children: [
3660
+ {
3661
+ type: "file",
3662
+ name: `I${moduleName}ReadModelRepository.ts`,
3663
+ content: this.generateRepositoryInterface(moduleName)
3664
+ }
3665
+ ]
3666
+ }
3667
+ ]
3668
+ },
3669
+ // Application Layer
3670
+ {
3671
+ type: "directory",
3672
+ name: "Application",
3673
+ children: [
3674
+ {
3675
+ type: "directory",
3676
+ name: "Services",
3677
+ children: [
3678
+ {
3679
+ type: "file",
3680
+ name: `Query${moduleName}Service.ts`,
3681
+ content: this.generateQueryService(moduleName)
3682
+ }
3683
+ ]
3684
+ },
3685
+ {
3686
+ type: "directory",
3687
+ name: "DTOs",
3688
+ children: [
3689
+ {
3690
+ type: "file",
3691
+ name: `${moduleName}ReadDTO.ts`,
3692
+ content: this.generateQueryDTO(moduleName)
3693
+ }
3694
+ ]
3695
+ }
3696
+ ]
3697
+ },
3698
+ // Infrastructure Layer
3699
+ {
3700
+ type: "directory",
3701
+ name: "Infrastructure",
3702
+ children: [
3703
+ {
3704
+ type: "directory",
3705
+ name: "Repositories",
3706
+ children: [
3707
+ {
3708
+ type: "file",
3709
+ name: `${moduleName}ReadModelRepository.ts`,
3710
+ content: this.generateRepositoryImplementation(moduleName)
3711
+ }
3712
+ ]
3713
+ },
3714
+ {
3715
+ type: "directory",
3716
+ name: "Subscribers",
3717
+ children: [
3718
+ {
3719
+ type: "file",
3720
+ name: `${moduleName}ProjectionSubscriber.ts`,
3721
+ content: this.generateSubscriber(moduleName)
3722
+ }
3723
+ ]
3724
+ },
3725
+ {
3726
+ type: "directory",
3727
+ name: "Cache",
3728
+ children: [
3729
+ {
3730
+ type: "file",
3731
+ name: `${moduleName}ReadModelCache.ts`,
3732
+ content: this.generateCache(moduleName)
3733
+ }
3734
+ ]
3735
+ }
3736
+ ]
3737
+ },
3738
+ // Presentation Layer
3739
+ {
3740
+ type: "directory",
3741
+ name: "Presentation",
3742
+ children: [
3743
+ {
3744
+ type: "directory",
3745
+ name: "Controllers",
3746
+ children: [
3747
+ {
3748
+ type: "file",
3749
+ name: `${moduleName}QueryController.ts`,
3750
+ content: this.generateController(moduleName)
3751
+ }
3752
+ ]
3753
+ },
3754
+ {
3755
+ type: "directory",
3756
+ name: "Routes",
3757
+ children: [
3758
+ {
3759
+ type: "file",
3760
+ name: `${this.toKebabCase(moduleName)}.routes.ts`,
3761
+ content: this.generateRoutes(moduleName)
3762
+ }
3763
+ ]
3764
+ }
3765
+ ]
3766
+ },
3767
+ // Module index
3768
+ {
3769
+ type: "file",
3770
+ name: "index.ts",
3771
+ content: this.generateIndex(moduleName)
3772
+ }
3773
+ ]
3774
+ };
3775
+ }
3776
+ /**
3777
+ * 生成讀模型檔案
3778
+ */
3779
+ generateReadModel(moduleName) {
3780
+ return `/**
3781
+ * ${moduleName}ReadModel - \u67E5\u8A62\u512A\u5316\u7684\u8B80\u6A21\u578B
3782
+ *
3783
+ * \u8B80\u6A21\u578B\u662F\u5F9E\u4E8B\u4EF6\u6295\u5F71\u800C\u4F86\u7684\u975E\u898F\u7BC4\u5316\u6578\u64DA\u7D50\u69CB\uFF0C
3784
+ * \u91DD\u5C0D\u7279\u5B9A\u67E5\u8A62\u5834\u666F\u9032\u884C\u512A\u5316\u3002
3785
+ *
3786
+ * \u8207 Aggregate Root \u4E0D\u540C\uFF1A
3787
+ * - Aggregate Root\uFF1A\u898F\u7BC4\u5316\uFF0C\u55AE\u4E00\u4F86\u6E90\uFF08\u4E8B\u4EF6\uFF09\uFF0C\u5F37\u4E00\u81F4\u6027
3788
+ * - ReadModel\uFF1A\u53CD\u898F\u7BC4\u5316\uFF0C\u4F9B\u67E5\u8A62\u4F7F\u7528\uFF0C\u6700\u7D42\u4E00\u81F4\u6027
3789
+ *
3790
+ * \u8A2D\u8A08\u539F\u5247\uFF1A
3791
+ * 1. \u4E0D\u53EF\u8B8A\u6027\uFF1A\u8B80\u6A21\u578B\u5B57\u6BB5\u901A\u904E\u6295\u5F71\u5668\u552F\u8B80\u4FEE\u6539
3792
+ * 2. \u6E05\u6670\u6027\uFF1A\u5B57\u6BB5\u540D\u7A31\u76F4\u89C0\u53CD\u6620\u67E5\u8A62\u9700\u6C42
3793
+ * 3. \u6027\u80FD\uFF1A\u5305\u542B\u9810\u8A08\u7B97\u5B57\u6BB5\uFF08\u5982\u7E3D\u984D\u3001\u8A08\u6578\uFF09
3794
+ * 4. \u7248\u672C\u63A7\u5236\uFF1A\u652F\u6301\u6295\u5F71\u5668\u7248\u672C\u9077\u79FB
3795
+ */
3796
+
3797
+ import type { DomainEvent } from '@/Shared/Domain/DomainEvent'
3798
+
3799
+ /**
3800
+ * ${moduleName} \u8B80\u6A21\u578B\u4ECB\u9762
3801
+ * \u4EE3\u8868\u512A\u5316\u7528\u65BC\u67E5\u8A62\u7684\u6578\u64DA\u7D50\u69CB
3802
+ */
3803
+ export interface ${moduleName}ReadModel {
3804
+ // TODO: \u6839\u64DA\u67E5\u8A62\u9700\u6C42\u6DFB\u52A0\u5B57\u6BB5
3805
+ // \u793A\u4F8B\uFF1A
3806
+ // id: string
3807
+ // customerId: string
3808
+ // totalAmount: string (decimal as string for precision)
3809
+ // transactionCount: number
3810
+ // lastUpdated: Date
3811
+ // status: string
3812
+
3813
+ // \u6295\u5F71\u5143\u6578\u64DA
3814
+ /** \u6700\u5F8C\u8655\u7406\u7684\u4E8B\u4EF6 ID\uFF08\u7528\u65BC\u51AA\u7B49\u6027\uFF09 */
3815
+ lastProcessedEventId?: string
3816
+ /** \u6295\u5F71\u5668\u7248\u672C */
3817
+ projectionVersion: number
3818
+ }
3819
+
3820
+ /**
3821
+ * \u5DE5\u5EE0\u65B9\u6CD5\uFF1A\u5275\u5EFA\u65B0\u7684\u8B80\u6A21\u578B
3822
+ */
3823
+ export function create${moduleName}ReadModel(data: Partial<${moduleName}ReadModel>): ${moduleName}ReadModel {
3824
+ return {
3825
+ ...data,
3826
+ projectionVersion: 1,
3827
+ } as ${moduleName}ReadModel
3828
+ }
3829
+
3830
+ /**
3831
+ * \u9A57\u8B49\u8B80\u6A21\u578B\u6578\u64DA\u5B8C\u6574\u6027
3832
+ */
3833
+ export function validate${moduleName}ReadModel(data: any): boolean {
3834
+ // TODO: \u6DFB\u52A0\u9A57\u8B49\u908F\u8F2F
3835
+ // - \u5FC5\u586B\u5B57\u6BB5\u6AA2\u67E5
3836
+ // - \u985E\u578B\u9A57\u8B49
3837
+ // - \u696D\u52D9\u898F\u5247\u9A57\u8B49
3838
+ return typeof data === 'object' && data !== null
3839
+ }
3840
+
3841
+ /**
3842
+ * \u5F9E\u8B80\u6A21\u578B\u63D0\u53D6\u67E5\u8A62\u4FE1\u606F
3843
+ * \u7528\u65BC DTO \u8F49\u63DB\u548C\u5E8F\u5217\u5316
3844
+ */
3845
+ export function extract${moduleName}Data(model: ${moduleName}ReadModel): Record<string, any> {
3846
+ return {
3847
+ // TODO: \u63D0\u53D6\u76F8\u95DC\u5B57\u6BB5\u7528\u65BC DTO \u8F49\u63DB
3848
+ projectionVersion: model.projectionVersion,
3849
+ }
3850
+ }
3851
+ `;
3852
+ }
3853
+ /**
3854
+ * 生成事件投影器檔案
3855
+ */
3856
+ generateProjector(moduleName) {
3857
+ return `/**
3858
+ * ${moduleName}EventProjector - \u4E8B\u4EF6\u6295\u5F71\u5668
3859
+ *
3860
+ * \u6295\u5F71\u5668\u662F\u7D14\u51FD\u6578\uFF0C\u8CA0\u8CAC\u5C07\u9818\u57DF\u4E8B\u4EF6\u8F49\u5316\u70BA\u8B80\u6A21\u578B\u3002
3861
+ * \u8A2D\u8A08\u70BA\u51AA\u7B49\u64CD\u4F5C\uFF1A\u540C\u4E00\u4E8B\u4EF6\u6295\u5F71\u591A\u6B21\u7D50\u679C\u76F8\u540C\u3002
3862
+ *
3863
+ * \u6838\u5FC3\u8077\u8CAC\uFF1A
3864
+ * 1. \u76E3\u807D\u7279\u5B9A\u7684\u9818\u57DF\u4E8B\u4EF6
3865
+ * 2. \u6839\u64DA\u4E8B\u4EF6\u5167\u5BB9\u66F4\u65B0\u8B80\u6A21\u578B
3866
+ * 3. \u78BA\u4FDD\u64CD\u4F5C\u51AA\u7B49\uFF08\u9632\u6B62\u91CD\u8907\u8655\u7406\uFF09
3867
+ * 4. \u8A18\u9304\u6295\u5F71\u5BE9\u8A08\u4FE1\u606F
3868
+ *
3869
+ * \u512A\u52E2\uFF1A
3870
+ * - \u7D14\u51FD\u6578\u908F\u8F2F\u6613\u65BC\u6E2C\u8A66
3871
+ * - \u652F\u6301\u4E8B\u4EF6\u91CD\u653E\u548C\u6295\u5F71\u91CD\u5EFA
3872
+ * - \u89E3\u8026\u4E8B\u4EF6\u4F86\u6E90\u548C\u6295\u5F71\u76EE\u6A19
3873
+ * - \u6613\u65BC\u65B0\u589E\u65B0\u7684\u8B80\u6A21\u578B\u800C\u7121\u9700\u4FEE\u6539\u805A\u5408\u6839
3874
+ */
3875
+
3876
+ import type { DomainEvent } from '@/Shared/Domain/DomainEvent'
3877
+ import type { I${moduleName}ReadModelRepository } from '../Repositories/I${moduleName}ReadModelRepository'
3878
+ import type { ${moduleName}ReadModel } from '../ReadModels/${moduleName}ReadModel'
3879
+
3880
+ /**
3881
+ * ${moduleName} \u4E8B\u4EF6\u6295\u5F71\u5668
3882
+ *
3883
+ * \u8CA0\u8CAC\u8A02\u95B1\u9818\u57DF\u4E8B\u4EF6\u4E26\u6295\u5F71\u5230\u8B80\u6A21\u578B
3884
+ */
3885
+ export class ${moduleName}EventProjector {
3886
+ constructor(private repository: I${moduleName}ReadModelRepository) {}
3887
+
3888
+ /**
3889
+ * \u6295\u5F71\u4E8B\u4EF6\u5230\u8B80\u6A21\u578B
3890
+ *
3891
+ * \u5DE5\u4F5C\u6D41\u7A0B\uFF1A
3892
+ * 1. \u6839\u64DA\u4E8B\u4EF6\u985E\u578B\u5206\u6D3E\u5230\u5C0D\u61C9\u7684 handler
3893
+ * 2. \u67E5\u8A62\u6216\u5275\u5EFA\u8B80\u6A21\u578B
3894
+ * 3. \u61C9\u7528\u6295\u5F71\u898F\u5247\u66F4\u65B0\u8B80\u6A21\u578B
3895
+ * 4. \u6AA2\u67E5\u51AA\u7B49\u6027\uFF08\u662F\u5426\u5DF2\u8655\u7406\u904E\uFF09
3896
+ * 5. \u4FDD\u5B58\u66F4\u65B0\u7D50\u679C
3897
+ *
3898
+ * @param event - \u8981\u6295\u5F71\u7684\u9818\u57DF\u4E8B\u4EF6
3899
+ * @throws \u5982\u679C\u4E8B\u4EF6\u8655\u7406\u5931\u6557
3900
+ */
3901
+ async projectEvent(event: DomainEvent): Promise<void> {
3902
+ try {
3903
+ // TODO: \u6839\u64DA\u4E8B\u4EF6\u985E\u578B\u5206\u6D3E
3904
+ // switch (event.constructor.name) {
3905
+ // case 'SomeEvent':
3906
+ // await this.handleSomeEvent(event as SomeEvent)
3907
+ // break
3908
+ // default:
3909
+ // console.warn(\`Unknown event type: \${event.constructor.name}\`)
3910
+ // }
3911
+ } catch (error) {
3912
+ console.error(
3913
+ \`Failed to project \${event.constructor.name} in ${moduleName}Projector:\`,
3914
+ error
3915
+ )
3916
+ throw error
3917
+ }
3918
+ }
3919
+
3920
+ /**
3921
+ * TODO: \u70BA\u6BCF\u500B\u8981\u8A02\u95B1\u7684\u4E8B\u4EF6\u6DFB\u52A0 handler \u65B9\u6CD5
3922
+ *
3923
+ * \u793A\u4F8B\uFF1A
3924
+ * private async handleSomeEvent(event: SomeEvent): Promise<void> {
3925
+ * // 1. \u67E5\u8A62\u73FE\u6709\u8B80\u6A21\u578B\u6216\u5275\u5EFA\u65B0\u7684
3926
+ * let model = await this.repository.findById(event.aggregateId)
3927
+ * if (!model) {
3928
+ * model = createReadModel({ id: event.aggregateId })
3929
+ * }
3930
+ *
3931
+ * // 2. \u6AA2\u67E5\u51AA\u7B49\u6027
3932
+ * if (model.lastProcessedEventId === event.eventId) {
3933
+ * return // \u5DF2\u8655\u7406\u904E\u6B64\u4E8B\u4EF6
3934
+ * }
3935
+ *
3936
+ * // 3. \u61C9\u7528\u6295\u5F71\u898F\u5247\u66F4\u65B0\u5B57\u6BB5
3937
+ * model = {
3938
+ * ...model,
3939
+ * field1: event.data.newValue,
3940
+ * lastProcessedEventId: event.eventId,
3941
+ * projectionVersion: model.projectionVersion + 1,
3942
+ * }
3943
+ *
3944
+ * // 4. \u4FDD\u5B58\u66F4\u65B0
3945
+ * await this.repository.save(model)
3946
+ * }
3947
+ */
3948
+
3949
+ /**
3950
+ * \u7372\u53D6\u6B64\u6295\u5F71\u5668\u8A02\u95B1\u7684\u6240\u6709\u4E8B\u4EF6\u985E\u578B
3951
+ *
3952
+ * \u7528\u65BC\u81EA\u52D5\u4E8B\u4EF6\u8A02\u95B1\u8A3B\u518A
3953
+ */
3954
+ getSubscribedEventTypes(): string[] {
3955
+ // TODO: \u8FD4\u56DE\u6B64\u6295\u5F71\u5668\u8A02\u95B1\u7684\u6240\u6709\u4E8B\u4EF6\u985E\u578B\u540D\u7A31
3956
+ return [
3957
+ // 'SomeEvent',
3958
+ // 'AnotherEvent',
3959
+ ]
3960
+ }
3961
+
3962
+ /**
3963
+ * \u5224\u65B7\u6295\u5F71\u5668\u662F\u5426\u652F\u63F4\u67D0\u500B\u4E8B\u4EF6
3964
+ */
3965
+ supportsEvent(eventType: string): boolean {
3966
+ return this.getSubscribedEventTypes().includes(eventType)
3967
+ }
3968
+ }
3969
+ `;
3970
+ }
3971
+ /**
3972
+ * 生成查詢服務檔案
3973
+ */
3974
+ generateQueryService(moduleName) {
3975
+ return `/**
3976
+ * Query${moduleName}Service - \u67E5\u8A62\u670D\u52D9
3977
+ *
3978
+ * \u61C9\u7528\u5C64\u670D\u52D9\uFF0C\u63D0\u4F9B\u67E5\u8A62\u63A5\u53E3\u3002
3979
+ * \u901A\u904E\u5009\u5EAB\u8A2A\u554F\u8B80\u6A21\u578B\uFF0C\u652F\u6301\u591A\u7A2E\u67E5\u8A62\u65B9\u6CD5\u3002
3980
+ *
3981
+ * \u8A2D\u8A08\u7279\u9EDE\uFF1A
3982
+ * 1. \u7121\u526F\u4F5C\u7528\uFF1A\u53EA\u8B80\u64CD\u4F5C\uFF0C\u4E0D\u4FEE\u6539\u72C0\u614B
3983
+ * 2. \u9AD8\u6548\uFF1A\u76F4\u63A5\u67E5\u8A62\u512A\u5316\u7684\u8B80\u6A21\u578B
3984
+ * 3. \u4E00\u81F4\u6027\uFF1A\u652F\u6301\u7DE9\u5B58\u63A7\u5236
3985
+ * 4. \u932F\u8AA4\u8655\u7406\uFF1A\u9069\u7576\u7684\u7570\u5E38\u8655\u7406\u548C\u65E5\u8A8C
3986
+ */
3987
+
3988
+ import type { I${moduleName}ReadModelRepository } from '../../Infrastructure/Repositories/I${moduleName}ReadModelRepository'
3989
+ import { ${moduleName}ReadDTO } from '../DTOs/${moduleName}ReadDTO'
3990
+
3991
+ /**
3992
+ * \u67E5\u8A62\u904E\u6FFE\u689D\u4EF6
3993
+ */
3994
+ export interface QueryFilters {
3995
+ [key: string]: any
3996
+ // TODO: \u6839\u64DA\u67E5\u8A62\u9700\u6C42\u5B9A\u7FA9\u904E\u6FFE\u5B57\u6BB5
3997
+ }
3998
+
3999
+ /**
4000
+ * ${moduleName} \u67E5\u8A62\u670D\u52D9
4001
+ * \u63D0\u4F9B\u8B80\u6A21\u578B\u7684\u5404\u7A2E\u67E5\u8A62\u65B9\u6CD5
4002
+ */
4003
+ export class Query${moduleName}Service {
4004
+ constructor(private repository: I${moduleName}ReadModelRepository) {}
4005
+
4006
+ /**
4007
+ * \u6839\u64DA ID \u67E5\u8A62\u55AE\u500B\u8B80\u6A21\u578B
4008
+ * @param id - \u8B80\u6A21\u578B ID
4009
+ * @returns \u8B80\u6A21\u578B DTO\uFF0C\u5982\u679C\u4E0D\u5B58\u5728\u8FD4\u56DE null
4010
+ */
4011
+ async findById(id: string): Promise<${moduleName}ReadDTO | null> {
4012
+ try {
4013
+ const model = await this.repository.findById(id)
4014
+ return model ? ${moduleName}ReadDTO.fromReadModel(model) : null
4015
+ } catch (error) {
4016
+ console.error(\`Error finding ${moduleName} by ID \${id}:\`, error)
4017
+ throw error
4018
+ }
4019
+ }
4020
+
4021
+ /**
4022
+ * \u67E5\u8A62\u6240\u6709\u8B80\u6A21\u578B
4023
+ * @param filters - \u67E5\u8A62\u904E\u6FFE\u689D\u4EF6\uFF08\u53EF\u9078\uFF09
4024
+ * @returns \u8B80\u6A21\u578B DTO \u5217\u8868
4025
+ */
4026
+ async findAll(filters?: QueryFilters): Promise<${moduleName}ReadDTO[]> {
4027
+ try {
4028
+ const models = await this.repository.findAll(filters)
4029
+ return models.map(model => ${moduleName}ReadDTO.fromReadModel(model))
4030
+ } catch (error) {
4031
+ console.error('Error finding all ${moduleName}:', error)
4032
+ throw error
4033
+ }
4034
+ }
4035
+
4036
+ /**
4037
+ * \u57FA\u65BC\u689D\u4EF6\u641C\u7D22
4038
+ * @param criteria - \u641C\u7D22\u689D\u4EF6
4039
+ * @returns \u5339\u914D\u7684\u8B80\u6A21\u578B DTO \u5217\u8868
4040
+ */
4041
+ async search(criteria: QueryFilters): Promise<${moduleName}ReadDTO[]> {
4042
+ try {
4043
+ // TODO: \u5BE6\u73FE\u81EA\u5B9A\u7FA9\u641C\u7D22\u908F\u8F2F
4044
+ return this.findAll(criteria)
4045
+ } catch (error) {
4046
+ console.error('Error searching ${moduleName}:', error)
4047
+ throw error
4048
+ }
4049
+ }
4050
+
4051
+ /**
4052
+ * \u7372\u53D6\u7D71\u8A08\u4FE1\u606F
4053
+ * @returns \u7D71\u8A08\u6578\u64DA
4054
+ */
4055
+ async getStatistics(): Promise<Record<string, any>> {
4056
+ try {
4057
+ // TODO: \u5BE6\u73FE\u7D71\u8A08\u908F\u8F2F
4058
+ // - \u7E3D\u6578\u91CF
4059
+ // - \u805A\u5408\u5B57\u6BB5\uFF08\u7E3D\u984D\u3001\u5E73\u5747\u503C\u7B49\uFF09
4060
+ // - \u5206\u7D44\u7D71\u8A08
4061
+
4062
+ return {
4063
+ totalCount: 0,
4064
+ // \u66F4\u591A\u7D71\u8A08\u5B57\u6BB5...
4065
+ }
4066
+ } catch (error) {
4067
+ console.error('Error getting ${moduleName} statistics:', error)
4068
+ throw error
4069
+ }
4070
+ }
4071
+ }
4072
+ `;
4073
+ }
4074
+ /**
4075
+ * 生成查詢 DTO 檔案
4076
+ */
4077
+ generateQueryDTO(moduleName) {
4078
+ return `/**
4079
+ * ${moduleName}ReadDTO - \u67E5\u8A62\u7D50\u679C DTO
4080
+ *
4081
+ * \u6578\u64DA\u50B3\u8F38\u5C0D\u8C61\uFF0C\u7528\u65BC\u5C55\u793A\u5C64\u8207\u61C9\u7528\u5C64\u4E4B\u9593\u7684\u901A\u4FE1\u3002
4082
+ * \u57FA\u65BC\u8B80\u6A21\u578B\u4F46\u53EF\u4EE5\u5305\u542B\u984D\u5916\u7684\u8A08\u7B97\u5B57\u6BB5\u3002
4083
+ */
4084
+
4085
+ import { BaseDTO } from '@/Shared/Application/BaseDTO'
4086
+ import type { ${moduleName}ReadModel } from '../../Domain/ReadModels/${moduleName}ReadModel'
2176
4087
 
2177
4088
  /**
2178
- * Middleware Provider - Global middleware registration
4089
+ * ${moduleName} \u8B80\u6A21\u578B DTO
2179
4090
  */
2180
- export class MiddlewareProvider extends ServiceProvider {
2181
- register(_container: Container): void {}
4091
+ export class ${moduleName}ReadDTO extends BaseDTO {
4092
+ // TODO: \u6839\u64DA\u67E5\u8A62\u9700\u6C42\u6DFB\u52A0\u5B57\u6BB5
4093
+ // \u793A\u4F8B\uFF1A
4094
+ // id: string
4095
+ // customerId: string
4096
+ // totalAmount: string
4097
+ // transactionCount: number
4098
+ // lastUpdated: Date
2182
4099
 
2183
- boot(core: PlanetCore): void {
2184
- const isDev = process.env.NODE_ENV !== 'production'
4100
+ /**
4101
+ * \u5F9E\u8B80\u6A21\u578B\u8F49\u63DB\u70BA DTO
4102
+ */
4103
+ static fromReadModel(model: ${moduleName}ReadModel): ${moduleName}ReadDTO {
4104
+ const dto = new ${moduleName}ReadDTO()
4105
+ // TODO: \u6620\u5C04\u8B80\u6A21\u578B\u5B57\u6BB5\u5230 DTO
4106
+ // dto.id = model.id
4107
+ // dto.customerId = model.customerId
4108
+ // ...
4109
+ return dto
4110
+ }
2185
4111
 
2186
- core.adapter.use('*', securityHeaders({
2187
- contentSecurityPolicy: isDev ? false : undefined,
2188
- }))
4112
+ /**
4113
+ * \u5E8F\u5217\u5316\u70BA JSON
4114
+ */
4115
+ toJSON(): Record<string, any> {
4116
+ return {
4117
+ // TODO: \u8FD4\u56DE DTO \u7684 JSON \u8868\u793A
4118
+ // id: this.id,
4119
+ // customerId: this.customerId,
4120
+ // ...
4121
+ }
4122
+ }
4123
+ }
4124
+ `;
4125
+ }
4126
+ /**
4127
+ * 生成倉庫介面
4128
+ */
4129
+ generateRepositoryInterface(moduleName) {
4130
+ return `/**
4131
+ * I${moduleName}ReadModelRepository - \u8B80\u6A21\u578B\u5009\u5EAB\u4ECB\u9762
4132
+ *
4133
+ * \u5B9A\u7FA9\u8B80\u6A21\u578B\u5B58\u5132\u548C\u67E5\u8A62\u7684\u63A5\u53E3\u3002
4134
+ * \u5BE6\u73FE\u985E\u8CA0\u8CAC\u8207\u6578\u64DA\u5EAB\u6216\u5FEB\u53D6\u7684\u4EA4\u4E92\u3002
4135
+ */
2189
4136
 
2190
- core.adapter.use('*', bodySizeLimit(10 * 1024 * 1024))
4137
+ import type { ${moduleName}ReadModel } from '../ReadModels/${moduleName}ReadModel'
2191
4138
 
2192
- core.logger.info('\u{1F6E1}\uFE0F Global middleware registered')
4139
+ /**
4140
+ * ${moduleName} \u8B80\u6A21\u578B\u5009\u5EAB\u4ECB\u9762
4141
+ */
4142
+ export interface I${moduleName}ReadModelRepository {
4143
+ /**
4144
+ * \u6839\u64DA ID \u67E5\u8A62\u8B80\u6A21\u578B
4145
+ */
4146
+ findById(id: string): Promise<${moduleName}ReadModel | null>
4147
+
4148
+ /**
4149
+ * \u67E5\u8A62\u6240\u6709\u8B80\u6A21\u578B
4150
+ */
4151
+ findAll(filters?: Record<string, any>): Promise<${moduleName}ReadModel[]>
4152
+
4153
+ /**
4154
+ * \u4FDD\u5B58\u8B80\u6A21\u578B\uFF08\u65B0\u589E\u6216\u66F4\u65B0\uFF09
4155
+ */
4156
+ save(model: ${moduleName}ReadModel): Promise<void>
4157
+
4158
+ /**
4159
+ * \u522A\u9664\u8B80\u6A21\u578B
4160
+ */
4161
+ delete(id: string): Promise<void>
4162
+
4163
+ /**
4164
+ * \u6839\u64DA\u81EA\u5B9A\u7FA9\u689D\u4EF6\u67E5\u8A62
4165
+ */
4166
+ query(condition: Record<string, any>): Promise<${moduleName}ReadModel[]>
4167
+
4168
+ /**
4169
+ * \u8A08\u6578
4170
+ */
4171
+ count(condition?: Record<string, any>): Promise<number>
4172
+ }
4173
+ `;
4174
+ }
4175
+ /**
4176
+ * 生成倉庫實現
4177
+ */
4178
+ generateRepositoryImplementation(moduleName) {
4179
+ return `/**
4180
+ * ${moduleName}ReadModelRepository - \u8B80\u6A21\u578B\u5009\u5EAB\u5BE6\u73FE
4181
+ *
4182
+ * \u5BE6\u73FE\u8B80\u6A21\u578B\u7684\u6301\u4E45\u5316\u548C\u67E5\u8A62\u3002
4183
+ * \u4F7F\u7528 Atlas ORM \u8207\u6578\u64DA\u5EAB\u4EA4\u4E92\u3002
4184
+ */
4185
+
4186
+ import type { I${moduleName}ReadModelRepository } from '../Repositories/I${moduleName}ReadModelRepository'
4187
+ import type { ${moduleName}ReadModel } from '../../Domain/ReadModels/${moduleName}ReadModel'
4188
+
4189
+ /**
4190
+ * ${moduleName} \u8B80\u6A21\u578B\u5009\u5EAB\u5BE6\u73FE
4191
+ * \u4F7F\u7528 Atlas ORM \u8207\u6578\u64DA\u5EAB\u4EA4\u4E92
4192
+ */
4193
+ export class ${moduleName}ReadModelRepository implements I${moduleName}ReadModelRepository {
4194
+ private readonly tableName = '${this.toSnakeCase(moduleName)}_read_models'
4195
+
4196
+ /**
4197
+ * \u6839\u64DA ID \u67E5\u8A62\u8B80\u6A21\u578B
4198
+ */
4199
+ async findById(id: string): Promise<${moduleName}ReadModel | null> {
4200
+ try {
4201
+ // TODO: \u4F7F\u7528 Atlas ORM \u67E5\u8A62
4202
+ // const db = getDatabase() // \u5F9E DI \u5BB9\u5668\u7372\u53D6
4203
+ // return await db.table(this.tableName).where('id', id).first()
4204
+ return null
4205
+ } catch (error) {
4206
+ console.error(\`Error finding ${moduleName} by ID \${id}:\`, error)
4207
+ throw error
4208
+ }
4209
+ }
4210
+
4211
+ /**
4212
+ * \u67E5\u8A62\u6240\u6709\u8B80\u6A21\u578B
4213
+ */
4214
+ async findAll(filters?: Record<string, any>): Promise<${moduleName}ReadModel[]> {
4215
+ try {
4216
+ // TODO: \u4F7F\u7528 Atlas ORM \u67E5\u8A62
4217
+ // const db = getDatabase()
4218
+ // let query = db.table(this.tableName)
4219
+ //
4220
+ // if (filters) {
4221
+ // for (const [key, value] of Object.entries(filters)) {
4222
+ // query = query.where(key, value)
4223
+ // }
4224
+ // }
4225
+ //
4226
+ // return await query.get()
4227
+ return []
4228
+ } catch (error) {
4229
+ console.error('Error finding all ${moduleName}:', error)
4230
+ throw error
4231
+ }
4232
+ }
4233
+
4234
+ /**
4235
+ * \u4FDD\u5B58\u8B80\u6A21\u578B
4236
+ */
4237
+ async save(model: ${moduleName}ReadModel): Promise<void> {
4238
+ try {
4239
+ // TODO: \u4F7F\u7528 Atlas ORM \u4FDD\u5B58
4240
+ // const db = getDatabase()
4241
+ // await db.table(this.tableName).updateOrInsert(
4242
+ // { id: model.id },
4243
+ // model
4244
+ // )
4245
+ } catch (error) {
4246
+ console.error('Error saving ${moduleName}:', error)
4247
+ throw error
4248
+ }
4249
+ }
4250
+
4251
+ /**
4252
+ * \u522A\u9664\u8B80\u6A21\u578B
4253
+ */
4254
+ async delete(id: string): Promise<void> {
4255
+ try {
4256
+ // TODO: \u4F7F\u7528 Atlas ORM \u522A\u9664
4257
+ // const db = getDatabase()
4258
+ // await db.table(this.tableName).where('id', id).delete()
4259
+ } catch (error) {
4260
+ console.error(\`Error deleting ${moduleName} ID \${id}:\`, error)
4261
+ throw error
4262
+ }
4263
+ }
4264
+
4265
+ /**
4266
+ * \u6839\u64DA\u81EA\u5B9A\u7FA9\u689D\u4EF6\u67E5\u8A62
4267
+ */
4268
+ async query(condition: Record<string, any>): Promise<${moduleName}ReadModel[]> {
4269
+ return this.findAll(condition)
4270
+ }
4271
+
4272
+ /**
4273
+ * \u8A08\u6578
4274
+ */
4275
+ async count(condition?: Record<string, any>): Promise<number> {
4276
+ try {
4277
+ // TODO: \u4F7F\u7528 Atlas ORM \u8A08\u6578
4278
+ // const db = getDatabase()
4279
+ // let query = db.table(this.tableName)
4280
+ //
4281
+ // if (condition) {
4282
+ // for (const [key, value] of Object.entries(condition)) {
4283
+ // query = query.where(key, value)
4284
+ // }
4285
+ // }
4286
+ //
4287
+ // return await query.count()
4288
+ return 0
4289
+ } catch (error) {
4290
+ console.error('Error counting ${moduleName}:', error)
4291
+ throw error
4292
+ }
2193
4293
  }
2194
4294
  }
4295
+ `;
4296
+ }
4297
+ /**
4298
+ * 生成事件訂閱器
4299
+ */
4300
+ generateSubscriber(moduleName) {
4301
+ return `/**
4302
+ * ${moduleName}ProjectionSubscriber - \u6295\u5F71\u4E8B\u4EF6\u8A02\u95B1\u5668
4303
+ *
4304
+ * \u8A02\u95B1\u9818\u57DF\u4E8B\u4EF6\u4E26\u8ABF\u7528\u6295\u5F71\u5668\u66F4\u65B0\u8B80\u6A21\u578B\u3002
4305
+ * \u5145\u7576\u4E8B\u4EF6\u6E90\u8207\u67E5\u8A62\u5074\u7684\u6A4B\u6881\u3002
4306
+ *
4307
+ * \u5DE5\u4F5C\u6D41\u7A0B\uFF1A
4308
+ * 1. \u61C9\u7528\u555F\u52D5\u6642\u8A3B\u518A\u5230\u4E8B\u4EF6\u532F\u6D41\u6392
4309
+ * 2. \u63A5\u6536\u76F8\u95DC\u9818\u57DF\u4E8B\u4EF6
4310
+ * 3. \u8ABF\u7528\u6295\u5F71\u5668\u8655\u7406\u4E8B\u4EF6
4311
+ * 4. \u66F4\u65B0\u8B80\u6A21\u578B
4312
+ */
2195
4313
 
2196
- export async function registerProviders(core: PlanetCore): Promise<void> {
2197
- // Global Providers
2198
- core.register(new MiddlewareProvider())
4314
+ import type { DomainEvent } from '@/Shared/Domain/DomainEvent'
4315
+ import { ${moduleName}EventProjector } from '../../Domain/Projectors/${moduleName}EventProjector'
2199
4316
 
2200
- // Module Providers
2201
- core.register(new OrderingServiceProvider())
2202
- core.register(new CatalogServiceProvider())
4317
+ /**
4318
+ * ${moduleName} \u6295\u5F71\u4E8B\u4EF6\u8A02\u95B1\u5668
4319
+ * \u76E3\u807D\u4E8B\u4EF6\u4E26\u9A45\u52D5\u8B80\u6A21\u578B\u6295\u5F71
4320
+ */
4321
+ export class ${moduleName}ProjectionSubscriber {
4322
+ constructor(private projector: ${moduleName}EventProjector) {}
2203
4323
 
2204
- // Add more providers as needed
4324
+ /**
4325
+ * \u8655\u7406\u4E8B\u4EF6
4326
+ * @param event - \u63A5\u6536\u5230\u7684\u9818\u57DF\u4E8B\u4EF6
4327
+ */
4328
+ async handle(event: DomainEvent): Promise<void> {
4329
+ try {
4330
+ // \u6AA2\u67E5\u6B64\u8A02\u95B1\u5668\u662F\u5426\u61C9\u8655\u7406\u8A72\u4E8B\u4EF6
4331
+ if (this.projector.supportsEvent(event.constructor.name)) {
4332
+ await this.projector.projectEvent(event)
4333
+ }
4334
+ } catch (error) {
4335
+ console.error(
4336
+ \`${moduleName}ProjectionSubscriber failed to handle \${event.constructor.name}:\`,
4337
+ error
4338
+ )
4339
+ throw error
4340
+ }
4341
+ }
4342
+
4343
+ /**
4344
+ * \u7372\u53D6\u8A02\u95B1\u7684\u4E8B\u4EF6\u985E\u578B\u5217\u8868
4345
+ * \u7528\u65BC\u4E8B\u4EF6\u532F\u6D41\u6392\u8A3B\u518A
4346
+ */
4347
+ getSubscribedEventTypes(): string[] {
4348
+ return this.projector.getSubscribedEventTypes()
4349
+ }
2205
4350
  }
2206
4351
  `;
2207
4352
  }
2208
- generateEventsRegistry() {
4353
+ /**
4354
+ * 生成快取層
4355
+ */
4356
+ generateCache(moduleName) {
2209
4357
  return `/**
2210
- * Domain Events Registry
4358
+ * ${moduleName}ReadModelCache - \u8B80\u6A21\u578B\u5FEB\u53D6\u5C64
2211
4359
  *
2212
- * Register all domain event handlers here.
4360
+ * \u53EF\u9078\u7684\u5FEB\u53D6\u5C64\uFF0C\u7528\u65BC\u63D0\u9AD8\u67E5\u8A62\u6027\u80FD\u3002
4361
+ * \u652F\u6301\u96D9\u5C64\u5FEB\u53D6\uFF1A\u9032\u7A0B\u5167\u5FEB\u53D6 + Redis \u5FEB\u53D6
2213
4362
  */
2214
4363
 
2215
- import { EventDispatcher } from '../Shared/Infrastructure/EventBus/EventDispatcher'
4364
+ import type { ${moduleName}ReadModel } from '../../Domain/ReadModels/${moduleName}ReadModel'
2216
4365
 
2217
- export function registerEvents(dispatcher: EventDispatcher): void {
2218
- // Register event handlers
2219
- // dispatcher.subscribe('ordering.created', async (event) => { ... })
4366
+ /**
4367
+ * ${moduleName} \u8B80\u6A21\u578B\u5FEB\u53D6
4368
+ */
4369
+ export class ${moduleName}ReadModelCache {
4370
+ private memoryCache: Map<string, ${moduleName}ReadModel> = new Map()
4371
+ private readonly cacheTTL = 3600 // 1 \u5C0F\u6642
4372
+
4373
+ /**
4374
+ * \u5F9E\u5FEB\u53D6\u7372\u53D6\u8B80\u6A21\u578B
4375
+ */
4376
+ async get(key: string): Promise<${moduleName}ReadModel | null> {
4377
+ // \u5148\u5617\u8A66\u9032\u7A0B\u8A18\u61B6\u9AD4\u5FEB\u53D6
4378
+ const cached = this.memoryCache.get(key)
4379
+ if (cached) {
4380
+ return cached
4381
+ }
4382
+
4383
+ // TODO: \u5617\u8A66 Redis \u5FEB\u53D6
4384
+ // const redis = getRedisClient()
4385
+ // const cached = await redis.get(\`${moduleName}:\${key}\`)
4386
+ // if (cached) {
4387
+ // return JSON.parse(cached)
4388
+ // }
4389
+
4390
+ return null
4391
+ }
4392
+
4393
+ /**
4394
+ * \u8A2D\u7F6E\u5FEB\u53D6
4395
+ */
4396
+ async set(key: string, value: ${moduleName}ReadModel, ttl?: number): Promise<void> {
4397
+ // \u8A2D\u7F6E\u9032\u7A0B\u8A18\u61B6\u9AD4\u5FEB\u53D6
4398
+ this.memoryCache.set(key, value)
4399
+
4400
+ // TODO: \u8A2D\u7F6E Redis \u5FEB\u53D6
4401
+ // const redis = getRedisClient()
4402
+ // await redis.setex(
4403
+ // \`${moduleName}:\${key}\`,
4404
+ // ttl || this.cacheTTL,
4405
+ // JSON.stringify(value)
4406
+ // )
4407
+ }
4408
+
4409
+ /**
4410
+ * \u5931\u6548\u5FEB\u53D6
4411
+ */
4412
+ async invalidate(key: string): Promise<void> {
4413
+ // \u6E05\u9664\u9032\u7A0B\u5FEB\u53D6
4414
+ this.memoryCache.delete(key)
4415
+
4416
+ // TODO: \u6E05\u9664 Redis \u5FEB\u53D6
4417
+ // const redis = getRedisClient()
4418
+ // await redis.del(\`${moduleName}:\${key}\`)
4419
+ }
4420
+
4421
+ /**
4422
+ * \u6E05\u7A7A\u6240\u6709\u5FEB\u53D6
4423
+ */
4424
+ async clear(): Promise<void> {
4425
+ this.memoryCache.clear()
4426
+
4427
+ // TODO: \u6E05\u7A7A Redis \u5FEB\u53D6
4428
+ // const redis = getRedisClient()
4429
+ // await redis.del(\`${moduleName}:*\`)
4430
+ }
4431
+ }
4432
+ `;
4433
+ }
4434
+ /**
4435
+ * 生成 HTTP 控制器
4436
+ */
4437
+ generateController(moduleName) {
4438
+ const kebabName = this.toKebabCase(moduleName);
4439
+ return `/**
4440
+ * ${moduleName}QueryController - \u67E5\u8A62\u7AEF\u9EDE\u63A7\u5236\u5668
4441
+ *
4442
+ * HTTP \u7AEF\u9EDE\uFF0C\u70BA\u5BA2\u6236\u7AEF\u63D0\u4F9B\u8B80\u6A21\u578B\u67E5\u8A62\u63A5\u53E3\u3002
4443
+ * \u8ABF\u7528\u67E5\u8A62\u670D\u52D9\u4E26\u683C\u5F0F\u5316\u97FF\u61C9\u3002
4444
+ */
4445
+
4446
+ import type { IHttpContext } from '@/Shared/Http/IHttpContext'
4447
+ import { Query${moduleName}Service } from '../../Application/Services/Query${moduleName}Service'
4448
+
4449
+ /**
4450
+ * ${moduleName} \u67E5\u8A62\u63A7\u5236\u5668
4451
+ */
4452
+ export class ${moduleName}QueryController {
4453
+ constructor(private queryService: Query${moduleName}Service) {}
4454
+
4455
+ /**
4456
+ * GET /${kebabName}/:id
4457
+ * \u6839\u64DA ID \u67E5\u8A62\u55AE\u500B ${moduleName}
4458
+ */
4459
+ async findById(ctx: IHttpContext): Promise<void> {
4460
+ try {
4461
+ const { id } = ctx.request.params as { id: string }
4462
+
4463
+ if (!id) {
4464
+ ctx.response.status = 400
4465
+ ctx.response.json({ error: 'ID is required' })
4466
+ return
4467
+ }
4468
+
4469
+ const dto = await this.queryService.findById(id)
4470
+
4471
+ if (!dto) {
4472
+ ctx.response.status = 404
4473
+ ctx.response.json({ error: '${moduleName} not found' })
4474
+ return
4475
+ }
4476
+
4477
+ ctx.response.status = 200
4478
+ ctx.response.json({ data: dto })
4479
+ } catch (error) {
4480
+ this.handleError(ctx, error)
4481
+ }
4482
+ }
4483
+
4484
+ /**
4485
+ * GET /${kebabName}
4486
+ * \u67E5\u8A62\u6240\u6709 ${moduleName}
4487
+ */
4488
+ async findAll(ctx: IHttpContext): Promise<void> {
4489
+ try {
4490
+ const filters = ctx.request.query as Record<string, any> | undefined
4491
+ const dtos = await this.queryService.findAll(filters)
4492
+
4493
+ ctx.response.status = 200
4494
+ ctx.response.json({
4495
+ data: dtos,
4496
+ meta: { count: dtos.length },
4497
+ })
4498
+ } catch (error) {
4499
+ this.handleError(ctx, error)
4500
+ }
4501
+ }
4502
+
4503
+ /**
4504
+ * GET /${kebabName}/search
4505
+ * \u641C\u7D22 ${moduleName}
4506
+ */
4507
+ async search(ctx: IHttpContext): Promise<void> {
4508
+ try {
4509
+ const criteria = ctx.request.query as Record<string, any> | undefined
4510
+
4511
+ if (!criteria) {
4512
+ ctx.response.status = 400
4513
+ ctx.response.json({ error: 'Search criteria required' })
4514
+ return
4515
+ }
4516
+
4517
+ const dtos = await this.queryService.search(criteria)
4518
+
4519
+ ctx.response.status = 200
4520
+ ctx.response.json({
4521
+ data: dtos,
4522
+ meta: { count: dtos.length },
4523
+ })
4524
+ } catch (error) {
4525
+ this.handleError(ctx, error)
4526
+ }
4527
+ }
4528
+
4529
+ /**
4530
+ * GET /${kebabName}/statistics
4531
+ * \u7372\u53D6\u7D71\u8A08\u4FE1\u606F
4532
+ */
4533
+ async getStatistics(ctx: IHttpContext): Promise<void> {
4534
+ try {
4535
+ const stats = await this.queryService.getStatistics()
4536
+
4537
+ ctx.response.status = 200
4538
+ ctx.response.json({ data: stats })
4539
+ } catch (error) {
4540
+ this.handleError(ctx, error)
4541
+ }
4542
+ }
4543
+
4544
+ /**
4545
+ * \u7D71\u4E00\u932F\u8AA4\u8655\u7406
4546
+ */
4547
+ private handleError(ctx: IHttpContext, error: unknown): void {
4548
+ console.error('${moduleName}QueryController error:', error)
4549
+
4550
+ ctx.response.status = 500
4551
+ ctx.response.json({
4552
+ error: 'Internal server error',
4553
+ message: error instanceof Error ? error.message : 'Unknown error',
4554
+ })
4555
+ }
2220
4556
  }
2221
4557
  `;
2222
4558
  }
2223
- generateRoutesRegistry(_context) {
4559
+ /**
4560
+ * 生成路由檔案
4561
+ */
4562
+ generateRoutes(moduleName) {
4563
+ const kebabName = this.toKebabCase(moduleName);
4564
+ const functionName = `register${moduleName}QueryRoutes`;
2224
4565
  return `/**
2225
- * Routes Registry
4566
+ * ${kebabName}.routes.ts - ${moduleName} \u67E5\u8A62\u8DEF\u7531
2226
4567
  *
2227
- * Register all module routes here.
4568
+ * \u8A3B\u518A ${moduleName} \u67E5\u8A62\u7AEF\u9EDE\u7684\u8DEF\u7531
2228
4569
  */
2229
4570
 
2230
- export function registerRoutes(router: any): void {
2231
- // Health check
2232
- router.get('/health', (c: any) => c.json({ status: 'healthy' }))
2233
-
2234
- // Ordering module
2235
- router.get('/api/orders', (c: any) => c.json({ message: 'Order list' }))
2236
- router.post('/api/orders', (c: any) => c.json({ message: 'Order created' }, 201))
4571
+ import type { PlanetCore } from '@gravito/core'
4572
+ import { ${moduleName}QueryController } from '../Controllers/${moduleName}QueryController'
2237
4573
 
2238
- // Catalog module
2239
- router.get('/api/products', (c: any) => c.json({ message: 'Product list' }))
4574
+ /**
4575
+ * \u8A3B\u518A ${moduleName} \u67E5\u8A62\u8DEF\u7531
4576
+ *
4577
+ * \u7AEF\u9EDE\uFF1A
4578
+ * - GET /${kebabName}/:id - \u7372\u53D6\u55AE\u500B ${moduleName}
4579
+ * - GET /${kebabName} - \u5217\u8868\u67E5\u8A62
4580
+ * - GET /${kebabName}/search - \u641C\u7D22
4581
+ * - GET /${kebabName}/statistics - \u7D71\u8A08\u4FE1\u606F
4582
+ */
4583
+ export function ${functionName}(core: PlanetCore): void {
4584
+ // TODO: \u5F9E DI \u5BB9\u5668\u7372\u53D6\u4F9D\u8CF4\u4E26\u5275\u5EFA\u63A7\u5236\u5668
4585
+ // const queryService = core.container.make('query-${kebabName}-service')
4586
+ // const controller = new ${moduleName}QueryController(queryService)
4587
+
4588
+ // TODO: \u8A3B\u518A\u8DEF\u7531
4589
+ // core.router.get('/${kebabName}/:id', (ctx) => controller.findById(ctx))
4590
+ // core.router.get('/${kebabName}', (ctx) => controller.findAll(ctx))
4591
+ // core.router.get('/${kebabName}/search', (ctx) => controller.search(ctx))
4592
+ // core.router.get('/${kebabName}/statistics', (ctx) => controller.getStatistics(ctx))
4593
+
4594
+ console.log(\`\u2713 ${moduleName} query routes registered\`)
2240
4595
  }
2241
4596
  `;
2242
4597
  }
2243
- generateModulesConfig() {
4598
+ /**
4599
+ * 生成模組索引檔案
4600
+ */
4601
+ generateIndex(moduleName) {
2244
4602
  return `/**
2245
- * Modules Configuration
4603
+ * ${moduleName} Query Module
2246
4604
  *
2247
- * Define module boundaries and their dependencies.
4605
+ * CQRS \u67E5\u8A62\u5074\u6A21\u7D44\uFF0C\u63D0\u4F9B\u8B80\u6A21\u578B\u548C\u67E5\u8A62\u670D\u52D9\u3002
4606
+ * \u8A02\u95B1\u4F86\u81EA\u5BEB\u5074\uFF08Event Sourcing\uFF09\u6A21\u7D44\u7684\u4E8B\u4EF6\u3002
2248
4607
  */
2249
4608
 
2250
- export default {
2251
- modules: {
2252
- ordering: {
2253
- name: 'Ordering',
2254
- description: 'Order management module',
2255
- prefix: '/api/orders',
2256
- },
2257
- catalog: {
2258
- name: 'Catalog',
2259
- description: 'Product catalog module',
2260
- prefix: '/api/products',
2261
- },
2262
- },
4609
+ // Domain exports
4610
+ export type { ${moduleName}ReadModel } from './Domain/ReadModels/${moduleName}ReadModel'
4611
+ export { create${moduleName}ReadModel, validate${moduleName}ReadModel } from './Domain/ReadModels/${moduleName}ReadModel'
4612
+ export { ${moduleName}EventProjector } from './Domain/Projectors/${moduleName}EventProjector'
4613
+ export type { I${moduleName}ReadModelRepository } from './Domain/Repositories/I${moduleName}ReadModelRepository'
2263
4614
 
2264
- // Module dependencies
2265
- dependencies: {
2266
- ordering: ['catalog'], // Ordering depends on Catalog
2267
- },
2268
- }
2269
- `;
2270
- }
2271
- generateAppConfig(context) {
2272
- return `export default {
2273
- name: process.env.APP_NAME ?? '${context.name}',
2274
- env: process.env.APP_ENV ?? 'development',
2275
- port: Number.parseInt(process.env.PORT ?? '3000', 10),
2276
- VIEW_DIR: process.env.VIEW_DIR ?? 'src/views',
2277
- debug: process.env.APP_DEBUG === 'true',
2278
- url: process.env.APP_URL ?? 'http://localhost:3000',
2279
- }
4615
+ // Application exports
4616
+ export { Query${moduleName}Service } from './Application/Services/Query${moduleName}Service'
4617
+ export { ${moduleName}ReadDTO } from './Application/DTOs/${moduleName}ReadDTO'
4618
+
4619
+ // Infrastructure exports
4620
+ export { ${moduleName}ReadModelRepository } from './Infrastructure/Repositories/${moduleName}ReadModelRepository'
4621
+ export { ${moduleName}ProjectionSubscriber } from './Infrastructure/Subscribers/${moduleName}ProjectionSubscriber'
4622
+ export { ${moduleName}ReadModelCache } from './Infrastructure/Cache/${moduleName}ReadModelCache'
4623
+
4624
+ // Presentation exports
4625
+ export { ${moduleName}QueryController } from './Presentation/Controllers/${moduleName}QueryController'
4626
+ export { register${moduleName}QueryRoutes } from './Presentation/Routes/${this.toKebabCase(moduleName)}.routes'
2280
4627
  `;
2281
4628
  }
2282
- generateDatabaseConfig() {
2283
- const driver = this.context?.profileConfig?.drivers?.database ?? "none";
2284
- return ConfigGenerator.generateDatabaseConfig(driver);
2285
- }
2286
- generateCacheConfig() {
2287
- return `export default {
2288
- default: process.env.CACHE_DRIVER ?? 'memory',
2289
- stores: { memory: { driver: 'memory' } },
2290
- }
2291
- `;
4629
+ /**
4630
+ * 將字符串轉換為 kebab-case
4631
+ */
4632
+ toKebabCase(str) {
4633
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
2292
4634
  }
2293
- generateLoggingConfig() {
2294
- return `export default {
2295
- default: 'console',
2296
- channels: { console: { driver: 'console', level: 'debug' } },
2297
- }
2298
- `;
4635
+ /**
4636
+ * 將字符串轉換為 snake_case
4637
+ */
4638
+ toSnakeCase(str) {
4639
+ return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase();
2299
4640
  }
2300
4641
  };
2301
4642
 
@@ -2442,6 +4783,30 @@ var ModuleGenerator = class {
2442
4783
  ]
2443
4784
  }
2444
4785
  ]
4786
+ },
4787
+ // UserInterface Layer
4788
+ {
4789
+ type: "directory",
4790
+ name: "UserInterface",
4791
+ children: [
4792
+ {
4793
+ type: "directory",
4794
+ name: "Http",
4795
+ children: [
4796
+ {
4797
+ type: "directory",
4798
+ name: "Controllers",
4799
+ children: [
4800
+ {
4801
+ type: "file",
4802
+ name: `${name}Controller.ts`,
4803
+ content: this.generateController(name)
4804
+ }
4805
+ ]
4806
+ }
4807
+ ]
4808
+ }
4809
+ ]
2445
4810
  }
2446
4811
  ]
2447
4812
  };
@@ -2457,17 +4822,13 @@ import { ${name}Created } from '../../Events/${name}Created'
2457
4822
  import { ${name}Status } from './${name}Status'
2458
4823
 
2459
4824
  export interface ${name}Props {
2460
- // Add properties here
2461
4825
  status: ${name}Status
2462
4826
  createdAt: Date
2463
4827
  }
2464
4828
 
2465
4829
  export class ${name} extends AggregateRoot<Id> {
2466
- private props: ${name}Props
2467
-
2468
- private constructor(id: Id, props: ${name}Props) {
4830
+ private constructor(id: Id, private props: ${name}Props) {
2469
4831
  super(id)
2470
- this.props = props
2471
4832
  }
2472
4833
 
2473
4834
  static create(id: Id): ${name} {
@@ -2485,7 +4846,12 @@ export class ${name} extends AggregateRoot<Id> {
2485
4846
  return this.props.status
2486
4847
  }
2487
4848
 
2488
- // Add domain methods here
4849
+ /**
4850
+ * Complete the ${name} process
4851
+ */
4852
+ complete(): void {
4853
+ this.props.status = ${name}Status.COMPLETED
4854
+ }
2489
4855
  }
2490
4856
  `;
2491
4857
  }
@@ -2510,17 +4876,13 @@ export enum ${name}Status {
2510
4876
  import { DomainEvent } from '@gravito/enterprise'
2511
4877
 
2512
4878
  export class ${name}Created extends DomainEvent {
2513
- constructor(public readonly ${name.toLowerCase()}Id: string) {
4879
+ constructor(public readonly aggregateId: string) {
2514
4880
  super()
2515
4881
  }
2516
4882
 
2517
4883
  override get eventName(): string {
2518
4884
  return '${name.toLowerCase()}.created'
2519
4885
  }
2520
-
2521
- get aggregateId(): string {
2522
- return this.${name.toLowerCase()}Id
2523
- }
2524
4886
  }
2525
4887
  `;
2526
4888
  }
@@ -2547,7 +4909,6 @@ import { Command } from '@gravito/enterprise'
2547
4909
 
2548
4910
  export class Create${name}Command extends Command {
2549
4911
  constructor(
2550
- // Add command properties
2551
4912
  public readonly id?: string
2552
4913
  ) {
2553
4914
  super()
@@ -2602,13 +4963,15 @@ export class Get${name}ByIdQuery extends Query {
2602
4963
  import { QueryHandler } from '@gravito/enterprise'
2603
4964
  import type { I${name}Repository } from '../../../Domain/Repositories/I${name}Repository'
2604
4965
  import type { ${name}DTO } from '../../DTOs/${name}DTO'
4966
+ import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2605
4967
  import type { Get${name}ByIdQuery } from './Get${name}ByIdQuery'
2606
4968
 
2607
4969
  export class Get${name}ByIdHandler implements QueryHandler<Get${name}ByIdQuery, ${name}DTO | null> {
2608
4970
  constructor(private repository: I${name}Repository) {}
2609
4971
 
2610
4972
  async handle(query: Get${name}ByIdQuery): Promise<${name}DTO | null> {
2611
- const aggregate = await this.repository.findById(query.id as any) // Simplified for demo
4973
+ const id = Id.from(query.id)
4974
+ const aggregate = await this.repository.findById(id)
2612
4975
  if (!aggregate) return null
2613
4976
 
2614
4977
  return {
@@ -2629,7 +4992,6 @@ import type { ${name}Status } from '../../Domain/Aggregates/${name}/${name}Statu
2629
4992
  export interface ${name}DTO {
2630
4993
  id: string
2631
4994
  status: ${name}Status
2632
- // Add more fields
2633
4995
  }
2634
4996
  `;
2635
4997
  }
@@ -2640,29 +5002,29 @@ export interface ${name}DTO {
2640
5002
 
2641
5003
  import type { ${name} } from '../../Domain/Aggregates/${name}/${name}'
2642
5004
  import type { I${name}Repository } from '../../Domain/Repositories/I${name}Repository'
2643
- import type { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2644
-
2645
- const store = new Map<string, ${name}>()
5005
+ import { Id } from '../../../../../Shared/Domain/ValueObjects/Id'
2646
5006
 
2647
5007
  export class ${name}Repository implements I${name}Repository {
5008
+ private store = new Map<string, ${name}>()
5009
+
2648
5010
  async findById(id: Id): Promise<${name} | null> {
2649
- return store.get(id.value) ?? null
5011
+ return this.store.get(id.value) ?? null
2650
5012
  }
2651
5013
 
2652
5014
  async save(aggregate: ${name}): Promise<void> {
2653
- store.set(aggregate.id.value, aggregate)
5015
+ this.store.set(aggregate.id.value, aggregate)
2654
5016
  }
2655
5017
 
2656
5018
  async delete(id: Id): Promise<void> {
2657
- store.delete(id.value)
5019
+ this.store.delete(id.value)
2658
5020
  }
2659
5021
 
2660
5022
  async findAll(): Promise<${name}[]> {
2661
- return Array.from(store.values())
5023
+ return Array.from(this.store.values())
2662
5024
  }
2663
5025
 
2664
5026
  async exists(id: Id): Promise<boolean> {
2665
- return store.has(id.value)
5027
+ return this.store.has(id.value)
2666
5028
  }
2667
5029
  }
2668
5030
  `;
@@ -2684,6 +5046,37 @@ export class ${name}ServiceProvider extends ServiceProvider {
2684
5046
  console.log('[${name}] Module loaded')
2685
5047
  }
2686
5048
  }
5049
+ `;
5050
+ }
5051
+ generateController(name) {
5052
+ return `/**
5053
+ * ${name} Controller
5054
+ */
5055
+
5056
+ import type { GravitoContext } from '@gravito/core'
5057
+
5058
+ export class ${name}Controller {
5059
+ /**
5060
+ * GET /${name.toLowerCase()}
5061
+ */
5062
+ async index(ctx: GravitoContext) {
5063
+ return ctx.json({
5064
+ success: true,
5065
+ data: []
5066
+ })
5067
+ }
5068
+
5069
+ /**
5070
+ * GET /${name.toLowerCase()}/:id
5071
+ */
5072
+ async show(ctx: GravitoContext) {
5073
+ const id = ctx.req.param('id')
5074
+ return ctx.json({
5075
+ success: true,
5076
+ data: { id }
5077
+ })
5078
+ }
5079
+ }
2687
5080
  `;
2688
5081
  }
2689
5082
  };
@@ -2858,24 +5251,39 @@ export class Email extends ValueObject<EmailProps> {
2858
5251
 
2859
5252
  import type { DomainEvent } from '@gravito/enterprise'
2860
5253
 
2861
- type EventHandler = (event: DomainEvent) => void | Promise<void>
5254
+ type EventHandler<T extends DomainEvent = any> = (event: T) => void | Promise<void>
2862
5255
 
2863
5256
  export class EventDispatcher {
2864
5257
  private handlers: Map<string, EventHandler[]> = new Map()
2865
5258
 
2866
- subscribe(eventName: string, handler: EventHandler): void {
5259
+ /**
5260
+ * Subscribe to an event
5261
+ */
5262
+ subscribe<T extends DomainEvent>(eventName: string, handler: EventHandler<T>): void {
2867
5263
  const handlers = this.handlers.get(eventName) ?? []
2868
5264
  handlers.push(handler)
2869
5265
  this.handlers.set(eventName, handlers)
2870
5266
  }
2871
5267
 
5268
+ /**
5269
+ * Dispatch a single event
5270
+ */
2872
5271
  async dispatch(event: DomainEvent): Promise<void> {
2873
5272
  const handlers = this.handlers.get(event.eventName) ?? []
2874
- for (const handler of handlers) {
2875
- await handler(event)
2876
- }
5273
+ const promises = handlers.map(handler => {
5274
+ try {
5275
+ return handler(event)
5276
+ } catch (error) {
5277
+ console.error(\`[EventDispatcher] Error in handler for \${event.eventName}:\`, error)
5278
+ }
5279
+ })
5280
+
5281
+ await Promise.all(promises)
2877
5282
  }
2878
5283
 
5284
+ /**
5285
+ * Dispatch multiple events sequentially
5286
+ */
2879
5287
  async dispatchAll(events: DomainEvent[]): Promise<void> {
2880
5288
  for (const event of events) {
2881
5289
  await this.dispatch(event)
@@ -2899,24 +5307,38 @@ export function report(error: unknown): void {
2899
5307
  // src/generators/DddGenerator.ts
2900
5308
  var DddGenerator = class extends BaseGenerator {
2901
5309
  moduleGenerator;
5310
+ advancedModuleGenerator;
5311
+ cqrsQueryModuleGenerator;
2902
5312
  sharedKernelGenerator;
2903
5313
  bootstrapGenerator;
5314
+ moduleType = "simple";
2904
5315
  constructor(config) {
2905
5316
  super(config);
2906
5317
  this.moduleGenerator = new ModuleGenerator();
5318
+ this.advancedModuleGenerator = new AdvancedModuleGenerator();
5319
+ this.cqrsQueryModuleGenerator = new CQRSQueryModuleGenerator();
2907
5320
  this.sharedKernelGenerator = new SharedKernelGenerator();
2908
5321
  this.bootstrapGenerator = new BootstrapGenerator();
2909
5322
  }
5323
+ /**
5324
+ * Set the module type for generated modules
5325
+ * @param type - 'simple' for basic CRUD, 'advanced' for Event Sourcing, 'cqrs-query' for CQRS Read Side
5326
+ * @default 'simple'
5327
+ */
5328
+ setModuleType(type) {
5329
+ this.moduleType = type;
5330
+ }
2910
5331
  get architectureType() {
2911
5332
  return "ddd";
2912
5333
  }
2913
5334
  get displayName() {
2914
- return "Domain-Driven Design (DDD)";
5335
+ return `Domain-Driven Design (DDD)${this.moduleType === "cqrs-query" ? " + CQRS" : ""}`;
2915
5336
  }
2916
5337
  get description() {
2917
5338
  return "Full DDD with Bounded Contexts, Aggregates, and Event-Driven patterns";
2918
5339
  }
2919
5340
  getDirectoryStructure(context) {
5341
+ const moduleGenerator = this.getModuleGenerator();
2920
5342
  return [
2921
5343
  this.bootstrapGenerator.generateConfigDirectory(context),
2922
5344
  {
@@ -2932,8 +5354,8 @@ var DddGenerator = class extends BaseGenerator {
2932
5354
  type: "directory",
2933
5355
  name: "Modules",
2934
5356
  children: [
2935
- this.moduleGenerator.generate("Ordering", context),
2936
- this.moduleGenerator.generate("Catalog", context)
5357
+ moduleGenerator === "cqrs-query" ? this.cqrsQueryModuleGenerator.generate("Ordering", context) : moduleGenerator === "advanced" ? this.advancedModuleGenerator.generate("Ordering", context) : this.moduleGenerator.generate("Ordering", context),
5358
+ moduleGenerator === "cqrs-query" ? this.cqrsQueryModuleGenerator.generate("Catalog", context) : moduleGenerator === "advanced" ? this.advancedModuleGenerator.generate("Catalog", context) : this.moduleGenerator.generate("Catalog", context)
2937
5359
  ]
2938
5360
  },
2939
5361
  {
@@ -2989,6 +5411,12 @@ var DddGenerator = class extends BaseGenerator {
2989
5411
  }
2990
5412
  ];
2991
5413
  }
5414
+ /**
5415
+ * Get the active module generator type
5416
+ */
5417
+ getModuleGenerator() {
5418
+ return this.moduleType;
5419
+ }
2992
5420
  /**
2993
5421
  * Override package.json for DDD architecture (uses main.ts instead of bootstrap.ts)
2994
5422
  */
@@ -3028,6 +5456,69 @@ var DddGenerator = class extends BaseGenerator {
3028
5456
 
3029
5457
  This project follows **Domain-Driven Design (DDD)** with strategic and tactical patterns.
3030
5458
 
5459
+ ${this.moduleType === "cqrs-query" ? `
5460
+ ## Module Types
5461
+
5462
+ This project uses **CQRS Query Module Template** with event projection:
5463
+ - **Read Models**: Denormalized, query-optimized data structures
5464
+ - **Event Projectors**: Pure functions transforming events to read models
5465
+ - **Query Services**: Dedicated read-side use cases
5466
+ - **Event Subscribers**: Subscribe to write-side domain events
5467
+ - **Optional Caching**: Dual-tier caching (memory + Redis)
5468
+
5469
+ See each module's Domain/Projectors/{ModuleName}EventProjector.ts for projection patterns.
5470
+
5471
+ ## CQRS Architecture
5472
+
5473
+ \`\`\`
5474
+ Write Side (Command) Read Side (Query)
5475
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
5476
+ \u2502 Aggregate Root \u2502 \u2502 Read Model \u2502
5477
+ \u2502 (Event Sourcing) \u2502 \u2502 (Denormalized) \u2502
5478
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
5479
+ \u2502 \u25B2
5480
+ \u2502 Domain Events \u2502
5481
+ \u25BC \u2502
5482
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
5483
+ \u2502 Event Store \u2502\u2500\u2500\u2500\u2500\u2500\u25B6\u2502 Event Subscriber \u2502
5484
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
5485
+ \u2502
5486
+ \u2502 Projects Events
5487
+ \u25BC
5488
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
5489
+ \u2502 Event Projector \u2502
5490
+ \u2502 (Pure Functions) \u2502
5491
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
5492
+ \u2502
5493
+ \u25BC
5494
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
5495
+ \u2502 Read Model DB \u2502
5496
+ \u2502 (Eventually Consistent)
5497
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
5498
+ \`\`\`
5499
+ ` : this.moduleType === "advanced" ? `
5500
+ ## Module Types
5501
+
5502
+ This project uses **Advanced Module Template** with Event Sourcing:
5503
+ - **Event Sourcing**: Complete event stream as source of truth
5504
+ - **Aggregate Roots**: Domain objects managing state through events
5505
+ - **Domain Events**: Rich, expressive events capturing domain changes
5506
+ - **Event Store**: Persistent event log for state reconstruction
5507
+ - **Event Applier**: Pure functions for immutable state transitions
5508
+
5509
+ See each module's Domain/Services/{ModuleName}EventApplier.ts for event handling patterns.
5510
+ ` : `
5511
+ ## Module Types
5512
+
5513
+ This project uses **Simple Module Template** with basic CRUD:
5514
+ - **Aggregates**: Standard entity-based domain objects
5515
+ - **Repositories**: Data access abstractions
5516
+ - **Services**: Domain and application logic
5517
+ - **Events**: Optional domain event support
5518
+
5519
+ To upgrade a module to Event Sourcing, use the Advanced template when scaffolding new modules.
5520
+ `}
5521
+
3031
5522
  ## Service Providers
3032
5523
 
3033
5524
  Service providers are the central place to configure your application and modules. They follow the ServiceProvider pattern with \`register()\` and \`boot()\` lifecycle methods.
@@ -3058,12 +5549,38 @@ Service providers are the central place to configure your application and module
3058
5549
  Each bounded context follows this structure:
3059
5550
 
3060
5551
  \`\`\`
5552
+ ${this.moduleType === "cqrs-query" ? `
5553
+ Context/ # CQRS Read Side Module
5554
+ \u251C\u2500\u2500 Domain/ # Query domain logic
5555
+ \u2502 \u251C\u2500\u2500 ReadModels/ # Denormalized data structures (immutable)
5556
+ \u2502 \u251C\u2500\u2500 Projectors/ # Pure functions: Event \u2192 ReadModel
5557
+ \u2502 \u2514\u2500\u2500 Repositories/ # Read model access interfaces
5558
+ \u251C\u2500\u2500 Application/ # Query use cases
5559
+ \u2502 \u251C\u2500\u2500 Services/ # Query services (findById, findAll, search, etc.)
5560
+ \u2502 \u2514\u2500\u2500 DTOs/ # Data transfer objects for responses
5561
+ \u251C\u2500\u2500 Infrastructure/ # Data access & external services
5562
+ \u2502 \u251C\u2500\u2500 Repositories/ # Read model implementations (ORM)
5563
+ \u2502 \u251C\u2500\u2500 Subscribers/ # Event subscribers (trigger projections)
5564
+ \u2502 \u2514\u2500\u2500 Cache/ # Optional caching layer (memory + Redis)
5565
+ \u2514\u2500\u2500 Presentation/ # Entry points
5566
+ \u251C\u2500\u2500 Controllers/ # HTTP query endpoints
5567
+ \u2514\u2500\u2500 Routes/ # Route registration
5568
+ \`\`\`
5569
+
5570
+ **Key Differences from Write Side:**
5571
+ - No Commands (read-only)
5572
+ - No EventStore (subscribes to write-side events)
5573
+ - Pure projectors (deterministic, testable)
5574
+ - Read models optimized for specific queries
5575
+ - Eventual consistency (projections may lag behind events)
5576
+ ` : `
3061
5577
  Context/
3062
5578
  \u251C\u2500\u2500 Domain/ # Core business logic
3063
5579
  \u2502 \u251C\u2500\u2500 Aggregates/ # Aggregate roots + entities
3064
5580
  \u2502 \u251C\u2500\u2500 Events/ # Domain events
3065
5581
  \u2502 \u251C\u2500\u2500 Repositories/ # Repository interfaces
3066
- \u2502 \u2514\u2500\u2500 Services/ # Domain services
5582
+ \u2502 \u251C\u2500\u2500 Services/ # Domain services (${this.moduleType === "advanced" ? "EventApplier for Event Sourcing" : "domain logic"})
5583
+ \u2502 \u2514\u2500\u2500 ValueObjects/ # Domain value objects
3067
5584
  \u251C\u2500\u2500 Application/ # Use cases
3068
5585
  \u2502 \u251C\u2500\u2500 Commands/ # Write operations
3069
5586
  \u2502 \u251C\u2500\u2500 Queries/ # Read operations
@@ -3071,11 +5588,13 @@ Context/
3071
5588
  \u2502 \u2514\u2500\u2500 DTOs/ # Data transfer objects
3072
5589
  \u251C\u2500\u2500 Infrastructure/ # External concerns
3073
5590
  \u2502 \u251C\u2500\u2500 Persistence/ # Repository implementations
5591
+ \u2502 \u251C\u2500\u2500 EventStore/ # ${this.moduleType === "advanced" ? "Event storage and reconstruction" : "(optional) Event storage"}
3074
5592
  \u2502 \u2514\u2500\u2500 Providers/ # DI configuration
3075
5593
  \u2514\u2500\u2500 UserInterface/ # Entry points
3076
5594
  \u251C\u2500\u2500 Http/ # REST controllers
3077
5595
  \u2514\u2500\u2500 Cli/ # CLI commands
3078
5596
  \`\`\`
5597
+ `}
3079
5598
 
3080
5599
  ## SharedKernel
3081
5600
 
@@ -3091,6 +5610,10 @@ Contains types shared across contexts:
3091
5610
  2. **Domain Events**: Inter-context communication
3092
5611
  3. **CQRS**: Separate read/write models
3093
5612
  4. **Repository Pattern**: Persistence abstraction
5613
+ ${this.moduleType === "cqrs-query" ? `5. **Event Projection**: Transforming events to denormalized read models
5614
+ 6. **Pure Projectors**: Deterministic, testable event handlers
5615
+ 7. **Eventual Consistency**: Read models eventually consistent with events
5616
+ 8. **Query Optimization**: Read models optimized for specific use cases` : this.moduleType === "advanced" ? "5. **Event Sourcing**: Event stream as single source of truth\n6. **Event Applier**: Pure functions for state transitions" : ""}
3094
5617
 
3095
5618
  Created with \u2764\uFE0F using Gravito Framework
3096
5619
  `;
@@ -3985,9 +6508,9 @@ var SatelliteGenerator = class extends BaseGenerator {
3985
6508
  children: [
3986
6509
  {
3987
6510
  type: "directory",
3988
- name: "Entities",
6511
+ name: "Aggregates",
3989
6512
  children: [
3990
- { type: "file", name: `${name}.ts`, content: this.generateEntity(name) }
6513
+ { type: "file", name: `${name}.ts`, content: this.generateAggregate(name) }
3991
6514
  ]
3992
6515
  },
3993
6516
  {
@@ -4001,8 +6524,24 @@ var SatelliteGenerator = class extends BaseGenerator {
4001
6524
  }
4002
6525
  ]
4003
6526
  },
4004
- { type: "directory", name: "ValueObjects", children: [] },
4005
- { type: "directory", name: "Events", children: [] }
6527
+ {
6528
+ type: "directory",
6529
+ name: "ValueObjects",
6530
+ children: [
6531
+ { type: "file", name: `${name}Id.ts`, content: this.generateIdValueObject(name) }
6532
+ ]
6533
+ },
6534
+ {
6535
+ type: "directory",
6536
+ name: "Events",
6537
+ children: [
6538
+ {
6539
+ type: "file",
6540
+ name: `${name}Created.ts`,
6541
+ content: this.generateCreatedEvent(name)
6542
+ }
6543
+ ]
6544
+ }
4006
6545
  ]
4007
6546
  },
4008
6547
  // Application Layer
@@ -4043,6 +6582,31 @@ var SatelliteGenerator = class extends BaseGenerator {
4043
6582
  }
4044
6583
  ]
4045
6584
  },
6585
+ // Interface Layer (HTTP/API)
6586
+ {
6587
+ type: "directory",
6588
+ name: "Interface",
6589
+ children: [
6590
+ {
6591
+ type: "directory",
6592
+ name: "Http",
6593
+ children: [
6594
+ {
6595
+ type: "directory",
6596
+ name: "Controllers",
6597
+ children: [
6598
+ {
6599
+ type: "file",
6600
+ name: `${name}Controller.ts`,
6601
+ content: this.generateController(name)
6602
+ }
6603
+ ]
6604
+ },
6605
+ { type: "directory", name: "Middleware", children: [] }
6606
+ ]
6607
+ }
6608
+ ]
6609
+ },
4046
6610
  // Entry Point
4047
6611
  { type: "file", name: "index.ts", content: this.generateEntryPoint(name) },
4048
6612
  {
@@ -4060,13 +6624,12 @@ var SatelliteGenerator = class extends BaseGenerator {
4060
6624
  {
4061
6625
  type: "file",
4062
6626
  name: "unit.test.ts",
4063
- content: `import { describe, it, expect } from "bun:test";
4064
-
4065
- describe("${name}", () => {
4066
- it("should work", () => {
4067
- expect(true).toBe(true);
4068
- });
4069
- });`
6627
+ content: this.generateUnitTest(name)
6628
+ },
6629
+ {
6630
+ type: "file",
6631
+ name: "integration.test.ts",
6632
+ content: this.generateIntegrationTest(name)
4070
6633
  }
4071
6634
  ]
4072
6635
  }
@@ -4075,35 +6638,76 @@ describe("${name}", () => {
4075
6638
  // ─────────────────────────────────────────────────────────────
4076
6639
  // Domain Templates
4077
6640
  // ─────────────────────────────────────────────────────────────
4078
- generateEntity(name) {
4079
- return `import { Entity } from '@gravito/enterprise'
6641
+ generateIdValueObject(name) {
6642
+ return `import { ValueObject } from '@gravito/enterprise'
6643
+
6644
+ interface IdProps {
6645
+ value: string
6646
+ }
6647
+
6648
+ export class ${name}Id extends ValueObject<IdProps> {
6649
+ constructor(value: string) {
6650
+ super({ value })
6651
+ }
6652
+
6653
+ static create(): ${name}Id {
6654
+ return new ${name}Id(crypto.randomUUID())
6655
+ }
6656
+
6657
+ get value(): string { return this.props.value }
6658
+ }
6659
+ `;
6660
+ }
6661
+ generateAggregate(name) {
6662
+ return `import { AggregateRoot } from '@gravito/enterprise'
6663
+ import { ${name}Id } from '../ValueObjects/${name}Id'
6664
+ import { ${name}Created } from '../Events/${name}Created'
4080
6665
 
4081
6666
  export interface ${name}Props {
4082
6667
  name: string
4083
6668
  createdAt: Date
4084
6669
  }
4085
6670
 
4086
- export class ${name} extends Entity<string> {
4087
- constructor(id: string, private props: ${name}Props) {
6671
+ export class ${name} extends AggregateRoot<${name}Id> {
6672
+ constructor(id: ${name}Id, private props: ${name}Props) {
4088
6673
  super(id)
4089
6674
  }
4090
6675
 
4091
- static create(id: string, name: string): ${name} {
4092
- return new ${name}(id, {
6676
+ static create(id: ${name}Id, name: string): ${name} {
6677
+ const aggregate = new ${name}(id, {
4093
6678
  name,
4094
6679
  createdAt: new Date()
4095
6680
  })
6681
+
6682
+ aggregate.addDomainEvent(new ${name}Created(id.value))
6683
+
6684
+ return aggregate
4096
6685
  }
4097
6686
 
4098
6687
  get name() { return this.props.name }
4099
6688
  }
6689
+ `;
6690
+ }
6691
+ generateCreatedEvent(name) {
6692
+ return `import { DomainEvent } from '@gravito/enterprise'
6693
+
6694
+ export class ${name}Created extends DomainEvent {
6695
+ constructor(public readonly aggregateId: string) {
6696
+ super()
6697
+ }
6698
+
6699
+ get eventName(): string {
6700
+ return '${this.context?.nameKebabCase}.created'
6701
+ }
6702
+ }
4100
6703
  `;
4101
6704
  }
4102
6705
  generateRepositoryInterface(name) {
4103
6706
  return `import { Repository } from '@gravito/enterprise'
4104
- import { ${name} } from '../Entities/${name}'
6707
+ import { ${name} } from '../Aggregates/${name}'
6708
+ import { ${name}Id } from '../ValueObjects/${name}Id'
4105
6709
 
4106
- export interface I${name}Repository extends Repository<${name}, string> {
6710
+ export interface I${name}Repository extends Repository<${name}, ${name}Id> {
4107
6711
  // Add custom methods here
4108
6712
  }
4109
6713
  `;
@@ -4114,7 +6718,8 @@ export interface I${name}Repository extends Repository<${name}, string> {
4114
6718
  generateUseCase(name) {
4115
6719
  return `import { UseCase } from '@gravito/enterprise'
4116
6720
  import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
4117
- import { ${name} } from '../../Domain/Entities/${name}'
6721
+ import { ${name} } from '../../Domain/Aggregates/${name}'
6722
+ import { ${name}Id } from '../../Domain/ValueObjects/${name}Id'
4118
6723
 
4119
6724
  export interface Create${name}Input {
4120
6725
  name: string
@@ -4126,12 +6731,12 @@ export class Create${name} extends UseCase<Create${name}Input, string> {
4126
6731
  }
4127
6732
 
4128
6733
  async execute(input: Create${name}Input): Promise<string> {
4129
- const id = crypto.randomUUID()
6734
+ const id = ${name}Id.create()
4130
6735
  const entity = ${name}.create(id, input.name)
4131
6736
 
4132
6737
  await this.repository.save(entity)
4133
6738
 
4134
- return id
6739
+ return id.value
4135
6740
  }
4136
6741
  }
4137
6742
  `;
@@ -4141,17 +6746,16 @@ export class Create${name} extends UseCase<Create${name}Input, string> {
4141
6746
  // ─────────────────────────────────────────────────────────────
4142
6747
  generateAtlasRepository(name) {
4143
6748
  return `import { I${name}Repository } from '../../Domain/Contracts/I${name}Repository'
4144
- import { ${name} } from '../../Domain/Entities/${name}'
4145
- import { DB } from '@gravito/atlas'
6749
+ import { ${name} } from '../../Domain/Aggregates/${name}'
6750
+ import { ${name}Id } from '../../Domain/ValueObjects/${name}Id'
4146
6751
 
4147
6752
  export class Atlas${name}Repository implements I${name}Repository {
4148
6753
  async save(entity: ${name}): Promise<void> {
4149
- // Dogfooding: Use @gravito/atlas for persistence
4150
- console.log('[Atlas] Saving entity:', entity.id)
4151
- // await DB.table('${name.toLowerCase()}s').insert({ ... })
6754
+ // Implementation using @gravito/atlas
6755
+ console.log('[Atlas] Saving aggregate:', entity.id.value)
4152
6756
  }
4153
6757
 
4154
- async findById(id: string): Promise<${name} | null> {
6758
+ async findById(id: ${name}Id): Promise<${name} | null> {
4155
6759
  return null
4156
6760
  }
4157
6761
 
@@ -4159,12 +6763,77 @@ export class Atlas${name}Repository implements I${name}Repository {
4159
6763
  return []
4160
6764
  }
4161
6765
 
4162
- async delete(id: string): Promise<void> {}
6766
+ async delete(id: ${name}Id): Promise<void> {}
4163
6767
 
4164
- async exists(id: string): Promise<boolean> {
6768
+ async exists(id: ${name}Id): Promise<boolean> {
4165
6769
  return false
4166
6770
  }
4167
6771
  }
6772
+ `;
6773
+ }
6774
+ // ─────────────────────────────────────────────────────────────
6775
+ // Test Templates
6776
+ // ─────────────────────────────────────────────────────────────
6777
+ generateUnitTest(name) {
6778
+ return `import { describe, it, expect } from "bun:test";
6779
+ import { ${name} } from "../src/Domain/Aggregates/${name}";
6780
+ import { ${name}Id } from "../src/Domain/ValueObjects/${name}Id";
6781
+
6782
+ describe("${name} Aggregate", () => {
6783
+ it("should create a new aggregate with a domain event", () => {
6784
+ const id = ${name}Id.create();
6785
+ const aggregate = ${name}.create(id, "Test Name");
6786
+
6787
+ expect(aggregate.id).toBe(id);
6788
+ expect(aggregate.name).toBe("Test Name");
6789
+ expect(aggregate.pullDomainEvents()).toHaveLength(1);
6790
+ });
6791
+ });`;
6792
+ }
6793
+ generateIntegrationTest(name) {
6794
+ return `import { describe, it, expect, beforeAll } from "bun:test";
6795
+ import { PlanetCore } from "@gravito/core";
6796
+
6797
+ describe("${name} Integration", () => {
6798
+ let core: PlanetCore;
6799
+
6800
+ beforeAll(async () => {
6801
+ core = new PlanetCore();
6802
+ // Setup dependencies here
6803
+ });
6804
+
6805
+ it("should handle the creation flow", async () => {
6806
+ expect(true).toBe(true); // Placeholder for actual integration logic
6807
+ });
6808
+ });`;
6809
+ }
6810
+ // ─────────────────────────────────────────────────────────────
6811
+ // Interface Templates
6812
+ // ─────────────────────────────────────────────────────────────
6813
+ generateController(name) {
6814
+ return `import type { GravitoContext } from '@gravito/core'
6815
+ import { Create${name} } from '../../../Application/UseCases/Create${name}'
6816
+
6817
+ export class ${name}Controller {
6818
+ constructor(private createUseCase: Create${name}) {}
6819
+
6820
+ async store(ctx: GravitoContext) {
6821
+ const body = await ctx.req.json()
6822
+ const id = await this.createUseCase.execute({ name: body.name })
6823
+
6824
+ return ctx.json({
6825
+ success: true,
6826
+ data: { id }
6827
+ }, 201)
6828
+ }
6829
+
6830
+ async index(ctx: GravitoContext) {
6831
+ return ctx.json({
6832
+ success: true,
6833
+ data: []
6834
+ })
6835
+ }
6836
+ }
4168
6837
  `;
4169
6838
  }
4170
6839
  // ─────────────────────────────────────────────────────────────
@@ -4173,17 +6842,22 @@ export class Atlas${name}Repository implements I${name}Repository {
4173
6842
  generateEntryPoint(name) {
4174
6843
  return `import { ServiceProvider, type Container } from '@gravito/core'
4175
6844
  import { Atlas${name}Repository } from './Infrastructure/Persistence/Atlas${name}Repository'
6845
+ import { Create${name} } from './Application/UseCases/Create${name}'
6846
+ import { ${name}Controller } from './Interface/Http/Controllers/${name}Controller'
4176
6847
 
4177
6848
  export class ${name}ServiceProvider extends ServiceProvider {
4178
6849
  register(container: Container): void {
4179
- // Bind Repository
6850
+ // 1. Bind Repository (Infrastructure)
4180
6851
  container.singleton('${name.toLowerCase()}.repo', () => new Atlas${name}Repository())
4181
6852
 
4182
- // Bind UseCases
4183
- container.singleton('${name.toLowerCase()}.create', () => {
4184
- return new (require('./Application/UseCases/Create${name}').Create${name})(
4185
- container.make('${name.toLowerCase()}.repo')
4186
- )
6853
+ // 2. Bind UseCases (Application)
6854
+ container.singleton('${name.toLowerCase()}.usecase.create', (c) => {
6855
+ return new Create${name}(c.make('${name.toLowerCase()}.repo'))
6856
+ })
6857
+
6858
+ // 3. Bind Controllers (Interface)
6859
+ container.singleton('${name.toLowerCase()}.controller', (c) => {
6860
+ return new ${name}Controller(c.make('${name.toLowerCase()}.usecase.create'))
4187
6861
  })
4188
6862
  }
4189
6863
 
@@ -4294,7 +6968,9 @@ var ProfileResolver = class _ProfileResolver {
4294
6968
  storage: "local",
4295
6969
  session: "file"
4296
6970
  },
4297
- features: []
6971
+ features: [],
6972
+ workers: "basic"
6973
+ // Basic workers configuration for core profile
4298
6974
  },
4299
6975
  scale: {
4300
6976
  drivers: {
@@ -4304,7 +6980,9 @@ var ProfileResolver = class _ProfileResolver {
4304
6980
  storage: "s3",
4305
6981
  session: "redis"
4306
6982
  },
4307
- features: ["stream", "nebula"]
6983
+ features: ["stream", "nebula"],
6984
+ workers: "advanced"
6985
+ // Advanced workers with Bun optimizations
4308
6986
  },
4309
6987
  enterprise: {
4310
6988
  drivers: {
@@ -4314,14 +6992,17 @@ var ProfileResolver = class _ProfileResolver {
4314
6992
  storage: "s3",
4315
6993
  session: "redis"
4316
6994
  },
4317
- features: ["stream", "nebula", "monitor", "sentinel", "fortify"]
6995
+ features: ["stream", "nebula", "monitor", "sentinel", "fortify"],
6996
+ workers: "production"
6997
+ // Production-optimized workers
4318
6998
  }
4319
6999
  };
4320
7000
  resolve(profile = "core", withFeatures = []) {
4321
7001
  const base = _ProfileResolver.DEFAULTS[profile] || _ProfileResolver.DEFAULTS.core;
4322
7002
  const config = {
4323
7003
  drivers: { ...base.drivers },
4324
- features: [...base.features]
7004
+ features: [...base.features],
7005
+ workers: base.workers
4325
7006
  };
4326
7007
  for (const feature of withFeatures) {
4327
7008
  this.applyFeature(config, feature);
@@ -4389,6 +7070,7 @@ var ProfileResolver = class _ProfileResolver {
4389
7070
 
4390
7071
  // src/Scaffold.ts
4391
7072
  var import_node_path6 = __toESM(require("path"), 1);
7073
+ var import_node_url = require("url");
4392
7074
 
4393
7075
  // src/generators/ActionDomainGenerator.ts
4394
7076
  var ActionDomainGenerator = class extends BaseGenerator {
@@ -5062,11 +7744,16 @@ bun start
5062
7744
  };
5063
7745
 
5064
7746
  // src/Scaffold.ts
5065
- var Scaffold = class {
7747
+ var import_meta = {};
7748
+ var Scaffold = class _Scaffold {
5066
7749
  templatesDir;
5067
7750
  verbose;
7751
+ static packageDir = import_node_path6.default.resolve(
7752
+ import_node_path6.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url)),
7753
+ ".."
7754
+ );
5068
7755
  constructor(options = {}) {
5069
- this.templatesDir = options.templatesDir ?? import_node_path6.default.resolve(__dirname, "../templates");
7756
+ this.templatesDir = options.templatesDir ?? import_node_path6.default.resolve(_Scaffold.packageDir, "templates");
5070
7757
  this.verbose = options.verbose ?? false;
5071
7758
  }
5072
7759
  /**
@@ -5118,7 +7805,7 @@ var Scaffold = class {
5118
7805
  * @returns {Promise<ScaffoldResult>}
5119
7806
  */
5120
7807
  async create(options) {
5121
- const generator = this.createGenerator(options.architecture);
7808
+ const generator = this.createGenerator(options);
5122
7809
  const fs5 = await import("fs/promises");
5123
7810
  const profileResolver = new ProfileResolver();
5124
7811
  const profileConfig = profileResolver.resolve(options.profile, options.features);
@@ -5164,29 +7851,39 @@ var Scaffold = class {
5164
7851
  }
5165
7852
  }
5166
7853
  /**
5167
- * Create a generator for the specified architecture.
7854
+ * Create a generator for the specified architecture with options.
7855
+ * @param options - Scaffold options including architecture type and module type for DDD
5168
7856
  */
5169
- createGenerator(type) {
7857
+ createGenerator(options) {
5170
7858
  const config = {
5171
7859
  templatesDir: this.templatesDir,
5172
7860
  verbose: this.verbose
5173
7861
  };
5174
- switch (type) {
5175
- case "enterprise-mvc":
5176
- return new EnterpriseMvcGenerator(config);
5177
- case "clean":
5178
- return new CleanArchitectureGenerator(config);
5179
- case "ddd":
5180
- return new DddGenerator(config);
5181
- case "action-domain":
5182
- return new ActionDomainGenerator(config);
5183
- case "satellite":
5184
- return new SatelliteGenerator(config);
5185
- case "standalone-engine":
5186
- return new StandaloneEngineGenerator(config);
5187
- default:
5188
- throw new Error(`Unknown architecture type: ${type}`);
5189
- }
7862
+ const type = typeof options === "string" ? options : options.architecture;
7863
+ const generator = (() => {
7864
+ switch (type) {
7865
+ case "enterprise-mvc":
7866
+ return new EnterpriseMvcGenerator(config);
7867
+ case "clean":
7868
+ return new CleanArchitectureGenerator(config);
7869
+ case "ddd": {
7870
+ const dddGen = new DddGenerator(config);
7871
+ if (typeof options !== "string" && options.dddModuleType) {
7872
+ dddGen.setModuleType(options.dddModuleType);
7873
+ }
7874
+ return dddGen;
7875
+ }
7876
+ case "action-domain":
7877
+ return new ActionDomainGenerator(config);
7878
+ case "satellite":
7879
+ return new SatelliteGenerator(config);
7880
+ case "standalone-engine":
7881
+ return new StandaloneEngineGenerator(config);
7882
+ default:
7883
+ throw new Error(`Unknown architecture type: ${type}`);
7884
+ }
7885
+ })();
7886
+ return generator;
5190
7887
  }
5191
7888
  /**
5192
7889
  * Generate a single module (for DDD bounded context).
@@ -5203,7 +7900,9 @@ var Scaffold = class {
5203
7900
  };
5204
7901
  // Annotate the CommonJS export names for ESM import in node:
5205
7902
  0 && (module.exports = {
7903
+ AdvancedModuleGenerator,
5206
7904
  BaseGenerator,
7905
+ CQRSQueryModuleGenerator,
5207
7906
  CleanArchitectureGenerator,
5208
7907
  DddGenerator,
5209
7908
  DependencyValidator,