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,796 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Domain Service Orchestration Generator
|
|
4
|
+
* Generates domain services for cross-aggregate operations
|
|
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.generateDomainService = generateDomainService;
|
|
44
|
+
exports.setupDomainServiceInfrastructure = setupDomainServiceInfrastructure;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
48
|
+
async function generateDomainService(name, basePath, options = {}) {
|
|
49
|
+
console.log(chalk_1.default.bold.blue('\n🔧 Generating Domain Service\n'));
|
|
50
|
+
const moduleName = options.module || 'shared';
|
|
51
|
+
const servicePath = path.join(basePath, 'src', moduleName, 'domain', 'services');
|
|
52
|
+
if (!fs.existsSync(servicePath)) {
|
|
53
|
+
fs.mkdirSync(servicePath, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
const serviceType = options.type || 'simple';
|
|
56
|
+
let serviceContent;
|
|
57
|
+
switch (serviceType) {
|
|
58
|
+
case 'workflow':
|
|
59
|
+
serviceContent = generateWorkflowService(name);
|
|
60
|
+
break;
|
|
61
|
+
case 'policy':
|
|
62
|
+
serviceContent = generatePolicyService(name);
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
serviceContent = generateSimpleDomainService(name);
|
|
66
|
+
}
|
|
67
|
+
const serviceFile = path.join(servicePath, `${toKebabCase(name)}.service.ts`);
|
|
68
|
+
fs.writeFileSync(serviceFile, serviceContent);
|
|
69
|
+
console.log(chalk_1.default.green(` ✓ Created ${serviceFile}`));
|
|
70
|
+
console.log(chalk_1.default.bold.green('\n✅ Domain service generated successfully!\n'));
|
|
71
|
+
}
|
|
72
|
+
function generateSimpleDomainService(name) {
|
|
73
|
+
const className = toPascalCase(name);
|
|
74
|
+
return `import { Injectable } from '@nestjs/common';
|
|
75
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* ${className} Domain Service
|
|
79
|
+
* Handles cross-aggregate business logic
|
|
80
|
+
*/
|
|
81
|
+
@Injectable()
|
|
82
|
+
export class ${className}Service {
|
|
83
|
+
constructor(
|
|
84
|
+
private readonly eventEmitter: EventEmitter2,
|
|
85
|
+
) {}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Execute domain operation
|
|
89
|
+
*/
|
|
90
|
+
async execute(params: ${className}Params): Promise<${className}Result> {
|
|
91
|
+
// Validate business rules
|
|
92
|
+
this.validateRules(params);
|
|
93
|
+
|
|
94
|
+
// Perform domain logic
|
|
95
|
+
const result = await this.performOperation(params);
|
|
96
|
+
|
|
97
|
+
// Emit domain events
|
|
98
|
+
this.eventEmitter.emit('${toKebabCase(name)}.completed', {
|
|
99
|
+
params,
|
|
100
|
+
result,
|
|
101
|
+
timestamp: new Date(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private validateRules(params: ${className}Params): void {
|
|
108
|
+
// Add business rule validations
|
|
109
|
+
if (!params) {
|
|
110
|
+
throw new DomainValidationError('Invalid parameters');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async performOperation(params: ${className}Params): Promise<${className}Result> {
|
|
115
|
+
// Implement domain logic
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
data: {},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Service parameters
|
|
125
|
+
*/
|
|
126
|
+
export interface ${className}Params {
|
|
127
|
+
[key: string]: any;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Service result
|
|
132
|
+
*/
|
|
133
|
+
export interface ${className}Result {
|
|
134
|
+
success: boolean;
|
|
135
|
+
data: any;
|
|
136
|
+
error?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Domain validation error
|
|
141
|
+
*/
|
|
142
|
+
export class DomainValidationError extends Error {
|
|
143
|
+
constructor(message: string) {
|
|
144
|
+
super(message);
|
|
145
|
+
this.name = 'DomainValidationError';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
function generateWorkflowService(name) {
|
|
151
|
+
const className = toPascalCase(name);
|
|
152
|
+
return `import { Injectable, Logger } from '@nestjs/common';
|
|
153
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* ${className} Workflow Service
|
|
157
|
+
* Orchestrates multi-step business processes
|
|
158
|
+
*/
|
|
159
|
+
@Injectable()
|
|
160
|
+
export class ${className}WorkflowService {
|
|
161
|
+
private readonly logger = new Logger(${className}WorkflowService.name);
|
|
162
|
+
|
|
163
|
+
constructor(
|
|
164
|
+
private readonly eventEmitter: EventEmitter2,
|
|
165
|
+
) {}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Execute workflow
|
|
169
|
+
*/
|
|
170
|
+
async execute(context: WorkflowContext): Promise<WorkflowResult> {
|
|
171
|
+
const workflow = new ${className}Workflow(context);
|
|
172
|
+
|
|
173
|
+
this.logger.log(\`Starting workflow: \${workflow.id}\`);
|
|
174
|
+
this.eventEmitter.emit('workflow.started', { workflowId: workflow.id, type: '${toKebabCase(name)}' });
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
// Execute workflow steps
|
|
178
|
+
for (const step of workflow.steps) {
|
|
179
|
+
this.logger.debug(\`Executing step: \${step.name}\`);
|
|
180
|
+
|
|
181
|
+
const stepResult = await this.executeStep(step, context);
|
|
182
|
+
|
|
183
|
+
if (!stepResult.success) {
|
|
184
|
+
if (step.required) {
|
|
185
|
+
throw new WorkflowStepError(step.name, stepResult.error!);
|
|
186
|
+
}
|
|
187
|
+
this.logger.warn(\`Optional step failed: \${step.name}\`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
context.results[step.name] = stepResult;
|
|
191
|
+
this.eventEmitter.emit('workflow.step.completed', {
|
|
192
|
+
workflowId: workflow.id,
|
|
193
|
+
step: step.name,
|
|
194
|
+
result: stepResult,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result: WorkflowResult = {
|
|
199
|
+
id: workflow.id,
|
|
200
|
+
status: 'completed',
|
|
201
|
+
results: context.results,
|
|
202
|
+
completedAt: new Date(),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
this.eventEmitter.emit('workflow.completed', result);
|
|
206
|
+
return result;
|
|
207
|
+
|
|
208
|
+
} catch (error) {
|
|
209
|
+
const failedResult: WorkflowResult = {
|
|
210
|
+
id: workflow.id,
|
|
211
|
+
status: 'failed',
|
|
212
|
+
results: context.results,
|
|
213
|
+
error: (error as Error).message,
|
|
214
|
+
completedAt: new Date(),
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
this.eventEmitter.emit('workflow.failed', failedResult);
|
|
218
|
+
|
|
219
|
+
// Execute compensation steps
|
|
220
|
+
await this.compensate(workflow, context);
|
|
221
|
+
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async executeStep(step: WorkflowStep, context: WorkflowContext): Promise<StepResult> {
|
|
227
|
+
const startTime = Date.now();
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const result = await step.handler(context);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
data: result,
|
|
235
|
+
duration: Date.now() - startTime,
|
|
236
|
+
};
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return {
|
|
239
|
+
success: false,
|
|
240
|
+
error: (error as Error).message,
|
|
241
|
+
duration: Date.now() - startTime,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private async compensate(workflow: ${className}Workflow, context: WorkflowContext): Promise<void> {
|
|
247
|
+
this.logger.log(\`Compensating workflow: \${workflow.id}\`);
|
|
248
|
+
|
|
249
|
+
// Execute compensation steps in reverse order
|
|
250
|
+
const completedSteps = Object.keys(context.results);
|
|
251
|
+
|
|
252
|
+
for (const stepName of completedSteps.reverse()) {
|
|
253
|
+
const step = workflow.steps.find(s => s.name === stepName);
|
|
254
|
+
|
|
255
|
+
if (step?.compensate) {
|
|
256
|
+
try {
|
|
257
|
+
await step.compensate(context);
|
|
258
|
+
this.logger.debug(\`Compensated step: \${stepName}\`);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
this.logger.error(\`Compensation failed for step: \${stepName}\`, error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Workflow definition
|
|
269
|
+
*/
|
|
270
|
+
class ${className}Workflow {
|
|
271
|
+
public readonly id: string;
|
|
272
|
+
public readonly steps: WorkflowStep[];
|
|
273
|
+
|
|
274
|
+
constructor(context: WorkflowContext) {
|
|
275
|
+
this.id = this.generateId();
|
|
276
|
+
this.steps = this.defineSteps();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private generateId(): string {
|
|
280
|
+
return \`wf_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private defineSteps(): WorkflowStep[] {
|
|
284
|
+
return [
|
|
285
|
+
{
|
|
286
|
+
name: 'validate',
|
|
287
|
+
required: true,
|
|
288
|
+
handler: async (ctx) => {
|
|
289
|
+
// Validation logic
|
|
290
|
+
return { validated: true };
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: 'process',
|
|
295
|
+
required: true,
|
|
296
|
+
handler: async (ctx) => {
|
|
297
|
+
// Processing logic
|
|
298
|
+
return { processed: true };
|
|
299
|
+
},
|
|
300
|
+
compensate: async (ctx) => {
|
|
301
|
+
// Rollback processing
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: 'notify',
|
|
306
|
+
required: false,
|
|
307
|
+
handler: async (ctx) => {
|
|
308
|
+
// Notification logic
|
|
309
|
+
return { notified: true };
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Workflow types
|
|
318
|
+
*/
|
|
319
|
+
export interface WorkflowContext {
|
|
320
|
+
input: any;
|
|
321
|
+
results: Record<string, StepResult>;
|
|
322
|
+
metadata?: Record<string, any>;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export interface WorkflowStep {
|
|
326
|
+
name: string;
|
|
327
|
+
required: boolean;
|
|
328
|
+
handler: (context: WorkflowContext) => Promise<any>;
|
|
329
|
+
compensate?: (context: WorkflowContext) => Promise<void>;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export interface StepResult {
|
|
333
|
+
success: boolean;
|
|
334
|
+
data?: any;
|
|
335
|
+
error?: string;
|
|
336
|
+
duration: number;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export interface WorkflowResult {
|
|
340
|
+
id: string;
|
|
341
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
342
|
+
results: Record<string, StepResult>;
|
|
343
|
+
error?: string;
|
|
344
|
+
completedAt: Date;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export class WorkflowStepError extends Error {
|
|
348
|
+
constructor(stepName: string, message: string) {
|
|
349
|
+
super(\`Step '\${stepName}' failed: \${message}\`);
|
|
350
|
+
this.name = 'WorkflowStepError';
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
355
|
+
function generatePolicyService(name) {
|
|
356
|
+
const className = toPascalCase(name);
|
|
357
|
+
return `import { Injectable } from '@nestjs/common';
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* ${className} Policy Service
|
|
361
|
+
* Encapsulates business rules and policies
|
|
362
|
+
*/
|
|
363
|
+
@Injectable()
|
|
364
|
+
export class ${className}PolicyService {
|
|
365
|
+
private readonly rules: PolicyRule[] = [];
|
|
366
|
+
|
|
367
|
+
constructor() {
|
|
368
|
+
this.registerRules();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Evaluate all policies for a given context
|
|
373
|
+
*/
|
|
374
|
+
evaluate(context: PolicyContext): PolicyResult {
|
|
375
|
+
const violations: PolicyViolation[] = [];
|
|
376
|
+
const warnings: PolicyWarning[] = [];
|
|
377
|
+
|
|
378
|
+
for (const rule of this.rules) {
|
|
379
|
+
if (!rule.condition(context)) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const result = rule.evaluate(context);
|
|
384
|
+
|
|
385
|
+
if (!result.passed) {
|
|
386
|
+
if (rule.severity === 'error') {
|
|
387
|
+
violations.push({
|
|
388
|
+
rule: rule.name,
|
|
389
|
+
message: result.message || rule.message,
|
|
390
|
+
code: rule.code,
|
|
391
|
+
});
|
|
392
|
+
} else {
|
|
393
|
+
warnings.push({
|
|
394
|
+
rule: rule.name,
|
|
395
|
+
message: result.message || rule.message,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
passed: violations.length === 0,
|
|
403
|
+
violations,
|
|
404
|
+
warnings,
|
|
405
|
+
context,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Register a new policy rule
|
|
411
|
+
*/
|
|
412
|
+
registerRule(rule: PolicyRule): void {
|
|
413
|
+
this.rules.push(rule);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Register default rules
|
|
418
|
+
*/
|
|
419
|
+
private registerRules(): void {
|
|
420
|
+
// Add default policy rules
|
|
421
|
+
this.registerRule({
|
|
422
|
+
name: 'example-rule',
|
|
423
|
+
code: 'POLICY_001',
|
|
424
|
+
message: 'Example policy violation',
|
|
425
|
+
severity: 'error',
|
|
426
|
+
condition: (ctx) => true,
|
|
427
|
+
evaluate: (ctx) => ({
|
|
428
|
+
passed: true,
|
|
429
|
+
}),
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Policy types
|
|
436
|
+
*/
|
|
437
|
+
export interface PolicyRule {
|
|
438
|
+
name: string;
|
|
439
|
+
code: string;
|
|
440
|
+
message: string;
|
|
441
|
+
severity: 'error' | 'warning';
|
|
442
|
+
condition: (context: PolicyContext) => boolean;
|
|
443
|
+
evaluate: (context: PolicyContext) => PolicyEvaluation;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export interface PolicyContext {
|
|
447
|
+
subject: any;
|
|
448
|
+
action: string;
|
|
449
|
+
resource: any;
|
|
450
|
+
environment?: Record<string, any>;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export interface PolicyEvaluation {
|
|
454
|
+
passed: boolean;
|
|
455
|
+
message?: string;
|
|
456
|
+
data?: any;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export interface PolicyViolation {
|
|
460
|
+
rule: string;
|
|
461
|
+
code: string;
|
|
462
|
+
message: string;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export interface PolicyWarning {
|
|
466
|
+
rule: string;
|
|
467
|
+
message: string;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export interface PolicyResult {
|
|
471
|
+
passed: boolean;
|
|
472
|
+
violations: PolicyViolation[];
|
|
473
|
+
warnings: PolicyWarning[];
|
|
474
|
+
context: PolicyContext;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Policy builder for fluent rule creation
|
|
479
|
+
*/
|
|
480
|
+
export class PolicyBuilder {
|
|
481
|
+
private rule: Partial<PolicyRule> = {
|
|
482
|
+
severity: 'error',
|
|
483
|
+
condition: () => true,
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
name(name: string): this {
|
|
487
|
+
this.rule.name = name;
|
|
488
|
+
return this;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
code(code: string): this {
|
|
492
|
+
this.rule.code = code;
|
|
493
|
+
return this;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
message(message: string): this {
|
|
497
|
+
this.rule.message = message;
|
|
498
|
+
return this;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
severity(severity: 'error' | 'warning'): this {
|
|
502
|
+
this.rule.severity = severity;
|
|
503
|
+
return this;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
when(condition: (context: PolicyContext) => boolean): this {
|
|
507
|
+
this.rule.condition = condition;
|
|
508
|
+
return this;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
evaluate(evaluator: (context: PolicyContext) => PolicyEvaluation): this {
|
|
512
|
+
this.rule.evaluate = evaluator;
|
|
513
|
+
return this;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
build(): PolicyRule {
|
|
517
|
+
if (!this.rule.name || !this.rule.code || !this.rule.evaluate) {
|
|
518
|
+
throw new Error('Policy rule is incomplete');
|
|
519
|
+
}
|
|
520
|
+
return this.rule as PolicyRule;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Create a policy rule builder
|
|
526
|
+
*/
|
|
527
|
+
export function policy(): PolicyBuilder {
|
|
528
|
+
return new PolicyBuilder();
|
|
529
|
+
}
|
|
530
|
+
`;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Setup domain service infrastructure
|
|
534
|
+
*/
|
|
535
|
+
async function setupDomainServiceInfrastructure(basePath, options = {}) {
|
|
536
|
+
console.log(chalk_1.default.bold.blue('\n🔧 Setting up Domain Service Infrastructure\n'));
|
|
537
|
+
const sharedPath = path.join(basePath, 'src/shared/domain/services');
|
|
538
|
+
if (!fs.existsSync(sharedPath)) {
|
|
539
|
+
fs.mkdirSync(sharedPath, { recursive: true });
|
|
540
|
+
}
|
|
541
|
+
// Generate base domain service
|
|
542
|
+
const baseServiceContent = generateBaseDomainService();
|
|
543
|
+
fs.writeFileSync(path.join(sharedPath, 'domain-service.base.ts'), baseServiceContent);
|
|
544
|
+
console.log(chalk_1.default.green(` ✓ Created base domain service`));
|
|
545
|
+
// Generate saga pattern
|
|
546
|
+
const sagaContent = generateSagaPattern();
|
|
547
|
+
fs.writeFileSync(path.join(sharedPath, 'saga.ts'), sagaContent);
|
|
548
|
+
console.log(chalk_1.default.green(` ✓ Created saga pattern`));
|
|
549
|
+
console.log(chalk_1.default.bold.green('\n✅ Domain service infrastructure ready!\n'));
|
|
550
|
+
}
|
|
551
|
+
function generateBaseDomainService() {
|
|
552
|
+
return `/**
|
|
553
|
+
* Base Domain Service
|
|
554
|
+
* Foundation for domain services
|
|
555
|
+
*/
|
|
556
|
+
|
|
557
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
558
|
+
|
|
559
|
+
export abstract class BaseDomainService {
|
|
560
|
+
constructor(protected readonly eventEmitter: EventEmitter2) {}
|
|
561
|
+
|
|
562
|
+
protected emit(event: string, payload: any): void {
|
|
563
|
+
this.eventEmitter.emit(event, {
|
|
564
|
+
...payload,
|
|
565
|
+
timestamp: new Date(),
|
|
566
|
+
service: this.constructor.name,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
protected async emitAsync(event: string, payload: any): Promise<void> {
|
|
571
|
+
await this.eventEmitter.emitAsync(event, {
|
|
572
|
+
...payload,
|
|
573
|
+
timestamp: new Date(),
|
|
574
|
+
service: this.constructor.name,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Domain service result type
|
|
581
|
+
*/
|
|
582
|
+
export type DomainResult<T, E = DomainError> =
|
|
583
|
+
| { success: true; data: T }
|
|
584
|
+
| { success: false; error: E };
|
|
585
|
+
|
|
586
|
+
export function success<T>(data: T): DomainResult<T> {
|
|
587
|
+
return { success: true, data };
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function failure<E>(error: E): DomainResult<never, E> {
|
|
591
|
+
return { success: false, error };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Domain error
|
|
596
|
+
*/
|
|
597
|
+
export class DomainError extends Error {
|
|
598
|
+
constructor(
|
|
599
|
+
message: string,
|
|
600
|
+
public readonly code: string,
|
|
601
|
+
public readonly details?: Record<string, any>,
|
|
602
|
+
) {
|
|
603
|
+
super(message);
|
|
604
|
+
this.name = 'DomainError';
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Domain service decorator
|
|
610
|
+
*/
|
|
611
|
+
export function DomainService(): ClassDecorator {
|
|
612
|
+
return function (target: Function) {
|
|
613
|
+
// Mark as domain service
|
|
614
|
+
Reflect.defineMetadata('isDomainService', true, target);
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
`;
|
|
618
|
+
}
|
|
619
|
+
function generateSagaPattern() {
|
|
620
|
+
return `/**
|
|
621
|
+
* Saga Pattern Implementation
|
|
622
|
+
* For distributed transactions across aggregates
|
|
623
|
+
*/
|
|
624
|
+
|
|
625
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
626
|
+
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Saga step definition
|
|
630
|
+
*/
|
|
631
|
+
export interface SagaStep<TData = any> {
|
|
632
|
+
name: string;
|
|
633
|
+
execute: (data: TData) => Promise<any>;
|
|
634
|
+
compensate: (data: TData, result: any) => Promise<void>;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Saga execution context
|
|
639
|
+
*/
|
|
640
|
+
export interface SagaContext<TData = any> {
|
|
641
|
+
id: string;
|
|
642
|
+
data: TData;
|
|
643
|
+
results: Map<string, any>;
|
|
644
|
+
completedSteps: string[];
|
|
645
|
+
status: 'running' | 'completed' | 'compensating' | 'failed';
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Saga orchestrator
|
|
650
|
+
*/
|
|
651
|
+
@Injectable()
|
|
652
|
+
export class SagaOrchestrator {
|
|
653
|
+
private readonly logger = new Logger(SagaOrchestrator.name);
|
|
654
|
+
|
|
655
|
+
constructor(private readonly eventEmitter: EventEmitter2) {}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Execute a saga with compensation support
|
|
659
|
+
*/
|
|
660
|
+
async execute<TData>(
|
|
661
|
+
sagaName: string,
|
|
662
|
+
steps: SagaStep<TData>[],
|
|
663
|
+
data: TData,
|
|
664
|
+
): Promise<SagaContext<TData>> {
|
|
665
|
+
const context: SagaContext<TData> = {
|
|
666
|
+
id: this.generateSagaId(),
|
|
667
|
+
data,
|
|
668
|
+
results: new Map(),
|
|
669
|
+
completedSteps: [],
|
|
670
|
+
status: 'running',
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
this.logger.log(\`Starting saga: \${sagaName} [\${context.id}]\`);
|
|
674
|
+
this.eventEmitter.emit('saga.started', { id: context.id, name: sagaName });
|
|
675
|
+
|
|
676
|
+
try {
|
|
677
|
+
for (const step of steps) {
|
|
678
|
+
this.logger.debug(\`Executing step: \${step.name}\`);
|
|
679
|
+
|
|
680
|
+
const result = await step.execute(data);
|
|
681
|
+
context.results.set(step.name, result);
|
|
682
|
+
context.completedSteps.push(step.name);
|
|
683
|
+
|
|
684
|
+
this.eventEmitter.emit('saga.step.completed', {
|
|
685
|
+
sagaId: context.id,
|
|
686
|
+
step: step.name,
|
|
687
|
+
result,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
context.status = 'completed';
|
|
692
|
+
this.eventEmitter.emit('saga.completed', { id: context.id, name: sagaName });
|
|
693
|
+
|
|
694
|
+
return context;
|
|
695
|
+
|
|
696
|
+
} catch (error) {
|
|
697
|
+
this.logger.error(\`Saga failed at step: \${context.completedSteps[context.completedSteps.length - 1]}\`);
|
|
698
|
+
context.status = 'compensating';
|
|
699
|
+
|
|
700
|
+
await this.compensate(sagaName, steps, context);
|
|
701
|
+
|
|
702
|
+
context.status = 'failed';
|
|
703
|
+
this.eventEmitter.emit('saga.failed', {
|
|
704
|
+
id: context.id,
|
|
705
|
+
name: sagaName,
|
|
706
|
+
error: (error as Error).message,
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
throw error;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private async compensate<TData>(
|
|
714
|
+
sagaName: string,
|
|
715
|
+
steps: SagaStep<TData>[],
|
|
716
|
+
context: SagaContext<TData>,
|
|
717
|
+
): Promise<void> {
|
|
718
|
+
this.logger.log(\`Compensating saga: \${sagaName} [\${context.id}]\`);
|
|
719
|
+
|
|
720
|
+
// Compensate in reverse order
|
|
721
|
+
for (const stepName of context.completedSteps.reverse()) {
|
|
722
|
+
const step = steps.find(s => s.name === stepName);
|
|
723
|
+
|
|
724
|
+
if (step) {
|
|
725
|
+
try {
|
|
726
|
+
const stepResult = context.results.get(stepName);
|
|
727
|
+
await step.compensate(context.data, stepResult);
|
|
728
|
+
|
|
729
|
+
this.logger.debug(\`Compensated step: \${stepName}\`);
|
|
730
|
+
this.eventEmitter.emit('saga.step.compensated', {
|
|
731
|
+
sagaId: context.id,
|
|
732
|
+
step: stepName,
|
|
733
|
+
});
|
|
734
|
+
} catch (compensateError) {
|
|
735
|
+
this.logger.error(\`Compensation failed for step: \${stepName}\`, compensateError);
|
|
736
|
+
// Continue with other compensations
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private generateSagaId(): string {
|
|
743
|
+
return \`saga_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Saga builder for fluent API
|
|
749
|
+
*/
|
|
750
|
+
export class SagaBuilder<TData = any> {
|
|
751
|
+
private steps: SagaStep<TData>[] = [];
|
|
752
|
+
private name: string = '';
|
|
753
|
+
|
|
754
|
+
named(name: string): this {
|
|
755
|
+
this.name = name;
|
|
756
|
+
return this;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
step(
|
|
760
|
+
name: string,
|
|
761
|
+
execute: (data: TData) => Promise<any>,
|
|
762
|
+
compensate: (data: TData, result: any) => Promise<void>,
|
|
763
|
+
): this {
|
|
764
|
+
this.steps.push({ name, execute, compensate });
|
|
765
|
+
return this;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
build(): { name: string; steps: SagaStep<TData>[] } {
|
|
769
|
+
return {
|
|
770
|
+
name: this.name,
|
|
771
|
+
steps: this.steps,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Create a saga builder
|
|
778
|
+
*/
|
|
779
|
+
export function saga<TData = any>(): SagaBuilder<TData> {
|
|
780
|
+
return new SagaBuilder<TData>();
|
|
781
|
+
}
|
|
782
|
+
`;
|
|
783
|
+
}
|
|
784
|
+
// Helper functions
|
|
785
|
+
function toKebabCase(str) {
|
|
786
|
+
return str
|
|
787
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
788
|
+
.replace(/[\s_]+/g, '-')
|
|
789
|
+
.toLowerCase();
|
|
790
|
+
}
|
|
791
|
+
function toPascalCase(str) {
|
|
792
|
+
return str
|
|
793
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
|
|
794
|
+
.replace(/^(.)/, c => c.toUpperCase());
|
|
795
|
+
}
|
|
796
|
+
//# sourceMappingURL=generate-domain-service.js.map
|