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,1019 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Abstract Repository Pattern Generator
|
|
4
|
+
* Generates type-safe repository interfaces and implementations
|
|
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.generateRepository = generateRepository;
|
|
44
|
+
exports.setupRepositoryInfrastructure = setupRepositoryInfrastructure;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
48
|
+
async function generateRepository(entityName, basePath, options = {}) {
|
|
49
|
+
console.log(chalk_1.default.bold.blue('\n📦 Generating Repository Pattern\n'));
|
|
50
|
+
const moduleName = options.module || 'shared';
|
|
51
|
+
const orm = options.orm || 'typeorm';
|
|
52
|
+
const repoPath = path.join(basePath, 'src', moduleName, 'infrastructure', 'repositories');
|
|
53
|
+
if (!fs.existsSync(repoPath)) {
|
|
54
|
+
fs.mkdirSync(repoPath, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
// Generate repository interface
|
|
57
|
+
const interfaceContent = generateRepositoryInterface(entityName);
|
|
58
|
+
const interfaceFile = path.join(repoPath, `${toKebabCase(entityName)}.repository.interface.ts`);
|
|
59
|
+
fs.writeFileSync(interfaceFile, interfaceContent);
|
|
60
|
+
console.log(chalk_1.default.green(` ✓ Created ${interfaceFile}`));
|
|
61
|
+
// Generate implementation
|
|
62
|
+
const implContent = orm === 'prisma'
|
|
63
|
+
? generatePrismaRepository(entityName)
|
|
64
|
+
: generateTypeORMRepository(entityName);
|
|
65
|
+
const implFile = path.join(repoPath, `${toKebabCase(entityName)}.repository.ts`);
|
|
66
|
+
fs.writeFileSync(implFile, implContent);
|
|
67
|
+
console.log(chalk_1.default.green(` ✓ Created ${implFile}`));
|
|
68
|
+
console.log(chalk_1.default.bold.green('\n✅ Repository generated successfully!\n'));
|
|
69
|
+
}
|
|
70
|
+
function generateRepositoryInterface(entityName) {
|
|
71
|
+
const className = toPascalCase(entityName);
|
|
72
|
+
return `import { ${className} } from '@domain/${toKebabCase(entityName)}.entity';
|
|
73
|
+
import { Specification } from '@shared/specifications/specification';
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* ${className} Repository Interface
|
|
77
|
+
* Defines the contract for ${className} persistence operations
|
|
78
|
+
*/
|
|
79
|
+
export interface I${className}Repository {
|
|
80
|
+
/**
|
|
81
|
+
* Find entity by ID
|
|
82
|
+
*/
|
|
83
|
+
findById(id: string): Promise<${className} | null>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Find all entities
|
|
87
|
+
*/
|
|
88
|
+
findAll(): Promise<${className}[]>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Find entities matching specification
|
|
92
|
+
*/
|
|
93
|
+
findBySpec(spec: Specification<${className}>): Promise<${className}[]>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Find one entity matching specification
|
|
97
|
+
*/
|
|
98
|
+
findOneBySpec(spec: Specification<${className}>): Promise<${className} | null>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Count entities matching specification
|
|
102
|
+
*/
|
|
103
|
+
countBySpec(spec: Specification<${className}>): Promise<number>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if any entity matches specification
|
|
107
|
+
*/
|
|
108
|
+
exists(spec: Specification<${className}>): Promise<boolean>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Save entity (create or update)
|
|
112
|
+
*/
|
|
113
|
+
save(entity: ${className}): Promise<${className}>;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Save multiple entities
|
|
117
|
+
*/
|
|
118
|
+
saveMany(entities: ${className}[]): Promise<${className}[]>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Delete entity by ID
|
|
122
|
+
*/
|
|
123
|
+
delete(id: string): Promise<void>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Delete entities matching specification
|
|
127
|
+
*/
|
|
128
|
+
deleteBySpec(spec: Specification<${className}>): Promise<number>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Perform operation in transaction
|
|
132
|
+
*/
|
|
133
|
+
transaction<T>(operation: (repo: I${className}Repository) => Promise<T>): Promise<T>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Repository query options
|
|
138
|
+
*/
|
|
139
|
+
export interface QueryOptions<T> {
|
|
140
|
+
where?: Partial<T>;
|
|
141
|
+
orderBy?: { field: keyof T; direction: 'ASC' | 'DESC' }[];
|
|
142
|
+
skip?: number;
|
|
143
|
+
take?: number;
|
|
144
|
+
relations?: string[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Paginated result
|
|
149
|
+
*/
|
|
150
|
+
export interface PaginatedResult<T> {
|
|
151
|
+
items: T[];
|
|
152
|
+
total: number;
|
|
153
|
+
page: number;
|
|
154
|
+
pageSize: number;
|
|
155
|
+
totalPages: number;
|
|
156
|
+
hasNext: boolean;
|
|
157
|
+
hasPrev: boolean;
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
function generateTypeORMRepository(entityName) {
|
|
162
|
+
const className = toPascalCase(entityName);
|
|
163
|
+
const varName = toCamelCase(entityName);
|
|
164
|
+
return `import { Injectable } from '@nestjs/common';
|
|
165
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
166
|
+
import { Repository, DataSource, EntityManager } from 'typeorm';
|
|
167
|
+
import { ${className} } from '@domain/${toKebabCase(entityName)}.entity';
|
|
168
|
+
import { I${className}Repository, QueryOptions, PaginatedResult } from './${toKebabCase(entityName)}.repository.interface';
|
|
169
|
+
import { Specification } from '@shared/specifications/specification';
|
|
170
|
+
import { SpecificationVisitor } from '@shared/specifications/specification.visitor';
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* TypeORM implementation of ${className} Repository
|
|
174
|
+
*/
|
|
175
|
+
@Injectable()
|
|
176
|
+
export class ${className}Repository implements I${className}Repository {
|
|
177
|
+
constructor(
|
|
178
|
+
@InjectRepository(${className})
|
|
179
|
+
private readonly repository: Repository<${className}>,
|
|
180
|
+
private readonly dataSource: DataSource,
|
|
181
|
+
) {}
|
|
182
|
+
|
|
183
|
+
async findById(id: string): Promise<${className} | null> {
|
|
184
|
+
return this.repository.findOne({ where: { id } as any });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async findAll(): Promise<${className}[]> {
|
|
188
|
+
return this.repository.find();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async findBySpec(spec: Specification<${className}>): Promise<${className}[]> {
|
|
192
|
+
const qb = this.repository.createQueryBuilder('entity');
|
|
193
|
+
const visitor = new TypeORMSpecVisitor(qb);
|
|
194
|
+
spec.accept(visitor);
|
|
195
|
+
return qb.getMany();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async findOneBySpec(spec: Specification<${className}>): Promise<${className} | null> {
|
|
199
|
+
const results = await this.findBySpec(spec);
|
|
200
|
+
return results[0] || null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async countBySpec(spec: Specification<${className}>): Promise<number> {
|
|
204
|
+
const qb = this.repository.createQueryBuilder('entity');
|
|
205
|
+
const visitor = new TypeORMSpecVisitor(qb);
|
|
206
|
+
spec.accept(visitor);
|
|
207
|
+
return qb.getCount();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async exists(spec: Specification<${className}>): Promise<boolean> {
|
|
211
|
+
const count = await this.countBySpec(spec);
|
|
212
|
+
return count > 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async save(entity: ${className}): Promise<${className}> {
|
|
216
|
+
return this.repository.save(entity);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async saveMany(entities: ${className}[]): Promise<${className}[]> {
|
|
220
|
+
return this.repository.save(entities);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async delete(id: string): Promise<void> {
|
|
224
|
+
await this.repository.delete(id);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async deleteBySpec(spec: Specification<${className}>): Promise<number> {
|
|
228
|
+
const entities = await this.findBySpec(spec);
|
|
229
|
+
if (entities.length === 0) return 0;
|
|
230
|
+
|
|
231
|
+
await this.repository.remove(entities);
|
|
232
|
+
return entities.length;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async transaction<T>(operation: (repo: I${className}Repository) => Promise<T>): Promise<T> {
|
|
236
|
+
return this.dataSource.transaction(async (manager: EntityManager) => {
|
|
237
|
+
const transactionalRepo = new TransactionalRepository(
|
|
238
|
+
manager.getRepository(${className}),
|
|
239
|
+
this.dataSource,
|
|
240
|
+
);
|
|
241
|
+
return operation(transactionalRepo);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Find with pagination
|
|
247
|
+
*/
|
|
248
|
+
async findPaginated(
|
|
249
|
+
options: QueryOptions<${className}>,
|
|
250
|
+
page: number = 1,
|
|
251
|
+
pageSize: number = 10,
|
|
252
|
+
): Promise<PaginatedResult<${className}>> {
|
|
253
|
+
const qb = this.repository.createQueryBuilder('entity');
|
|
254
|
+
|
|
255
|
+
if (options.where) {
|
|
256
|
+
Object.entries(options.where).forEach(([key, value]) => {
|
|
257
|
+
qb.andWhere(\`entity.\${key} = :\${key}\`, { [key]: value });
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (options.orderBy) {
|
|
262
|
+
options.orderBy.forEach(({ field, direction }) => {
|
|
263
|
+
qb.addOrderBy(\`entity.\${String(field)}\`, direction);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (options.relations) {
|
|
268
|
+
options.relations.forEach(relation => {
|
|
269
|
+
qb.leftJoinAndSelect(\`entity.\${relation}\`, relation);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const total = await qb.getCount();
|
|
274
|
+
const items = await qb
|
|
275
|
+
.skip((page - 1) * pageSize)
|
|
276
|
+
.take(pageSize)
|
|
277
|
+
.getMany();
|
|
278
|
+
|
|
279
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
items,
|
|
283
|
+
total,
|
|
284
|
+
page,
|
|
285
|
+
pageSize,
|
|
286
|
+
totalPages,
|
|
287
|
+
hasNext: page < totalPages,
|
|
288
|
+
hasPrev: page > 1,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Transactional repository wrapper
|
|
295
|
+
*/
|
|
296
|
+
class TransactionalRepository implements I${className}Repository {
|
|
297
|
+
constructor(
|
|
298
|
+
private readonly repository: Repository<${className}>,
|
|
299
|
+
private readonly dataSource: DataSource,
|
|
300
|
+
) {}
|
|
301
|
+
|
|
302
|
+
async findById(id: string): Promise<${className} | null> {
|
|
303
|
+
return this.repository.findOne({ where: { id } as any });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async findAll(): Promise<${className}[]> {
|
|
307
|
+
return this.repository.find();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async findBySpec(spec: Specification<${className}>): Promise<${className}[]> {
|
|
311
|
+
const qb = this.repository.createQueryBuilder('entity');
|
|
312
|
+
const visitor = new TypeORMSpecVisitor(qb);
|
|
313
|
+
spec.accept(visitor);
|
|
314
|
+
return qb.getMany();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async findOneBySpec(spec: Specification<${className}>): Promise<${className} | null> {
|
|
318
|
+
const results = await this.findBySpec(spec);
|
|
319
|
+
return results[0] || null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async countBySpec(spec: Specification<${className}>): Promise<number> {
|
|
323
|
+
const qb = this.repository.createQueryBuilder('entity');
|
|
324
|
+
const visitor = new TypeORMSpecVisitor(qb);
|
|
325
|
+
spec.accept(visitor);
|
|
326
|
+
return qb.getCount();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async exists(spec: Specification<${className}>): Promise<boolean> {
|
|
330
|
+
const count = await this.countBySpec(spec);
|
|
331
|
+
return count > 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async save(entity: ${className}): Promise<${className}> {
|
|
335
|
+
return this.repository.save(entity);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async saveMany(entities: ${className}[]): Promise<${className}[]> {
|
|
339
|
+
return this.repository.save(entities);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async delete(id: string): Promise<void> {
|
|
343
|
+
await this.repository.delete(id);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async deleteBySpec(spec: Specification<${className}>): Promise<number> {
|
|
347
|
+
const entities = await this.findBySpec(spec);
|
|
348
|
+
if (entities.length === 0) return 0;
|
|
349
|
+
|
|
350
|
+
await this.repository.remove(entities);
|
|
351
|
+
return entities.length;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async transaction<T>(operation: (repo: I${className}Repository) => Promise<T>): Promise<T> {
|
|
355
|
+
return operation(this);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* TypeORM Specification Visitor
|
|
361
|
+
*/
|
|
362
|
+
class TypeORMSpecVisitor<T> implements SpecificationVisitor<T> {
|
|
363
|
+
constructor(private readonly qb: any) {}
|
|
364
|
+
|
|
365
|
+
visitAnd(specs: Specification<T>[]): void {
|
|
366
|
+
specs.forEach(spec => spec.accept(this));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
visitOr(specs: Specification<T>[]): void {
|
|
370
|
+
const conditions = specs.map((spec, i) => {
|
|
371
|
+
const tempVisitor = new TypeORMSpecVisitor(this.qb);
|
|
372
|
+
spec.accept(tempVisitor);
|
|
373
|
+
return \`cond\${i}\`;
|
|
374
|
+
});
|
|
375
|
+
this.qb.orWhere(conditions.join(' OR '));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
visitNot(spec: Specification<T>): void {
|
|
379
|
+
// Implementation depends on the spec type
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
visitProperty(field: string, operator: string, value: any): void {
|
|
383
|
+
const paramName = \`\${field}_\${Date.now()}\`;
|
|
384
|
+
switch (operator) {
|
|
385
|
+
case 'eq':
|
|
386
|
+
this.qb.andWhere(\`entity.\${field} = :\${paramName}\`, { [paramName]: value });
|
|
387
|
+
break;
|
|
388
|
+
case 'neq':
|
|
389
|
+
this.qb.andWhere(\`entity.\${field} != :\${paramName}\`, { [paramName]: value });
|
|
390
|
+
break;
|
|
391
|
+
case 'gt':
|
|
392
|
+
this.qb.andWhere(\`entity.\${field} > :\${paramName}\`, { [paramName]: value });
|
|
393
|
+
break;
|
|
394
|
+
case 'gte':
|
|
395
|
+
this.qb.andWhere(\`entity.\${field} >= :\${paramName}\`, { [paramName]: value });
|
|
396
|
+
break;
|
|
397
|
+
case 'lt':
|
|
398
|
+
this.qb.andWhere(\`entity.\${field} < :\${paramName}\`, { [paramName]: value });
|
|
399
|
+
break;
|
|
400
|
+
case 'lte':
|
|
401
|
+
this.qb.andWhere(\`entity.\${field} <= :\${paramName}\`, { [paramName]: value });
|
|
402
|
+
break;
|
|
403
|
+
case 'like':
|
|
404
|
+
this.qb.andWhere(\`entity.\${field} LIKE :\${paramName}\`, { [paramName]: \`%\${value}%\` });
|
|
405
|
+
break;
|
|
406
|
+
case 'in':
|
|
407
|
+
this.qb.andWhere(\`entity.\${field} IN (:\${paramName})\`, { [paramName]: value });
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
`;
|
|
413
|
+
}
|
|
414
|
+
function generatePrismaRepository(entityName) {
|
|
415
|
+
const className = toPascalCase(entityName);
|
|
416
|
+
const varName = toCamelCase(entityName);
|
|
417
|
+
return `import { Injectable } from '@nestjs/common';
|
|
418
|
+
import { PrismaService } from '@shared/prisma/prisma.service';
|
|
419
|
+
import { ${className} } from '@domain/${toKebabCase(entityName)}.entity';
|
|
420
|
+
import { I${className}Repository, QueryOptions, PaginatedResult } from './${toKebabCase(entityName)}.repository.interface';
|
|
421
|
+
import { Specification } from '@shared/specifications/specification';
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Prisma implementation of ${className} Repository
|
|
425
|
+
*/
|
|
426
|
+
@Injectable()
|
|
427
|
+
export class ${className}Repository implements I${className}Repository {
|
|
428
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
429
|
+
|
|
430
|
+
async findById(id: string): Promise<${className} | null> {
|
|
431
|
+
const result = await this.prisma.${varName}.findUnique({
|
|
432
|
+
where: { id },
|
|
433
|
+
});
|
|
434
|
+
return result ? this.toDomain(result) : null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async findAll(): Promise<${className}[]> {
|
|
438
|
+
const results = await this.prisma.${varName}.findMany();
|
|
439
|
+
return results.map(r => this.toDomain(r));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async findBySpec(spec: Specification<${className}>): Promise<${className}[]> {
|
|
443
|
+
const where = this.specToWhere(spec);
|
|
444
|
+
const results = await this.prisma.${varName}.findMany({ where });
|
|
445
|
+
return results.map(r => this.toDomain(r));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async findOneBySpec(spec: Specification<${className}>): Promise<${className} | null> {
|
|
449
|
+
const where = this.specToWhere(spec);
|
|
450
|
+
const result = await this.prisma.${varName}.findFirst({ where });
|
|
451
|
+
return result ? this.toDomain(result) : null;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async countBySpec(spec: Specification<${className}>): Promise<number> {
|
|
455
|
+
const where = this.specToWhere(spec);
|
|
456
|
+
return this.prisma.${varName}.count({ where });
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async exists(spec: Specification<${className}>): Promise<boolean> {
|
|
460
|
+
const count = await this.countBySpec(spec);
|
|
461
|
+
return count > 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async save(entity: ${className}): Promise<${className}> {
|
|
465
|
+
const data = this.toDatabase(entity);
|
|
466
|
+
const result = await this.prisma.${varName}.upsert({
|
|
467
|
+
where: { id: entity.id },
|
|
468
|
+
create: data,
|
|
469
|
+
update: data,
|
|
470
|
+
});
|
|
471
|
+
return this.toDomain(result);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async saveMany(entities: ${className}[]): Promise<${className}[]> {
|
|
475
|
+
const results = await Promise.all(
|
|
476
|
+
entities.map(entity => this.save(entity))
|
|
477
|
+
);
|
|
478
|
+
return results;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async delete(id: string): Promise<void> {
|
|
482
|
+
await this.prisma.${varName}.delete({ where: { id } });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async deleteBySpec(spec: Specification<${className}>): Promise<number> {
|
|
486
|
+
const where = this.specToWhere(spec);
|
|
487
|
+
const result = await this.prisma.${varName}.deleteMany({ where });
|
|
488
|
+
return result.count;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async transaction<T>(operation: (repo: I${className}Repository) => Promise<T>): Promise<T> {
|
|
492
|
+
return this.prisma.$transaction(async (tx) => {
|
|
493
|
+
const transactionalRepo = new Transactional${className}Repository(tx as any);
|
|
494
|
+
return operation(transactionalRepo);
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async findPaginated(
|
|
499
|
+
options: QueryOptions<${className}>,
|
|
500
|
+
page: number = 1,
|
|
501
|
+
pageSize: number = 10,
|
|
502
|
+
): Promise<PaginatedResult<${className}>> {
|
|
503
|
+
const where = options.where || {};
|
|
504
|
+
const orderBy = options.orderBy?.map(o => ({ [o.field]: o.direction.toLowerCase() })) || [];
|
|
505
|
+
|
|
506
|
+
const [items, total] = await Promise.all([
|
|
507
|
+
this.prisma.${varName}.findMany({
|
|
508
|
+
where,
|
|
509
|
+
orderBy,
|
|
510
|
+
skip: (page - 1) * pageSize,
|
|
511
|
+
take: pageSize,
|
|
512
|
+
include: options.relations?.reduce((acc, r) => ({ ...acc, [r]: true }), {}),
|
|
513
|
+
}),
|
|
514
|
+
this.prisma.${varName}.count({ where }),
|
|
515
|
+
]);
|
|
516
|
+
|
|
517
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
items: items.map(i => this.toDomain(i)),
|
|
521
|
+
total,
|
|
522
|
+
page,
|
|
523
|
+
pageSize,
|
|
524
|
+
totalPages,
|
|
525
|
+
hasNext: page < totalPages,
|
|
526
|
+
hasPrev: page > 1,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private toDomain(data: any): ${className} {
|
|
531
|
+
// Map database record to domain entity
|
|
532
|
+
return new ${className}(data);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private toDatabase(entity: ${className}): any {
|
|
536
|
+
// Map domain entity to database record
|
|
537
|
+
return { ...entity };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private specToWhere(spec: Specification<${className}>): any {
|
|
541
|
+
// Convert specification to Prisma where clause
|
|
542
|
+
return {};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Transactional repository wrapper for Prisma
|
|
548
|
+
*/
|
|
549
|
+
class Transactional${className}Repository implements I${className}Repository {
|
|
550
|
+
constructor(private readonly tx: any) {}
|
|
551
|
+
|
|
552
|
+
// Implement all methods using this.tx instead of prisma
|
|
553
|
+
async findById(id: string): Promise<${className} | null> {
|
|
554
|
+
const result = await this.tx.${varName}.findUnique({ where: { id } });
|
|
555
|
+
return result ? this.toDomain(result) : null;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async findAll(): Promise<${className}[]> {
|
|
559
|
+
const results = await this.tx.${varName}.findMany();
|
|
560
|
+
return results.map((r: any) => this.toDomain(r));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async findBySpec(spec: Specification<${className}>): Promise<${className}[]> {
|
|
564
|
+
return [];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async findOneBySpec(spec: Specification<${className}>): Promise<${className} | null> {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async countBySpec(spec: Specification<${className}>): Promise<number> {
|
|
572
|
+
return 0;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
async exists(spec: Specification<${className}>): Promise<boolean> {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async save(entity: ${className}): Promise<${className}> {
|
|
580
|
+
const data = this.toDatabase(entity);
|
|
581
|
+
const result = await this.tx.${varName}.upsert({
|
|
582
|
+
where: { id: entity.id },
|
|
583
|
+
create: data,
|
|
584
|
+
update: data,
|
|
585
|
+
});
|
|
586
|
+
return this.toDomain(result);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async saveMany(entities: ${className}[]): Promise<${className}[]> {
|
|
590
|
+
return Promise.all(entities.map(e => this.save(e)));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async delete(id: string): Promise<void> {
|
|
594
|
+
await this.tx.${varName}.delete({ where: { id } });
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
async deleteBySpec(spec: Specification<${className}>): Promise<number> {
|
|
598
|
+
return 0;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async transaction<T>(operation: (repo: I${className}Repository) => Promise<T>): Promise<T> {
|
|
602
|
+
return operation(this);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private toDomain(data: any): ${className} {
|
|
606
|
+
return new ${className}(data);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private toDatabase(entity: ${className}): any {
|
|
610
|
+
return { ...entity };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
`;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Setup base repository infrastructure
|
|
617
|
+
*/
|
|
618
|
+
async function setupRepositoryInfrastructure(basePath, options = {}) {
|
|
619
|
+
console.log(chalk_1.default.bold.blue('\n📦 Setting up Repository Infrastructure\n'));
|
|
620
|
+
const sharedPath = path.join(basePath, 'src/shared');
|
|
621
|
+
const specPath = path.join(sharedPath, 'specifications');
|
|
622
|
+
const repoPath = path.join(sharedPath, 'repositories');
|
|
623
|
+
// Create directories
|
|
624
|
+
[specPath, repoPath].forEach(p => {
|
|
625
|
+
if (!fs.existsSync(p)) {
|
|
626
|
+
fs.mkdirSync(p, { recursive: true });
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
// Generate specification pattern
|
|
630
|
+
const specContent = generateSpecificationPattern();
|
|
631
|
+
fs.writeFileSync(path.join(specPath, 'specification.ts'), specContent);
|
|
632
|
+
console.log(chalk_1.default.green(` ✓ Created specification pattern`));
|
|
633
|
+
// Generate base repository
|
|
634
|
+
const baseRepoContent = generateBaseRepository();
|
|
635
|
+
fs.writeFileSync(path.join(repoPath, 'base.repository.ts'), baseRepoContent);
|
|
636
|
+
console.log(chalk_1.default.green(` ✓ Created base repository`));
|
|
637
|
+
// Generate unit of work
|
|
638
|
+
const uowContent = generateUnitOfWork();
|
|
639
|
+
fs.writeFileSync(path.join(repoPath, 'unit-of-work.ts'), uowContent);
|
|
640
|
+
console.log(chalk_1.default.green(` ✓ Created unit of work`));
|
|
641
|
+
console.log(chalk_1.default.bold.green('\n✅ Repository infrastructure ready!\n'));
|
|
642
|
+
}
|
|
643
|
+
function generateSpecificationPattern() {
|
|
644
|
+
return `/**
|
|
645
|
+
* Specification Pattern
|
|
646
|
+
* Type-safe query specifications for repositories
|
|
647
|
+
*/
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Base specification interface
|
|
651
|
+
*/
|
|
652
|
+
export interface Specification<T> {
|
|
653
|
+
isSatisfiedBy(candidate: T): boolean;
|
|
654
|
+
accept(visitor: SpecificationVisitor<T>): void;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Specification visitor interface
|
|
659
|
+
*/
|
|
660
|
+
export interface SpecificationVisitor<T> {
|
|
661
|
+
visitAnd(specs: Specification<T>[]): void;
|
|
662
|
+
visitOr(specs: Specification<T>[]): void;
|
|
663
|
+
visitNot(spec: Specification<T>): void;
|
|
664
|
+
visitProperty(field: string, operator: string, value: any): void;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Base specification class
|
|
669
|
+
*/
|
|
670
|
+
export abstract class BaseSpecification<T> implements Specification<T> {
|
|
671
|
+
abstract isSatisfiedBy(candidate: T): boolean;
|
|
672
|
+
abstract accept(visitor: SpecificationVisitor<T>): void;
|
|
673
|
+
|
|
674
|
+
and(other: Specification<T>): Specification<T> {
|
|
675
|
+
return new AndSpecification([this, other]);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
or(other: Specification<T>): Specification<T> {
|
|
679
|
+
return new OrSpecification([this, other]);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
not(): Specification<T> {
|
|
683
|
+
return new NotSpecification(this);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* AND specification
|
|
689
|
+
*/
|
|
690
|
+
export class AndSpecification<T> extends BaseSpecification<T> {
|
|
691
|
+
constructor(private readonly specs: Specification<T>[]) {
|
|
692
|
+
super();
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
isSatisfiedBy(candidate: T): boolean {
|
|
696
|
+
return this.specs.every(spec => spec.isSatisfiedBy(candidate));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
accept(visitor: SpecificationVisitor<T>): void {
|
|
700
|
+
visitor.visitAnd(this.specs);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* OR specification
|
|
706
|
+
*/
|
|
707
|
+
export class OrSpecification<T> extends BaseSpecification<T> {
|
|
708
|
+
constructor(private readonly specs: Specification<T>[]) {
|
|
709
|
+
super();
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
isSatisfiedBy(candidate: T): boolean {
|
|
713
|
+
return this.specs.some(spec => spec.isSatisfiedBy(candidate));
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
accept(visitor: SpecificationVisitor<T>): void {
|
|
717
|
+
visitor.visitOr(this.specs);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* NOT specification
|
|
723
|
+
*/
|
|
724
|
+
export class NotSpecification<T> extends BaseSpecification<T> {
|
|
725
|
+
constructor(private readonly spec: Specification<T>) {
|
|
726
|
+
super();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
isSatisfiedBy(candidate: T): boolean {
|
|
730
|
+
return !this.spec.isSatisfiedBy(candidate);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
accept(visitor: SpecificationVisitor<T>): void {
|
|
734
|
+
visitor.visitNot(this.spec);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Property specification
|
|
740
|
+
*/
|
|
741
|
+
export class PropertySpecification<T, K extends keyof T> extends BaseSpecification<T> {
|
|
742
|
+
constructor(
|
|
743
|
+
private readonly field: K,
|
|
744
|
+
private readonly operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'in',
|
|
745
|
+
private readonly value: T[K] | T[K][],
|
|
746
|
+
) {
|
|
747
|
+
super();
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
isSatisfiedBy(candidate: T): boolean {
|
|
751
|
+
const fieldValue = candidate[this.field];
|
|
752
|
+
|
|
753
|
+
switch (this.operator) {
|
|
754
|
+
case 'eq':
|
|
755
|
+
return fieldValue === this.value;
|
|
756
|
+
case 'neq':
|
|
757
|
+
return fieldValue !== this.value;
|
|
758
|
+
case 'gt':
|
|
759
|
+
return fieldValue > (this.value as T[K]);
|
|
760
|
+
case 'gte':
|
|
761
|
+
return fieldValue >= (this.value as T[K]);
|
|
762
|
+
case 'lt':
|
|
763
|
+
return fieldValue < (this.value as T[K]);
|
|
764
|
+
case 'lte':
|
|
765
|
+
return fieldValue <= (this.value as T[K]);
|
|
766
|
+
case 'like':
|
|
767
|
+
return String(fieldValue).includes(String(this.value));
|
|
768
|
+
case 'in':
|
|
769
|
+
return (this.value as T[K][]).includes(fieldValue);
|
|
770
|
+
default:
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
accept(visitor: SpecificationVisitor<T>): void {
|
|
776
|
+
visitor.visitProperty(String(this.field), this.operator, this.value);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Specification builder for fluent API
|
|
782
|
+
*/
|
|
783
|
+
export class SpecificationBuilder<T> {
|
|
784
|
+
private specs: Specification<T>[] = [];
|
|
785
|
+
|
|
786
|
+
where<K extends keyof T>(field: K, operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'in', value: T[K] | T[K][]): this {
|
|
787
|
+
this.specs.push(new PropertySpecification(field, operator, value));
|
|
788
|
+
return this;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
equals<K extends keyof T>(field: K, value: T[K]): this {
|
|
792
|
+
return this.where(field, 'eq', value);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
notEquals<K extends keyof T>(field: K, value: T[K]): this {
|
|
796
|
+
return this.where(field, 'neq', value);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
greaterThan<K extends keyof T>(field: K, value: T[K]): this {
|
|
800
|
+
return this.where(field, 'gt', value);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
lessThan<K extends keyof T>(field: K, value: T[K]): this {
|
|
804
|
+
return this.where(field, 'lt', value);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
contains<K extends keyof T>(field: K, value: string): this {
|
|
808
|
+
return this.where(field, 'like', value as unknown as T[K]);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
in<K extends keyof T>(field: K, values: T[K][]): this {
|
|
812
|
+
return this.where(field, 'in', values);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
build(): Specification<T> {
|
|
816
|
+
if (this.specs.length === 0) {
|
|
817
|
+
return new TrueSpecification();
|
|
818
|
+
}
|
|
819
|
+
if (this.specs.length === 1) {
|
|
820
|
+
return this.specs[0];
|
|
821
|
+
}
|
|
822
|
+
return new AndSpecification(this.specs);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Always true specification
|
|
828
|
+
*/
|
|
829
|
+
export class TrueSpecification<T> extends BaseSpecification<T> {
|
|
830
|
+
isSatisfiedBy(_candidate: T): boolean {
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
accept(_visitor: SpecificationVisitor<T>): void {
|
|
835
|
+
// No-op
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Create a specification builder
|
|
841
|
+
*/
|
|
842
|
+
export function spec<T>(): SpecificationBuilder<T> {
|
|
843
|
+
return new SpecificationBuilder<T>();
|
|
844
|
+
}
|
|
845
|
+
`;
|
|
846
|
+
}
|
|
847
|
+
function generateBaseRepository() {
|
|
848
|
+
return `/**
|
|
849
|
+
* Base Repository Interface
|
|
850
|
+
* Generic repository contract for all entities
|
|
851
|
+
*/
|
|
852
|
+
|
|
853
|
+
import { Specification } from '../specifications/specification';
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Generic repository interface
|
|
857
|
+
*/
|
|
858
|
+
export interface IRepository<T, ID = string> {
|
|
859
|
+
findById(id: ID): Promise<T | null>;
|
|
860
|
+
findAll(): Promise<T[]>;
|
|
861
|
+
findBySpec(spec: Specification<T>): Promise<T[]>;
|
|
862
|
+
findOneBySpec(spec: Specification<T>): Promise<T | null>;
|
|
863
|
+
countBySpec(spec: Specification<T>): Promise<number>;
|
|
864
|
+
exists(spec: Specification<T>): Promise<boolean>;
|
|
865
|
+
save(entity: T): Promise<T>;
|
|
866
|
+
saveMany(entities: T[]): Promise<T[]>;
|
|
867
|
+
delete(id: ID): Promise<void>;
|
|
868
|
+
deleteBySpec(spec: Specification<T>): Promise<number>;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Read-only repository interface
|
|
873
|
+
*/
|
|
874
|
+
export interface IReadRepository<T, ID = string> {
|
|
875
|
+
findById(id: ID): Promise<T | null>;
|
|
876
|
+
findAll(): Promise<T[]>;
|
|
877
|
+
findBySpec(spec: Specification<T>): Promise<T[]>;
|
|
878
|
+
findOneBySpec(spec: Specification<T>): Promise<T | null>;
|
|
879
|
+
countBySpec(spec: Specification<T>): Promise<number>;
|
|
880
|
+
exists(spec: Specification<T>): Promise<boolean>;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Write-only repository interface
|
|
885
|
+
*/
|
|
886
|
+
export interface IWriteRepository<T, ID = string> {
|
|
887
|
+
save(entity: T): Promise<T>;
|
|
888
|
+
saveMany(entities: T[]): Promise<T[]>;
|
|
889
|
+
delete(id: ID): Promise<void>;
|
|
890
|
+
deleteBySpec(spec: Specification<T>): Promise<number>;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Aggregate repository with event publishing
|
|
895
|
+
*/
|
|
896
|
+
export interface IAggregateRepository<T, ID = string> extends IRepository<T, ID> {
|
|
897
|
+
saveWithEvents(aggregate: T): Promise<T>;
|
|
898
|
+
}
|
|
899
|
+
`;
|
|
900
|
+
}
|
|
901
|
+
function generateUnitOfWork() {
|
|
902
|
+
return `/**
|
|
903
|
+
* Unit of Work Pattern
|
|
904
|
+
* Manages transactions across multiple repositories
|
|
905
|
+
*/
|
|
906
|
+
|
|
907
|
+
import { Injectable } from '@nestjs/common';
|
|
908
|
+
import { DataSource, EntityManager, QueryRunner } from 'typeorm';
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Unit of Work interface
|
|
912
|
+
*/
|
|
913
|
+
export interface IUnitOfWork {
|
|
914
|
+
begin(): Promise<void>;
|
|
915
|
+
commit(): Promise<void>;
|
|
916
|
+
rollback(): Promise<void>;
|
|
917
|
+
getRepository<T>(entityClass: new () => T): any;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* TypeORM Unit of Work implementation
|
|
922
|
+
*/
|
|
923
|
+
@Injectable()
|
|
924
|
+
export class UnitOfWork implements IUnitOfWork {
|
|
925
|
+
private queryRunner: QueryRunner | null = null;
|
|
926
|
+
|
|
927
|
+
constructor(private readonly dataSource: DataSource) {}
|
|
928
|
+
|
|
929
|
+
async begin(): Promise<void> {
|
|
930
|
+
this.queryRunner = this.dataSource.createQueryRunner();
|
|
931
|
+
await this.queryRunner.connect();
|
|
932
|
+
await this.queryRunner.startTransaction();
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async commit(): Promise<void> {
|
|
936
|
+
if (!this.queryRunner) {
|
|
937
|
+
throw new Error('Transaction not started');
|
|
938
|
+
}
|
|
939
|
+
await this.queryRunner.commitTransaction();
|
|
940
|
+
await this.queryRunner.release();
|
|
941
|
+
this.queryRunner = null;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
async rollback(): Promise<void> {
|
|
945
|
+
if (!this.queryRunner) {
|
|
946
|
+
throw new Error('Transaction not started');
|
|
947
|
+
}
|
|
948
|
+
await this.queryRunner.rollbackTransaction();
|
|
949
|
+
await this.queryRunner.release();
|
|
950
|
+
this.queryRunner = null;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
getRepository<T>(entityClass: new () => T): any {
|
|
954
|
+
if (!this.queryRunner) {
|
|
955
|
+
throw new Error('Transaction not started');
|
|
956
|
+
}
|
|
957
|
+
return this.queryRunner.manager.getRepository(entityClass);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
get manager(): EntityManager {
|
|
961
|
+
if (!this.queryRunner) {
|
|
962
|
+
throw new Error('Transaction not started');
|
|
963
|
+
}
|
|
964
|
+
return this.queryRunner.manager;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Unit of Work decorator
|
|
970
|
+
*/
|
|
971
|
+
export function Transactional(): MethodDecorator {
|
|
972
|
+
return function (
|
|
973
|
+
target: any,
|
|
974
|
+
propertyKey: string | symbol,
|
|
975
|
+
descriptor: PropertyDescriptor,
|
|
976
|
+
) {
|
|
977
|
+
const originalMethod = descriptor.value;
|
|
978
|
+
|
|
979
|
+
descriptor.value = async function (...args: any[]) {
|
|
980
|
+
const unitOfWork: UnitOfWork = (this as any).unitOfWork;
|
|
981
|
+
|
|
982
|
+
if (!unitOfWork) {
|
|
983
|
+
throw new Error('UnitOfWork not injected');
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
await unitOfWork.begin();
|
|
987
|
+
|
|
988
|
+
try {
|
|
989
|
+
const result = await originalMethod.apply(this, args);
|
|
990
|
+
await unitOfWork.commit();
|
|
991
|
+
return result;
|
|
992
|
+
} catch (error) {
|
|
993
|
+
await unitOfWork.rollback();
|
|
994
|
+
throw error;
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
|
|
998
|
+
return descriptor;
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
`;
|
|
1002
|
+
}
|
|
1003
|
+
// Helper functions
|
|
1004
|
+
function toKebabCase(str) {
|
|
1005
|
+
return str
|
|
1006
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
1007
|
+
.replace(/[\\s_]+/g, '-')
|
|
1008
|
+
.toLowerCase();
|
|
1009
|
+
}
|
|
1010
|
+
function toPascalCase(str) {
|
|
1011
|
+
return str
|
|
1012
|
+
.replace(/[-_\\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
|
|
1013
|
+
.replace(/^(.)/, c => c.toUpperCase());
|
|
1014
|
+
}
|
|
1015
|
+
function toCamelCase(str) {
|
|
1016
|
+
const pascal = toPascalCase(str);
|
|
1017
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
1018
|
+
}
|
|
1019
|
+
//# sourceMappingURL=generate-repository.js.map
|