nestjs-ddd-cli 2.2.1 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -408
- package/ddd.schema.json +111 -0
- package/dist/commands/aggregate-validator.d.ts +9 -0
- package/dist/commands/aggregate-validator.js +953 -0
- package/dist/commands/aggregate-validator.js.map +1 -0
- package/dist/commands/ai-assist.d.ts +8 -0
- package/dist/commands/ai-assist.js +337 -0
- package/dist/commands/ai-assist.js.map +1 -0
- package/dist/commands/api-contracts.d.ts +9 -0
- package/dist/commands/api-contracts.js +1368 -0
- package/dist/commands/api-contracts.js.map +1 -0
- package/dist/commands/api-docs.d.ts +8 -0
- package/dist/commands/api-docs.js +408 -0
- package/dist/commands/api-docs.js.map +1 -0
- package/dist/commands/api-versioning.d.ts +11 -0
- package/dist/commands/api-versioning.js +643 -0
- package/dist/commands/api-versioning.js.map +1 -0
- package/dist/commands/audit-logging.d.ts +9 -0
- package/dist/commands/audit-logging.js +1129 -0
- package/dist/commands/audit-logging.js.map +1 -0
- package/dist/commands/batch-generate.d.ts +10 -0
- package/dist/commands/batch-generate.js +405 -0
- package/dist/commands/batch-generate.js.map +1 -0
- package/dist/commands/caching-strategies.d.ts +9 -0
- package/dist/commands/caching-strategies.js +874 -0
- package/dist/commands/caching-strategies.js.map +1 -0
- package/dist/commands/code-analyzer.d.ts +42 -0
- package/dist/commands/code-analyzer.js +474 -0
- package/dist/commands/code-analyzer.js.map +1 -0
- package/dist/commands/database-seeding.d.ts +6 -0
- package/dist/commands/database-seeding.js +621 -0
- package/dist/commands/database-seeding.js.map +1 -0
- package/dist/commands/db-optimization.d.ts +7 -0
- package/dist/commands/db-optimization.js +687 -0
- package/dist/commands/db-optimization.js.map +1 -0
- package/dist/commands/dependency-graph.d.ts +6 -0
- package/dist/commands/dependency-graph.js +329 -0
- package/dist/commands/dependency-graph.js.map +1 -0
- package/dist/commands/doctor-enhanced.d.ts +22 -0
- package/dist/commands/doctor-enhanced.js +543 -0
- package/dist/commands/doctor-enhanced.js.map +1 -0
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +151 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/env-manager.d.ts +6 -0
- package/dist/commands/env-manager.js +419 -0
- package/dist/commands/env-manager.js.map +1 -0
- package/dist/commands/event-sourcing-full.d.ts +10 -0
- package/dist/commands/event-sourcing-full.js +1107 -0
- package/dist/commands/event-sourcing-full.js.map +1 -0
- package/dist/commands/feature-flags.d.ts +9 -0
- package/dist/commands/feature-flags.js +824 -0
- package/dist/commands/feature-flags.js.map +1 -0
- package/dist/commands/filter-dsl.d.ts +10 -0
- package/dist/commands/filter-dsl.js +1407 -0
- package/dist/commands/filter-dsl.js.map +1 -0
- package/dist/commands/generate-all.js +485 -32
- package/dist/commands/generate-all.js.map +1 -1
- package/dist/commands/generate-deployment.d.ts +8 -0
- package/dist/commands/generate-deployment.js +746 -0
- package/dist/commands/generate-deployment.js.map +1 -0
- package/dist/commands/generate-domain-service.d.ts +14 -0
- package/dist/commands/generate-domain-service.js +796 -0
- package/dist/commands/generate-domain-service.js.map +1 -0
- package/dist/commands/generate-entity.js +82 -24
- package/dist/commands/generate-entity.js.map +1 -1
- package/dist/commands/generate-from-schema.d.ts +56 -0
- package/dist/commands/generate-from-schema.js +222 -0
- package/dist/commands/generate-from-schema.js.map +1 -0
- package/dist/commands/generate-orchestrator.d.ts +14 -0
- package/dist/commands/generate-orchestrator.js +887 -0
- package/dist/commands/generate-orchestrator.js.map +1 -0
- package/dist/commands/generate-repository.d.ts +14 -0
- package/dist/commands/generate-repository.js +1019 -0
- package/dist/commands/generate-repository.js.map +1 -0
- package/dist/commands/generate-shared.d.ts +4 -0
- package/dist/commands/generate-shared.js +388 -0
- package/dist/commands/generate-shared.js.map +1 -0
- package/dist/commands/generate-value-object.d.ts +32 -0
- package/dist/commands/generate-value-object.js +700 -0
- package/dist/commands/generate-value-object.js.map +1 -0
- package/dist/commands/graphql-subscriptions.d.ts +6 -0
- package/dist/commands/graphql-subscriptions.js +607 -0
- package/dist/commands/graphql-subscriptions.js.map +1 -0
- package/dist/commands/graphql-types.d.ts +5 -0
- package/dist/commands/graphql-types.js +423 -0
- package/dist/commands/graphql-types.js.map +1 -0
- package/dist/commands/health-probes-advanced.d.ts +6 -0
- package/dist/commands/health-probes-advanced.js +655 -0
- package/dist/commands/health-probes-advanced.js.map +1 -0
- package/dist/commands/i18n-setup.d.ts +10 -0
- package/dist/commands/i18n-setup.js +677 -0
- package/dist/commands/i18n-setup.js.map +1 -0
- package/dist/commands/init-config.d.ts +6 -0
- package/dist/commands/init-config.js +370 -0
- package/dist/commands/init-config.js.map +1 -0
- package/dist/commands/init-project.js +56 -6
- package/dist/commands/init-project.js.map +1 -1
- package/dist/commands/interactive-scaffold.d.ts +5 -0
- package/dist/commands/interactive-scaffold.js +271 -0
- package/dist/commands/interactive-scaffold.js.map +1 -0
- package/dist/commands/metrics-prometheus.d.ts +6 -0
- package/dist/commands/metrics-prometheus.js +681 -0
- package/dist/commands/metrics-prometheus.js.map +1 -0
- package/dist/commands/migration-engine.d.ts +6 -0
- package/dist/commands/migration-engine.js +446 -0
- package/dist/commands/migration-engine.js.map +1 -0
- package/dist/commands/migration.d.ts +12 -0
- package/dist/commands/migration.js +484 -0
- package/dist/commands/migration.js.map +1 -0
- package/dist/commands/monorepo.d.ts +8 -0
- package/dist/commands/monorepo.js +483 -0
- package/dist/commands/monorepo.js.map +1 -0
- package/dist/commands/multi-database.d.ts +5 -0
- package/dist/commands/multi-database.js +439 -0
- package/dist/commands/multi-database.js.map +1 -0
- package/dist/commands/observability-tracing.d.ts +10 -0
- package/dist/commands/observability-tracing.js +740 -0
- package/dist/commands/observability-tracing.js.map +1 -0
- package/dist/commands/openapi-export.d.ts +8 -0
- package/dist/commands/openapi-export.js +359 -0
- package/dist/commands/openapi-export.js.map +1 -0
- package/dist/commands/perf-analyzer.d.ts +8 -0
- package/dist/commands/perf-analyzer.js +423 -0
- package/dist/commands/perf-analyzer.js.map +1 -0
- package/dist/commands/rate-limiting.d.ts +10 -0
- package/dist/commands/rate-limiting.js +953 -0
- package/dist/commands/rate-limiting.js.map +1 -0
- package/dist/commands/recipe-plugin.d.ts +56 -0
- package/dist/commands/recipe-plugin.js +315 -0
- package/dist/commands/recipe-plugin.js.map +1 -0
- package/dist/commands/recipe.d.ts +6 -0
- package/dist/commands/recipe.js +3941 -0
- package/dist/commands/recipe.js.map +1 -0
- package/dist/commands/recipes/elasticsearch.recipe.d.ts +1 -0
- package/dist/commands/recipes/elasticsearch.recipe.js +761 -0
- package/dist/commands/recipes/elasticsearch.recipe.js.map +1 -0
- package/dist/commands/recipes/event-sourcing.recipe.d.ts +1 -0
- package/dist/commands/recipes/event-sourcing.recipe.js +889 -0
- package/dist/commands/recipes/event-sourcing.recipe.js.map +1 -0
- package/dist/commands/recipes/index.d.ts +7 -0
- package/dist/commands/recipes/index.js +24 -0
- package/dist/commands/recipes/index.js.map +1 -0
- package/dist/commands/recipes/message-queue.recipe.d.ts +1 -0
- package/dist/commands/recipes/message-queue.recipe.js +706 -0
- package/dist/commands/recipes/message-queue.recipe.js.map +1 -0
- package/dist/commands/recipes/middleware.recipe.d.ts +1 -0
- package/dist/commands/recipes/middleware.recipe.js +383 -0
- package/dist/commands/recipes/middleware.recipe.js.map +1 -0
- package/dist/commands/recipes/multi-tenancy.recipe.d.ts +1 -0
- package/dist/commands/recipes/multi-tenancy.recipe.js +520 -0
- package/dist/commands/recipes/multi-tenancy.recipe.js.map +1 -0
- package/dist/commands/recipes/oauth2.recipe.d.ts +1 -0
- package/dist/commands/recipes/oauth2.recipe.js +472 -0
- package/dist/commands/recipes/oauth2.recipe.js.map +1 -0
- package/dist/commands/recipes/websocket.recipe.d.ts +1 -0
- package/dist/commands/recipes/websocket.recipe.js +453 -0
- package/dist/commands/recipes/websocket.recipe.js.map +1 -0
- package/dist/commands/resilience-patterns.d.ts +13 -0
- package/dist/commands/resilience-patterns.js +1029 -0
- package/dist/commands/resilience-patterns.js.map +1 -0
- package/dist/commands/security-patterns.d.ts +11 -0
- package/dist/commands/security-patterns.js +2233 -0
- package/dist/commands/security-patterns.js.map +1 -0
- package/dist/commands/template-debug.d.ts +27 -0
- package/dist/commands/template-debug.js +388 -0
- package/dist/commands/template-debug.js.map +1 -0
- package/dist/commands/test-factory-full.d.ts +9 -0
- package/dist/commands/test-factory-full.js +1570 -0
- package/dist/commands/test-factory-full.js.map +1 -0
- package/dist/commands/test-scaffold.d.ts +7 -0
- package/dist/commands/test-scaffold.js +621 -0
- package/dist/commands/test-scaffold.js.map +1 -0
- package/dist/index.js +1088 -0
- package/dist/index.js.map +1 -1
- package/dist/templates/ai-context/CLAUDE.md.hbs +158 -0
- package/dist/templates/ai-context/conventions.md.hbs +154 -0
- package/dist/templates/command/create-command.hbs +6 -14
- package/dist/templates/command/delete-command.hbs +19 -0
- package/dist/templates/command/update-command.hbs +24 -0
- package/dist/templates/controller/controller.hbs +64 -17
- package/dist/templates/dto/create-dto.hbs +29 -5
- package/dist/templates/dto/filter-dto.hbs +52 -0
- package/dist/templates/dto/filter-query.dto.hbs +148 -0
- package/dist/templates/dto/paginated-response.dto.hbs +29 -0
- package/dist/templates/dto/pagination-query.dto.hbs +30 -0
- package/dist/templates/dto/response-dto.hbs +38 -0
- package/dist/templates/dto/update-dto.hbs +11 -0
- package/dist/templates/entity/entity.hbs +32 -1
- package/dist/templates/event/domain-event.hbs +33 -7
- package/dist/templates/event/event-handler.hbs +40 -0
- package/dist/templates/exception/base-exceptions.hbs +69 -0
- package/dist/templates/exception/entity-not-found.exception.hbs +7 -0
- package/dist/templates/mapper/mapper.hbs +49 -24
- package/dist/templates/module/module.hbs +34 -10
- package/dist/templates/orm-entity/orm-entity.hbs +63 -12
- package/dist/templates/prisma/prisma-mapper.hbs +71 -0
- package/dist/templates/prisma/prisma-repository.hbs +114 -0
- package/dist/templates/prisma/prisma-schema.hbs +20 -0
- package/dist/templates/prisma/prisma-service.hbs +51 -0
- package/dist/templates/query/get-all.query.hbs +50 -0
- package/dist/templates/query/get-by-id.query.hbs +31 -0
- package/dist/templates/repository/repository.hbs +55 -13
- package/dist/templates/resolver/graphql-input.hbs +54 -0
- package/dist/templates/resolver/graphql-type.hbs +58 -0
- package/dist/templates/resolver/pagination-args.hbs +33 -0
- package/dist/templates/resolver/resolver.hbs +62 -0
- package/dist/templates/shared/prisma-query-builder.util.hbs +189 -0
- package/dist/templates/shared/query-builder.util.hbs +218 -0
- package/dist/templates/test/controller.spec.hbs +124 -0
- package/dist/templates/test/repository.spec.hbs +158 -0
- package/dist/templates/test/usecase.spec.hbs +116 -0
- package/dist/templates/usecase/create-usecase.hbs +19 -7
- package/dist/templates/usecase/delete-usecase.hbs +17 -0
- package/dist/templates/usecase/update-usecase.hbs +31 -0
- package/dist/utils/config.utils.d.ts +45 -0
- package/dist/utils/config.utils.js +211 -0
- package/dist/utils/config.utils.js.map +1 -0
- package/dist/utils/error.utils.d.ts +145 -0
- package/dist/utils/error.utils.js +422 -0
- package/dist/utils/error.utils.js.map +1 -0
- package/dist/utils/field.utils.d.ts +54 -0
- package/dist/utils/field.utils.js +389 -0
- package/dist/utils/field.utils.js.map +1 -0
- package/dist/utils/file.utils.d.ts +19 -8
- package/dist/utils/file.utils.js +135 -4
- package/dist/utils/file.utils.js.map +1 -1
- package/dist/utils/idempotency.utils.d.ts +123 -0
- package/dist/utils/idempotency.utils.js +444 -0
- package/dist/utils/idempotency.utils.js.map +1 -0
- package/dist/utils/naming.utils.js +24 -5
- package/dist/utils/naming.utils.js.map +1 -1
- package/dist/utils/performance.utils.d.ts +37 -0
- package/dist/utils/performance.utils.js +158 -0
- package/dist/utils/performance.utils.js.map +1 -0
- package/dist/utils/relation.utils.d.ts +92 -0
- package/dist/utils/relation.utils.js +388 -0
- package/dist/utils/relation.utils.js.map +1 -0
- package/dist/utils/rollback.utils.d.ts +49 -0
- package/dist/utils/rollback.utils.js +306 -0
- package/dist/utils/rollback.utils.js.map +1 -0
- package/dist/utils/schema.utils.d.ts +123 -0
- package/dist/utils/schema.utils.js +419 -0
- package/dist/utils/schema.utils.js.map +1 -0
- package/dist/utils/security.utils.d.ts +57 -0
- package/dist/utils/security.utils.js +315 -0
- package/dist/utils/security.utils.js.map +1 -0
- package/dist/utils/template-engine.utils.d.ts +80 -0
- package/dist/utils/template-engine.utils.js +463 -0
- package/dist/utils/template-engine.utils.js.map +1 -0
- package/dist/utils/validation-registry.utils.d.ts +160 -0
- package/dist/utils/validation-registry.utils.js +526 -0
- package/dist/utils/validation-registry.utils.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Application Layer Use Case Orchestrator Generator
|
|
4
|
+
* Generates standardized use case handlers with transaction management
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.generateOrchestrator = generateOrchestrator;
|
|
44
|
+
exports.setupOrchestratorInfrastructure = setupOrchestratorInfrastructure;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
48
|
+
async function generateOrchestrator(name, basePath, options = {}) {
|
|
49
|
+
console.log(chalk_1.default.bold.blue('\n🎭 Generating Use Case Orchestrator\n'));
|
|
50
|
+
const moduleName = options.module || 'shared';
|
|
51
|
+
const orchestratorType = options.type || 'command';
|
|
52
|
+
const orchestratorPath = path.join(basePath, 'src', moduleName, 'application', 'orchestrators');
|
|
53
|
+
if (!fs.existsSync(orchestratorPath)) {
|
|
54
|
+
fs.mkdirSync(orchestratorPath, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
let content;
|
|
57
|
+
switch (orchestratorType) {
|
|
58
|
+
case 'query':
|
|
59
|
+
content = generateQueryOrchestrator(name);
|
|
60
|
+
break;
|
|
61
|
+
case 'saga':
|
|
62
|
+
content = generateSagaOrchestrator(name);
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
content = generateCommandOrchestrator(name);
|
|
66
|
+
}
|
|
67
|
+
const fileName = `${toKebabCase(name)}.orchestrator.ts`;
|
|
68
|
+
const filePath = path.join(orchestratorPath, fileName);
|
|
69
|
+
fs.writeFileSync(filePath, content);
|
|
70
|
+
console.log(chalk_1.default.green(` ✓ Created ${filePath}`));
|
|
71
|
+
console.log(chalk_1.default.bold.green('\n✅ Orchestrator generated successfully!\n'));
|
|
72
|
+
}
|
|
73
|
+
function generateCommandOrchestrator(name) {
|
|
74
|
+
const className = toPascalCase(name);
|
|
75
|
+
return `import { Injectable, Logger } from '@nestjs/common';
|
|
76
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
77
|
+
import { DataSource } from 'typeorm';
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* ${className} Command Orchestrator
|
|
81
|
+
* Handles command execution with transaction management and event publishing
|
|
82
|
+
*/
|
|
83
|
+
@Injectable()
|
|
84
|
+
export class ${className}Orchestrator {
|
|
85
|
+
private readonly logger = new Logger(${className}Orchestrator.name);
|
|
86
|
+
|
|
87
|
+
constructor(
|
|
88
|
+
private readonly dataSource: DataSource,
|
|
89
|
+
private readonly eventEmitter: EventEmitter2,
|
|
90
|
+
) {}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Execute the ${name} command
|
|
94
|
+
*/
|
|
95
|
+
async execute(command: ${className}Command): Promise<${className}Result> {
|
|
96
|
+
const correlationId = this.generateCorrelationId();
|
|
97
|
+
|
|
98
|
+
this.logger.log(\`Executing ${name} command [\${correlationId}]\`);
|
|
99
|
+
|
|
100
|
+
// Validate command
|
|
101
|
+
const validationResult = await this.validate(command);
|
|
102
|
+
if (!validationResult.valid) {
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
error: {
|
|
106
|
+
code: 'VALIDATION_ERROR',
|
|
107
|
+
message: validationResult.message,
|
|
108
|
+
details: validationResult.errors,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Execute in transaction
|
|
114
|
+
const queryRunner = this.dataSource.createQueryRunner();
|
|
115
|
+
await queryRunner.connect();
|
|
116
|
+
await queryRunner.startTransaction();
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// Pre-execution hooks
|
|
120
|
+
await this.beforeExecute(command, correlationId);
|
|
121
|
+
|
|
122
|
+
// Execute command logic
|
|
123
|
+
const result = await this.executeCommand(command, queryRunner);
|
|
124
|
+
|
|
125
|
+
// Commit transaction
|
|
126
|
+
await queryRunner.commitTransaction();
|
|
127
|
+
|
|
128
|
+
// Post-execution hooks
|
|
129
|
+
await this.afterExecute(command, result, correlationId);
|
|
130
|
+
|
|
131
|
+
// Publish domain events
|
|
132
|
+
await this.publishEvents(result.events || [], correlationId);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
data: result.data,
|
|
137
|
+
correlationId,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
await queryRunner.rollbackTransaction();
|
|
142
|
+
|
|
143
|
+
this.logger.error(\`Command failed [\${correlationId}]: \${(error as Error).message}\`);
|
|
144
|
+
|
|
145
|
+
// Publish failure event
|
|
146
|
+
this.eventEmitter.emit('command.failed', {
|
|
147
|
+
command: '${name}',
|
|
148
|
+
correlationId,
|
|
149
|
+
error: (error as Error).message,
|
|
150
|
+
timestamp: new Date(),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
error: {
|
|
156
|
+
code: 'EXECUTION_ERROR',
|
|
157
|
+
message: (error as Error).message,
|
|
158
|
+
},
|
|
159
|
+
correlationId,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
} finally {
|
|
163
|
+
await queryRunner.release();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async validate(command: ${className}Command): Promise<ValidationResult> {
|
|
168
|
+
// Add validation logic
|
|
169
|
+
return { valid: true };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async beforeExecute(command: ${className}Command, correlationId: string): Promise<void> {
|
|
173
|
+
this.eventEmitter.emit('command.executing', {
|
|
174
|
+
command: '${name}',
|
|
175
|
+
correlationId,
|
|
176
|
+
timestamp: new Date(),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async executeCommand(
|
|
181
|
+
command: ${className}Command,
|
|
182
|
+
queryRunner: any,
|
|
183
|
+
): Promise<ExecutionResult> {
|
|
184
|
+
// Implement command execution logic
|
|
185
|
+
return {
|
|
186
|
+
data: {},
|
|
187
|
+
events: [],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async afterExecute(
|
|
192
|
+
command: ${className}Command,
|
|
193
|
+
result: ExecutionResult,
|
|
194
|
+
correlationId: string,
|
|
195
|
+
): Promise<void> {
|
|
196
|
+
this.eventEmitter.emit('command.executed', {
|
|
197
|
+
command: '${name}',
|
|
198
|
+
correlationId,
|
|
199
|
+
result: result.data,
|
|
200
|
+
timestamp: new Date(),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private async publishEvents(events: DomainEvent[], correlationId: string): Promise<void> {
|
|
205
|
+
for (const event of events) {
|
|
206
|
+
this.eventEmitter.emit(event.type, {
|
|
207
|
+
...event.payload,
|
|
208
|
+
correlationId,
|
|
209
|
+
timestamp: new Date(),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private generateCorrelationId(): string {
|
|
215
|
+
return \`cmd_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Command input
|
|
221
|
+
*/
|
|
222
|
+
export interface ${className}Command {
|
|
223
|
+
[key: string]: any;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Command result
|
|
228
|
+
*/
|
|
229
|
+
export interface ${className}Result {
|
|
230
|
+
success: boolean;
|
|
231
|
+
data?: any;
|
|
232
|
+
error?: {
|
|
233
|
+
code: string;
|
|
234
|
+
message: string;
|
|
235
|
+
details?: any;
|
|
236
|
+
};
|
|
237
|
+
correlationId?: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Validation result
|
|
242
|
+
*/
|
|
243
|
+
interface ValidationResult {
|
|
244
|
+
valid: boolean;
|
|
245
|
+
message?: string;
|
|
246
|
+
errors?: Record<string, string[]>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Execution result
|
|
251
|
+
*/
|
|
252
|
+
interface ExecutionResult {
|
|
253
|
+
data: any;
|
|
254
|
+
events?: DomainEvent[];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Domain event
|
|
259
|
+
*/
|
|
260
|
+
interface DomainEvent {
|
|
261
|
+
type: string;
|
|
262
|
+
payload: any;
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
function generateQueryOrchestrator(name) {
|
|
267
|
+
const className = toPascalCase(name);
|
|
268
|
+
return `import { Injectable, Logger } from '@nestjs/common';
|
|
269
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* ${className} Query Orchestrator
|
|
273
|
+
* Handles query execution with caching and optimization
|
|
274
|
+
*/
|
|
275
|
+
@Injectable()
|
|
276
|
+
export class ${className}QueryOrchestrator {
|
|
277
|
+
private readonly logger = new Logger(${className}QueryOrchestrator.name);
|
|
278
|
+
private readonly cache = new Map<string, CacheEntry>();
|
|
279
|
+
|
|
280
|
+
constructor(
|
|
281
|
+
private readonly eventEmitter: EventEmitter2,
|
|
282
|
+
) {}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Execute the ${name} query
|
|
286
|
+
*/
|
|
287
|
+
async execute(query: ${className}Query): Promise<${className}QueryResult> {
|
|
288
|
+
const cacheKey = this.generateCacheKey(query);
|
|
289
|
+
|
|
290
|
+
// Check cache
|
|
291
|
+
if (query.useCache !== false) {
|
|
292
|
+
const cached = this.getFromCache(cacheKey);
|
|
293
|
+
if (cached) {
|
|
294
|
+
this.logger.debug(\`Cache hit for query: \${cacheKey}\`);
|
|
295
|
+
return {
|
|
296
|
+
success: true,
|
|
297
|
+
data: cached,
|
|
298
|
+
fromCache: true,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
// Execute query
|
|
305
|
+
const result = await this.executeQuery(query);
|
|
306
|
+
|
|
307
|
+
// Cache result
|
|
308
|
+
if (query.useCache !== false && result) {
|
|
309
|
+
this.setCache(cacheKey, result, query.cacheTtl);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
success: true,
|
|
314
|
+
data: result,
|
|
315
|
+
fromCache: false,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
} catch (error) {
|
|
319
|
+
this.logger.error(\`Query failed: \${(error as Error).message}\`);
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
success: false,
|
|
323
|
+
error: {
|
|
324
|
+
code: 'QUERY_ERROR',
|
|
325
|
+
message: (error as Error).message,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Execute paginated query
|
|
333
|
+
*/
|
|
334
|
+
async executePaginated(
|
|
335
|
+
query: ${className}Query,
|
|
336
|
+
pagination: PaginationParams,
|
|
337
|
+
): Promise<PaginatedResult<any>> {
|
|
338
|
+
const { page = 1, pageSize = 10 } = pagination;
|
|
339
|
+
|
|
340
|
+
const result = await this.executeQuery({
|
|
341
|
+
...query,
|
|
342
|
+
skip: (page - 1) * pageSize,
|
|
343
|
+
take: pageSize,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const total = await this.count(query);
|
|
347
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
items: result || [],
|
|
351
|
+
total,
|
|
352
|
+
page,
|
|
353
|
+
pageSize,
|
|
354
|
+
totalPages,
|
|
355
|
+
hasNext: page < totalPages,
|
|
356
|
+
hasPrev: page > 1,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private async executeQuery(query: ${className}Query): Promise<any> {
|
|
361
|
+
// Implement query execution logic
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private async count(query: ${className}Query): Promise<number> {
|
|
366
|
+
// Implement count logic
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private generateCacheKey(query: ${className}Query): string {
|
|
371
|
+
return \`${toKebabCase(name)}:\${JSON.stringify(query)}\`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private getFromCache(key: string): any | null {
|
|
375
|
+
const entry = this.cache.get(key);
|
|
376
|
+
if (!entry) return null;
|
|
377
|
+
|
|
378
|
+
if (Date.now() > entry.expiresAt) {
|
|
379
|
+
this.cache.delete(key);
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return entry.data;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private setCache(key: string, data: any, ttl: number = 60000): void {
|
|
387
|
+
this.cache.set(key, {
|
|
388
|
+
data,
|
|
389
|
+
expiresAt: Date.now() + ttl,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Invalidate cache
|
|
395
|
+
*/
|
|
396
|
+
invalidateCache(pattern?: string): void {
|
|
397
|
+
if (pattern) {
|
|
398
|
+
for (const key of this.cache.keys()) {
|
|
399
|
+
if (key.includes(pattern)) {
|
|
400
|
+
this.cache.delete(key);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
this.cache.clear();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Query input
|
|
411
|
+
*/
|
|
412
|
+
export interface ${className}Query {
|
|
413
|
+
[key: string]: any;
|
|
414
|
+
useCache?: boolean;
|
|
415
|
+
cacheTtl?: number;
|
|
416
|
+
skip?: number;
|
|
417
|
+
take?: number;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Query result
|
|
422
|
+
*/
|
|
423
|
+
export interface ${className}QueryResult {
|
|
424
|
+
success: boolean;
|
|
425
|
+
data?: any;
|
|
426
|
+
fromCache?: boolean;
|
|
427
|
+
error?: {
|
|
428
|
+
code: string;
|
|
429
|
+
message: string;
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Cache entry
|
|
435
|
+
*/
|
|
436
|
+
interface CacheEntry {
|
|
437
|
+
data: any;
|
|
438
|
+
expiresAt: number;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Pagination params
|
|
443
|
+
*/
|
|
444
|
+
interface PaginationParams {
|
|
445
|
+
page?: number;
|
|
446
|
+
pageSize?: number;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Paginated result
|
|
451
|
+
*/
|
|
452
|
+
interface PaginatedResult<T> {
|
|
453
|
+
items: T[];
|
|
454
|
+
total: number;
|
|
455
|
+
page: number;
|
|
456
|
+
pageSize: number;
|
|
457
|
+
totalPages: number;
|
|
458
|
+
hasNext: boolean;
|
|
459
|
+
hasPrev: boolean;
|
|
460
|
+
}
|
|
461
|
+
`;
|
|
462
|
+
}
|
|
463
|
+
function generateSagaOrchestrator(name) {
|
|
464
|
+
const className = toPascalCase(name);
|
|
465
|
+
return `import { Injectable, Logger } from '@nestjs/common';
|
|
466
|
+
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* ${className} Saga Orchestrator
|
|
470
|
+
* Coordinates long-running business processes
|
|
471
|
+
*/
|
|
472
|
+
@Injectable()
|
|
473
|
+
export class ${className}SagaOrchestrator {
|
|
474
|
+
private readonly logger = new Logger(${className}SagaOrchestrator.name);
|
|
475
|
+
private readonly sagas = new Map<string, SagaState>();
|
|
476
|
+
|
|
477
|
+
constructor(
|
|
478
|
+
private readonly eventEmitter: EventEmitter2,
|
|
479
|
+
) {}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Start a new saga
|
|
483
|
+
*/
|
|
484
|
+
async start(input: ${className}SagaInput): Promise<string> {
|
|
485
|
+
const sagaId = this.generateSagaId();
|
|
486
|
+
|
|
487
|
+
const state: SagaState = {
|
|
488
|
+
id: sagaId,
|
|
489
|
+
status: 'started',
|
|
490
|
+
input,
|
|
491
|
+
currentStep: 0,
|
|
492
|
+
steps: this.defineSteps(),
|
|
493
|
+
completedSteps: [],
|
|
494
|
+
results: new Map(),
|
|
495
|
+
startedAt: new Date(),
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
this.sagas.set(sagaId, state);
|
|
499
|
+
|
|
500
|
+
this.logger.log(\`Starting saga [\${sagaId}]\`);
|
|
501
|
+
this.eventEmitter.emit('saga.${toKebabCase(name)}.started', { sagaId, input });
|
|
502
|
+
|
|
503
|
+
// Execute first step
|
|
504
|
+
await this.executeNextStep(sagaId);
|
|
505
|
+
|
|
506
|
+
return sagaId;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Handle step completion event
|
|
511
|
+
*/
|
|
512
|
+
@OnEvent('saga.${toKebabCase(name)}.step.completed')
|
|
513
|
+
async onStepCompleted(event: StepCompletedEvent): Promise<void> {
|
|
514
|
+
const state = this.sagas.get(event.sagaId);
|
|
515
|
+
if (!state) return;
|
|
516
|
+
|
|
517
|
+
state.results.set(event.stepName, event.result);
|
|
518
|
+
state.completedSteps.push(event.stepName);
|
|
519
|
+
state.currentStep++;
|
|
520
|
+
|
|
521
|
+
if (state.currentStep >= state.steps.length) {
|
|
522
|
+
await this.completeSaga(event.sagaId);
|
|
523
|
+
} else {
|
|
524
|
+
await this.executeNextStep(event.sagaId);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Handle step failure event
|
|
530
|
+
*/
|
|
531
|
+
@OnEvent('saga.${toKebabCase(name)}.step.failed')
|
|
532
|
+
async onStepFailed(event: StepFailedEvent): Promise<void> {
|
|
533
|
+
const state = this.sagas.get(event.sagaId);
|
|
534
|
+
if (!state) return;
|
|
535
|
+
|
|
536
|
+
this.logger.error(\`Step failed in saga [\${event.sagaId}]: \${event.error}\`);
|
|
537
|
+
|
|
538
|
+
state.status = 'compensating';
|
|
539
|
+
await this.compensate(event.sagaId);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private async executeNextStep(sagaId: string): Promise<void> {
|
|
543
|
+
const state = this.sagas.get(sagaId);
|
|
544
|
+
if (!state) return;
|
|
545
|
+
|
|
546
|
+
const step = state.steps[state.currentStep];
|
|
547
|
+
|
|
548
|
+
this.logger.debug(\`Executing step: \${step.name} [\${sagaId}]\`);
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
const result = await step.execute(state.input, state.results);
|
|
552
|
+
|
|
553
|
+
this.eventEmitter.emit('saga.${toKebabCase(name)}.step.completed', {
|
|
554
|
+
sagaId,
|
|
555
|
+
stepName: step.name,
|
|
556
|
+
result,
|
|
557
|
+
});
|
|
558
|
+
} catch (error) {
|
|
559
|
+
this.eventEmitter.emit('saga.${toKebabCase(name)}.step.failed', {
|
|
560
|
+
sagaId,
|
|
561
|
+
stepName: step.name,
|
|
562
|
+
error: (error as Error).message,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private async compensate(sagaId: string): Promise<void> {
|
|
568
|
+
const state = this.sagas.get(sagaId);
|
|
569
|
+
if (!state) return;
|
|
570
|
+
|
|
571
|
+
this.logger.log(\`Compensating saga [\${sagaId}]\`);
|
|
572
|
+
|
|
573
|
+
for (const stepName of state.completedSteps.reverse()) {
|
|
574
|
+
const step = state.steps.find(s => s.name === stepName);
|
|
575
|
+
if (step?.compensate) {
|
|
576
|
+
try {
|
|
577
|
+
await step.compensate(state.input, state.results);
|
|
578
|
+
this.logger.debug(\`Compensated step: \${stepName}\`);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
this.logger.error(\`Compensation failed for \${stepName}: \${(error as Error).message}\`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
state.status = 'failed';
|
|
586
|
+
state.completedAt = new Date();
|
|
587
|
+
|
|
588
|
+
this.eventEmitter.emit('saga.${toKebabCase(name)}.failed', {
|
|
589
|
+
sagaId,
|
|
590
|
+
completedSteps: state.completedSteps,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private async completeSaga(sagaId: string): Promise<void> {
|
|
595
|
+
const state = this.sagas.get(sagaId);
|
|
596
|
+
if (!state) return;
|
|
597
|
+
|
|
598
|
+
state.status = 'completed';
|
|
599
|
+
state.completedAt = new Date();
|
|
600
|
+
|
|
601
|
+
this.logger.log(\`Saga completed [\${sagaId}]\`);
|
|
602
|
+
|
|
603
|
+
this.eventEmitter.emit('saga.${toKebabCase(name)}.completed', {
|
|
604
|
+
sagaId,
|
|
605
|
+
results: Object.fromEntries(state.results),
|
|
606
|
+
duration: state.completedAt.getTime() - state.startedAt.getTime(),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
private defineSteps(): SagaStep[] {
|
|
611
|
+
return [
|
|
612
|
+
{
|
|
613
|
+
name: 'validate',
|
|
614
|
+
execute: async (input, results) => {
|
|
615
|
+
// Validation logic
|
|
616
|
+
return { validated: true };
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
name: 'process',
|
|
621
|
+
execute: async (input, results) => {
|
|
622
|
+
// Processing logic
|
|
623
|
+
return { processed: true };
|
|
624
|
+
},
|
|
625
|
+
compensate: async (input, results) => {
|
|
626
|
+
// Rollback processing
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
name: 'notify',
|
|
631
|
+
execute: async (input, results) => {
|
|
632
|
+
// Notification logic
|
|
633
|
+
return { notified: true };
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
];
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
private generateSagaId(): string {
|
|
640
|
+
return \`saga_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Get saga state
|
|
645
|
+
*/
|
|
646
|
+
getState(sagaId: string): SagaState | undefined {
|
|
647
|
+
return this.sagas.get(sagaId);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Saga types
|
|
653
|
+
*/
|
|
654
|
+
export interface ${className}SagaInput {
|
|
655
|
+
[key: string]: any;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
interface SagaState {
|
|
659
|
+
id: string;
|
|
660
|
+
status: 'started' | 'running' | 'compensating' | 'completed' | 'failed';
|
|
661
|
+
input: any;
|
|
662
|
+
currentStep: number;
|
|
663
|
+
steps: SagaStep[];
|
|
664
|
+
completedSteps: string[];
|
|
665
|
+
results: Map<string, any>;
|
|
666
|
+
startedAt: Date;
|
|
667
|
+
completedAt?: Date;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
interface SagaStep {
|
|
671
|
+
name: string;
|
|
672
|
+
execute: (input: any, results: Map<string, any>) => Promise<any>;
|
|
673
|
+
compensate?: (input: any, results: Map<string, any>) => Promise<void>;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
interface StepCompletedEvent {
|
|
677
|
+
sagaId: string;
|
|
678
|
+
stepName: string;
|
|
679
|
+
result: any;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
interface StepFailedEvent {
|
|
683
|
+
sagaId: string;
|
|
684
|
+
stepName: string;
|
|
685
|
+
error: string;
|
|
686
|
+
}
|
|
687
|
+
`;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Setup orchestrator infrastructure
|
|
691
|
+
*/
|
|
692
|
+
async function setupOrchestratorInfrastructure(basePath, options = {}) {
|
|
693
|
+
console.log(chalk_1.default.bold.blue('\n🎭 Setting up Orchestrator Infrastructure\n'));
|
|
694
|
+
const sharedPath = path.join(basePath, 'src/shared/application');
|
|
695
|
+
if (!fs.existsSync(sharedPath)) {
|
|
696
|
+
fs.mkdirSync(sharedPath, { recursive: true });
|
|
697
|
+
}
|
|
698
|
+
// Generate base orchestrator
|
|
699
|
+
const baseContent = generateBaseOrchestrator();
|
|
700
|
+
fs.writeFileSync(path.join(sharedPath, 'orchestrator.base.ts'), baseContent);
|
|
701
|
+
console.log(chalk_1.default.green(` ✓ Created base orchestrator`));
|
|
702
|
+
// Generate pipeline pattern
|
|
703
|
+
const pipelineContent = generatePipeline();
|
|
704
|
+
fs.writeFileSync(path.join(sharedPath, 'pipeline.ts'), pipelineContent);
|
|
705
|
+
console.log(chalk_1.default.green(` ✓ Created pipeline pattern`));
|
|
706
|
+
console.log(chalk_1.default.bold.green('\n✅ Orchestrator infrastructure ready!\n'));
|
|
707
|
+
}
|
|
708
|
+
function generateBaseOrchestrator() {
|
|
709
|
+
return `/**
|
|
710
|
+
* Base Orchestrator
|
|
711
|
+
* Foundation for use case orchestrators
|
|
712
|
+
*/
|
|
713
|
+
|
|
714
|
+
import { Logger } from '@nestjs/common';
|
|
715
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
716
|
+
|
|
717
|
+
export abstract class BaseOrchestrator<TInput, TOutput> {
|
|
718
|
+
protected abstract readonly logger: Logger;
|
|
719
|
+
|
|
720
|
+
constructor(protected readonly eventEmitter: EventEmitter2) {}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Execute orchestrated operation
|
|
724
|
+
*/
|
|
725
|
+
abstract execute(input: TInput): Promise<OrchestratorResult<TOutput>>;
|
|
726
|
+
|
|
727
|
+
protected success(data: TOutput, correlationId?: string): OrchestratorResult<TOutput> {
|
|
728
|
+
return {
|
|
729
|
+
success: true,
|
|
730
|
+
data,
|
|
731
|
+
correlationId,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
protected failure(error: OrchestratorError, correlationId?: string): OrchestratorResult<TOutput> {
|
|
736
|
+
return {
|
|
737
|
+
success: false,
|
|
738
|
+
error,
|
|
739
|
+
correlationId,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
protected generateCorrelationId(): string {
|
|
744
|
+
return \`orch_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Orchestrator result
|
|
750
|
+
*/
|
|
751
|
+
export interface OrchestratorResult<T> {
|
|
752
|
+
success: boolean;
|
|
753
|
+
data?: T;
|
|
754
|
+
error?: OrchestratorError;
|
|
755
|
+
correlationId?: string;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Orchestrator error
|
|
760
|
+
*/
|
|
761
|
+
export interface OrchestratorError {
|
|
762
|
+
code: string;
|
|
763
|
+
message: string;
|
|
764
|
+
details?: any;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Orchestrator decorator
|
|
769
|
+
*/
|
|
770
|
+
export function Orchestrator(): ClassDecorator {
|
|
771
|
+
return function (target: Function) {
|
|
772
|
+
Reflect.defineMetadata('isOrchestrator', true, target);
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
`;
|
|
776
|
+
}
|
|
777
|
+
function generatePipeline() {
|
|
778
|
+
return `/**
|
|
779
|
+
* Pipeline Pattern
|
|
780
|
+
* For building request/response pipelines
|
|
781
|
+
*/
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Pipeline step interface
|
|
785
|
+
*/
|
|
786
|
+
export interface PipelineStep<TContext> {
|
|
787
|
+
execute(context: TContext, next: () => Promise<void>): Promise<void>;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Pipeline builder
|
|
792
|
+
*/
|
|
793
|
+
export class Pipeline<TContext> {
|
|
794
|
+
private steps: PipelineStep<TContext>[] = [];
|
|
795
|
+
|
|
796
|
+
use(step: PipelineStep<TContext>): this {
|
|
797
|
+
this.steps.push(step);
|
|
798
|
+
return this;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
async execute(context: TContext): Promise<TContext> {
|
|
802
|
+
let index = 0;
|
|
803
|
+
|
|
804
|
+
const next = async (): Promise<void> => {
|
|
805
|
+
if (index < this.steps.length) {
|
|
806
|
+
const step = this.steps[index++];
|
|
807
|
+
await step.execute(context, next);
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
await next();
|
|
812
|
+
return context;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Create a pipeline
|
|
818
|
+
*/
|
|
819
|
+
export function pipeline<TContext>(): Pipeline<TContext> {
|
|
820
|
+
return new Pipeline<TContext>();
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Common pipeline steps
|
|
825
|
+
*/
|
|
826
|
+
export class LoggingStep<TContext> implements PipelineStep<TContext> {
|
|
827
|
+
constructor(private readonly logger: any) {}
|
|
828
|
+
|
|
829
|
+
async execute(context: TContext, next: () => Promise<void>): Promise<void> {
|
|
830
|
+
const start = Date.now();
|
|
831
|
+
this.logger.log('Pipeline started');
|
|
832
|
+
|
|
833
|
+
await next();
|
|
834
|
+
|
|
835
|
+
this.logger.log(\`Pipeline completed in \${Date.now() - start}ms\`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export class ValidationStep<TContext extends { input: any; errors?: string[] }> implements PipelineStep<TContext> {
|
|
840
|
+
constructor(private readonly validator: (input: any) => string[]) {}
|
|
841
|
+
|
|
842
|
+
async execute(context: TContext, next: () => Promise<void>): Promise<void> {
|
|
843
|
+
const errors = this.validator(context.input);
|
|
844
|
+
|
|
845
|
+
if (errors.length > 0) {
|
|
846
|
+
context.errors = errors;
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
await next();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export class TransactionStep<TContext> implements PipelineStep<TContext> {
|
|
855
|
+
constructor(private readonly dataSource: any) {}
|
|
856
|
+
|
|
857
|
+
async execute(context: TContext, next: () => Promise<void>): Promise<void> {
|
|
858
|
+
const queryRunner = this.dataSource.createQueryRunner();
|
|
859
|
+
await queryRunner.connect();
|
|
860
|
+
await queryRunner.startTransaction();
|
|
861
|
+
|
|
862
|
+
try {
|
|
863
|
+
await next();
|
|
864
|
+
await queryRunner.commitTransaction();
|
|
865
|
+
} catch (error) {
|
|
866
|
+
await queryRunner.rollbackTransaction();
|
|
867
|
+
throw error;
|
|
868
|
+
} finally {
|
|
869
|
+
await queryRunner.release();
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
`;
|
|
874
|
+
}
|
|
875
|
+
// Helper functions
|
|
876
|
+
function toKebabCase(str) {
|
|
877
|
+
return str
|
|
878
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
879
|
+
.replace(/[\s_]+/g, '-')
|
|
880
|
+
.toLowerCase();
|
|
881
|
+
}
|
|
882
|
+
function toPascalCase(str) {
|
|
883
|
+
return str
|
|
884
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
|
|
885
|
+
.replace(/^(.)/, c => c.toUpperCase());
|
|
886
|
+
}
|
|
887
|
+
//# sourceMappingURL=generate-orchestrator.js.map
|