nestjs-ddd-cli 2.2.0 → 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.
Files changed (254) hide show
  1. package/README.md +247 -408
  2. package/ddd.schema.json +111 -0
  3. package/dist/commands/aggregate-validator.d.ts +9 -0
  4. package/dist/commands/aggregate-validator.js +953 -0
  5. package/dist/commands/aggregate-validator.js.map +1 -0
  6. package/dist/commands/ai-assist.d.ts +8 -0
  7. package/dist/commands/ai-assist.js +337 -0
  8. package/dist/commands/ai-assist.js.map +1 -0
  9. package/dist/commands/api-contracts.d.ts +9 -0
  10. package/dist/commands/api-contracts.js +1368 -0
  11. package/dist/commands/api-contracts.js.map +1 -0
  12. package/dist/commands/api-docs.d.ts +8 -0
  13. package/dist/commands/api-docs.js +408 -0
  14. package/dist/commands/api-docs.js.map +1 -0
  15. package/dist/commands/api-versioning.d.ts +11 -0
  16. package/dist/commands/api-versioning.js +643 -0
  17. package/dist/commands/api-versioning.js.map +1 -0
  18. package/dist/commands/audit-logging.d.ts +9 -0
  19. package/dist/commands/audit-logging.js +1129 -0
  20. package/dist/commands/audit-logging.js.map +1 -0
  21. package/dist/commands/batch-generate.d.ts +10 -0
  22. package/dist/commands/batch-generate.js +405 -0
  23. package/dist/commands/batch-generate.js.map +1 -0
  24. package/dist/commands/caching-strategies.d.ts +9 -0
  25. package/dist/commands/caching-strategies.js +874 -0
  26. package/dist/commands/caching-strategies.js.map +1 -0
  27. package/dist/commands/code-analyzer.d.ts +42 -0
  28. package/dist/commands/code-analyzer.js +474 -0
  29. package/dist/commands/code-analyzer.js.map +1 -0
  30. package/dist/commands/database-seeding.d.ts +6 -0
  31. package/dist/commands/database-seeding.js +621 -0
  32. package/dist/commands/database-seeding.js.map +1 -0
  33. package/dist/commands/db-optimization.d.ts +7 -0
  34. package/dist/commands/db-optimization.js +687 -0
  35. package/dist/commands/db-optimization.js.map +1 -0
  36. package/dist/commands/dependency-graph.d.ts +6 -0
  37. package/dist/commands/dependency-graph.js +329 -0
  38. package/dist/commands/dependency-graph.js.map +1 -0
  39. package/dist/commands/doctor-enhanced.d.ts +22 -0
  40. package/dist/commands/doctor-enhanced.js +543 -0
  41. package/dist/commands/doctor-enhanced.js.map +1 -0
  42. package/dist/commands/doctor.d.ts +4 -0
  43. package/dist/commands/doctor.js +151 -0
  44. package/dist/commands/doctor.js.map +1 -0
  45. package/dist/commands/env-manager.d.ts +6 -0
  46. package/dist/commands/env-manager.js +419 -0
  47. package/dist/commands/env-manager.js.map +1 -0
  48. package/dist/commands/event-sourcing-full.d.ts +10 -0
  49. package/dist/commands/event-sourcing-full.js +1107 -0
  50. package/dist/commands/event-sourcing-full.js.map +1 -0
  51. package/dist/commands/feature-flags.d.ts +9 -0
  52. package/dist/commands/feature-flags.js +824 -0
  53. package/dist/commands/feature-flags.js.map +1 -0
  54. package/dist/commands/filter-dsl.d.ts +10 -0
  55. package/dist/commands/filter-dsl.js +1407 -0
  56. package/dist/commands/filter-dsl.js.map +1 -0
  57. package/dist/commands/generate-all.js +485 -32
  58. package/dist/commands/generate-all.js.map +1 -1
  59. package/dist/commands/generate-deployment.d.ts +8 -0
  60. package/dist/commands/generate-deployment.js +746 -0
  61. package/dist/commands/generate-deployment.js.map +1 -0
  62. package/dist/commands/generate-domain-service.d.ts +14 -0
  63. package/dist/commands/generate-domain-service.js +796 -0
  64. package/dist/commands/generate-domain-service.js.map +1 -0
  65. package/dist/commands/generate-entity.js +82 -24
  66. package/dist/commands/generate-entity.js.map +1 -1
  67. package/dist/commands/generate-from-schema.d.ts +56 -0
  68. package/dist/commands/generate-from-schema.js +222 -0
  69. package/dist/commands/generate-from-schema.js.map +1 -0
  70. package/dist/commands/generate-orchestrator.d.ts +14 -0
  71. package/dist/commands/generate-orchestrator.js +887 -0
  72. package/dist/commands/generate-orchestrator.js.map +1 -0
  73. package/dist/commands/generate-repository.d.ts +14 -0
  74. package/dist/commands/generate-repository.js +1019 -0
  75. package/dist/commands/generate-repository.js.map +1 -0
  76. package/dist/commands/generate-shared.d.ts +4 -0
  77. package/dist/commands/generate-shared.js +388 -0
  78. package/dist/commands/generate-shared.js.map +1 -0
  79. package/dist/commands/generate-value-object.d.ts +32 -0
  80. package/dist/commands/generate-value-object.js +700 -0
  81. package/dist/commands/generate-value-object.js.map +1 -0
  82. package/dist/commands/graphql-subscriptions.d.ts +6 -0
  83. package/dist/commands/graphql-subscriptions.js +607 -0
  84. package/dist/commands/graphql-subscriptions.js.map +1 -0
  85. package/dist/commands/graphql-types.d.ts +5 -0
  86. package/dist/commands/graphql-types.js +423 -0
  87. package/dist/commands/graphql-types.js.map +1 -0
  88. package/dist/commands/health-probes-advanced.d.ts +6 -0
  89. package/dist/commands/health-probes-advanced.js +655 -0
  90. package/dist/commands/health-probes-advanced.js.map +1 -0
  91. package/dist/commands/i18n-setup.d.ts +10 -0
  92. package/dist/commands/i18n-setup.js +677 -0
  93. package/dist/commands/i18n-setup.js.map +1 -0
  94. package/dist/commands/init-config.d.ts +6 -0
  95. package/dist/commands/init-config.js +370 -0
  96. package/dist/commands/init-config.js.map +1 -0
  97. package/dist/commands/init-project.js +56 -6
  98. package/dist/commands/init-project.js.map +1 -1
  99. package/dist/commands/interactive-scaffold.d.ts +5 -0
  100. package/dist/commands/interactive-scaffold.js +271 -0
  101. package/dist/commands/interactive-scaffold.js.map +1 -0
  102. package/dist/commands/metrics-prometheus.d.ts +6 -0
  103. package/dist/commands/metrics-prometheus.js +681 -0
  104. package/dist/commands/metrics-prometheus.js.map +1 -0
  105. package/dist/commands/migration-engine.d.ts +6 -0
  106. package/dist/commands/migration-engine.js +446 -0
  107. package/dist/commands/migration-engine.js.map +1 -0
  108. package/dist/commands/migration.d.ts +12 -0
  109. package/dist/commands/migration.js +484 -0
  110. package/dist/commands/migration.js.map +1 -0
  111. package/dist/commands/monorepo.d.ts +8 -0
  112. package/dist/commands/monorepo.js +483 -0
  113. package/dist/commands/monorepo.js.map +1 -0
  114. package/dist/commands/multi-database.d.ts +5 -0
  115. package/dist/commands/multi-database.js +439 -0
  116. package/dist/commands/multi-database.js.map +1 -0
  117. package/dist/commands/observability-tracing.d.ts +10 -0
  118. package/dist/commands/observability-tracing.js +740 -0
  119. package/dist/commands/observability-tracing.js.map +1 -0
  120. package/dist/commands/openapi-export.d.ts +8 -0
  121. package/dist/commands/openapi-export.js +359 -0
  122. package/dist/commands/openapi-export.js.map +1 -0
  123. package/dist/commands/perf-analyzer.d.ts +8 -0
  124. package/dist/commands/perf-analyzer.js +423 -0
  125. package/dist/commands/perf-analyzer.js.map +1 -0
  126. package/dist/commands/rate-limiting.d.ts +10 -0
  127. package/dist/commands/rate-limiting.js +953 -0
  128. package/dist/commands/rate-limiting.js.map +1 -0
  129. package/dist/commands/recipe-plugin.d.ts +56 -0
  130. package/dist/commands/recipe-plugin.js +315 -0
  131. package/dist/commands/recipe-plugin.js.map +1 -0
  132. package/dist/commands/recipe.d.ts +6 -0
  133. package/dist/commands/recipe.js +3941 -0
  134. package/dist/commands/recipe.js.map +1 -0
  135. package/dist/commands/recipes/elasticsearch.recipe.d.ts +1 -0
  136. package/dist/commands/recipes/elasticsearch.recipe.js +761 -0
  137. package/dist/commands/recipes/elasticsearch.recipe.js.map +1 -0
  138. package/dist/commands/recipes/event-sourcing.recipe.d.ts +1 -0
  139. package/dist/commands/recipes/event-sourcing.recipe.js +889 -0
  140. package/dist/commands/recipes/event-sourcing.recipe.js.map +1 -0
  141. package/dist/commands/recipes/index.d.ts +7 -0
  142. package/dist/commands/recipes/index.js +24 -0
  143. package/dist/commands/recipes/index.js.map +1 -0
  144. package/dist/commands/recipes/message-queue.recipe.d.ts +1 -0
  145. package/dist/commands/recipes/message-queue.recipe.js +706 -0
  146. package/dist/commands/recipes/message-queue.recipe.js.map +1 -0
  147. package/dist/commands/recipes/middleware.recipe.d.ts +1 -0
  148. package/dist/commands/recipes/middleware.recipe.js +383 -0
  149. package/dist/commands/recipes/middleware.recipe.js.map +1 -0
  150. package/dist/commands/recipes/multi-tenancy.recipe.d.ts +1 -0
  151. package/dist/commands/recipes/multi-tenancy.recipe.js +520 -0
  152. package/dist/commands/recipes/multi-tenancy.recipe.js.map +1 -0
  153. package/dist/commands/recipes/oauth2.recipe.d.ts +1 -0
  154. package/dist/commands/recipes/oauth2.recipe.js +472 -0
  155. package/dist/commands/recipes/oauth2.recipe.js.map +1 -0
  156. package/dist/commands/recipes/websocket.recipe.d.ts +1 -0
  157. package/dist/commands/recipes/websocket.recipe.js +453 -0
  158. package/dist/commands/recipes/websocket.recipe.js.map +1 -0
  159. package/dist/commands/resilience-patterns.d.ts +13 -0
  160. package/dist/commands/resilience-patterns.js +1029 -0
  161. package/dist/commands/resilience-patterns.js.map +1 -0
  162. package/dist/commands/security-patterns.d.ts +11 -0
  163. package/dist/commands/security-patterns.js +2233 -0
  164. package/dist/commands/security-patterns.js.map +1 -0
  165. package/dist/commands/template-debug.d.ts +27 -0
  166. package/dist/commands/template-debug.js +388 -0
  167. package/dist/commands/template-debug.js.map +1 -0
  168. package/dist/commands/test-factory-full.d.ts +9 -0
  169. package/dist/commands/test-factory-full.js +1570 -0
  170. package/dist/commands/test-factory-full.js.map +1 -0
  171. package/dist/commands/test-scaffold.d.ts +7 -0
  172. package/dist/commands/test-scaffold.js +621 -0
  173. package/dist/commands/test-scaffold.js.map +1 -0
  174. package/dist/index.js +1088 -0
  175. package/dist/index.js.map +1 -1
  176. package/dist/templates/ai-context/CLAUDE.md.hbs +158 -0
  177. package/dist/templates/ai-context/conventions.md.hbs +154 -0
  178. package/dist/templates/command/create-command.hbs +6 -14
  179. package/dist/templates/command/delete-command.hbs +19 -0
  180. package/dist/templates/command/update-command.hbs +24 -0
  181. package/dist/templates/controller/controller.hbs +64 -17
  182. package/dist/templates/dto/create-dto.hbs +29 -5
  183. package/dist/templates/dto/filter-dto.hbs +52 -0
  184. package/dist/templates/dto/filter-query.dto.hbs +148 -0
  185. package/dist/templates/dto/paginated-response.dto.hbs +29 -0
  186. package/dist/templates/dto/pagination-query.dto.hbs +30 -0
  187. package/dist/templates/dto/response-dto.hbs +38 -0
  188. package/dist/templates/dto/update-dto.hbs +11 -0
  189. package/dist/templates/entity/entity.hbs +32 -1
  190. package/dist/templates/event/domain-event.hbs +33 -7
  191. package/dist/templates/event/event-handler.hbs +40 -0
  192. package/dist/templates/exception/base-exceptions.hbs +69 -0
  193. package/dist/templates/exception/entity-not-found.exception.hbs +7 -0
  194. package/dist/templates/mapper/mapper.hbs +49 -24
  195. package/dist/templates/module/module.hbs +34 -10
  196. package/dist/templates/orm-entity/orm-entity.hbs +63 -12
  197. package/dist/templates/prisma/prisma-mapper.hbs +71 -0
  198. package/dist/templates/prisma/prisma-repository.hbs +114 -0
  199. package/dist/templates/prisma/prisma-schema.hbs +20 -0
  200. package/dist/templates/prisma/prisma-service.hbs +51 -0
  201. package/dist/templates/query/get-all.query.hbs +50 -0
  202. package/dist/templates/query/get-by-id.query.hbs +31 -0
  203. package/dist/templates/repository/repository.hbs +55 -13
  204. package/dist/templates/resolver/graphql-input.hbs +54 -0
  205. package/dist/templates/resolver/graphql-type.hbs +58 -0
  206. package/dist/templates/resolver/pagination-args.hbs +33 -0
  207. package/dist/templates/resolver/resolver.hbs +62 -0
  208. package/dist/templates/shared/prisma-query-builder.util.hbs +189 -0
  209. package/dist/templates/shared/query-builder.util.hbs +218 -0
  210. package/dist/templates/test/controller.spec.hbs +124 -0
  211. package/dist/templates/test/repository.spec.hbs +158 -0
  212. package/dist/templates/test/usecase.spec.hbs +116 -0
  213. package/dist/templates/usecase/create-usecase.hbs +19 -7
  214. package/dist/templates/usecase/delete-usecase.hbs +17 -0
  215. package/dist/templates/usecase/update-usecase.hbs +31 -0
  216. package/dist/utils/config.utils.d.ts +45 -0
  217. package/dist/utils/config.utils.js +211 -0
  218. package/dist/utils/config.utils.js.map +1 -0
  219. package/dist/utils/error.utils.d.ts +145 -0
  220. package/dist/utils/error.utils.js +422 -0
  221. package/dist/utils/error.utils.js.map +1 -0
  222. package/dist/utils/field.utils.d.ts +54 -0
  223. package/dist/utils/field.utils.js +389 -0
  224. package/dist/utils/field.utils.js.map +1 -0
  225. package/dist/utils/file.utils.d.ts +19 -8
  226. package/dist/utils/file.utils.js +135 -4
  227. package/dist/utils/file.utils.js.map +1 -1
  228. package/dist/utils/idempotency.utils.d.ts +123 -0
  229. package/dist/utils/idempotency.utils.js +444 -0
  230. package/dist/utils/idempotency.utils.js.map +1 -0
  231. package/dist/utils/naming.utils.js +26 -3
  232. package/dist/utils/naming.utils.js.map +1 -1
  233. package/dist/utils/performance.utils.d.ts +37 -0
  234. package/dist/utils/performance.utils.js +158 -0
  235. package/dist/utils/performance.utils.js.map +1 -0
  236. package/dist/utils/relation.utils.d.ts +92 -0
  237. package/dist/utils/relation.utils.js +388 -0
  238. package/dist/utils/relation.utils.js.map +1 -0
  239. package/dist/utils/rollback.utils.d.ts +49 -0
  240. package/dist/utils/rollback.utils.js +306 -0
  241. package/dist/utils/rollback.utils.js.map +1 -0
  242. package/dist/utils/schema.utils.d.ts +123 -0
  243. package/dist/utils/schema.utils.js +419 -0
  244. package/dist/utils/schema.utils.js.map +1 -0
  245. package/dist/utils/security.utils.d.ts +57 -0
  246. package/dist/utils/security.utils.js +315 -0
  247. package/dist/utils/security.utils.js.map +1 -0
  248. package/dist/utils/template-engine.utils.d.ts +80 -0
  249. package/dist/utils/template-engine.utils.js +463 -0
  250. package/dist/utils/template-engine.utils.js.map +1 -0
  251. package/dist/utils/validation-registry.utils.d.ts +160 -0
  252. package/dist/utils/validation-registry.utils.js +526 -0
  253. package/dist/utils/validation-registry.utils.js.map +1 -0
  254. package/package.json +3 -1
@@ -0,0 +1,761 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.applyElasticsearchRecipe = applyElasticsearchRecipe;
40
+ const path = __importStar(require("path"));
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const file_utils_1 = require("../../utils/file.utils");
43
+ async function applyElasticsearchRecipe(basePath) {
44
+ const sharedPath = path.join(basePath, 'src/shared');
45
+ const searchPath = path.join(sharedPath, 'search');
46
+ await (0, file_utils_1.ensureDir)(searchPath);
47
+ await (0, file_utils_1.ensureDir)(path.join(searchPath, 'decorators'));
48
+ // Search types
49
+ const searchTypesContent = `export interface SearchQuery {
50
+ query: string;
51
+ fields?: string[];
52
+ filters?: SearchFilter[];
53
+ sort?: SearchSort[];
54
+ pagination?: SearchPagination;
55
+ highlight?: boolean;
56
+ fuzzy?: boolean;
57
+ fuzziness?: number | "AUTO";
58
+ }
59
+
60
+ export interface SearchFilter {
61
+ field: string;
62
+ operator: "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "range" | "exists";
63
+ value: any;
64
+ }
65
+
66
+ export interface SearchSort {
67
+ field: string;
68
+ order: "asc" | "desc";
69
+ }
70
+
71
+ export interface SearchPagination {
72
+ page: number;
73
+ limit: number;
74
+ }
75
+
76
+ export interface SearchResult<T> {
77
+ hits: SearchHit<T>[];
78
+ total: number;
79
+ page: number;
80
+ limit: number;
81
+ totalPages: number;
82
+ aggregations?: Record<string, any>;
83
+ }
84
+
85
+ export interface SearchHit<T> {
86
+ id: string;
87
+ score: number;
88
+ source: T;
89
+ highlight?: Record<string, string[]>;
90
+ }
91
+
92
+ export interface IndexConfig {
93
+ index: string;
94
+ settings?: {
95
+ numberOfShards?: number;
96
+ numberOfReplicas?: number;
97
+ refreshInterval?: string;
98
+ analysis?: {
99
+ analyzer?: Record<string, any>;
100
+ tokenizer?: Record<string, any>;
101
+ filter?: Record<string, any>;
102
+ };
103
+ };
104
+ mappings?: {
105
+ properties: Record<string, MappingProperty>;
106
+ };
107
+ }
108
+
109
+ export interface MappingProperty {
110
+ type: "text" | "keyword" | "long" | "integer" | "double" | "boolean" | "date" | "nested" | "object";
111
+ analyzer?: string;
112
+ searchAnalyzer?: string;
113
+ fields?: Record<string, MappingProperty>;
114
+ properties?: Record<string, MappingProperty>;
115
+ index?: boolean;
116
+ }
117
+
118
+ export interface AutocompleteQuery {
119
+ query: string;
120
+ field: string;
121
+ size?: number;
122
+ filters?: SearchFilter[];
123
+ }
124
+
125
+ export interface AutocompleteResult {
126
+ suggestions: string[];
127
+ }
128
+ `;
129
+ await (0, file_utils_1.writeFile)(path.join(searchPath, 'search.types.ts'), searchTypesContent);
130
+ // Elasticsearch Service
131
+ const esServiceContent = `import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
132
+ import { Client } from "@elastic/elasticsearch";
133
+ import {
134
+ SearchQuery,
135
+ SearchResult,
136
+ SearchHit,
137
+ IndexConfig,
138
+ AutocompleteQuery,
139
+ AutocompleteResult,
140
+ } from "./search.types";
141
+
142
+ @Injectable()
143
+ export class ElasticsearchService implements OnModuleInit {
144
+ private readonly logger = new Logger(ElasticsearchService.name);
145
+ private client: Client;
146
+
147
+ constructor() {
148
+ this.client = new Client({
149
+ node: process.env.ELASTICSEARCH_URL || "http://localhost:9200",
150
+ auth: process.env.ELASTICSEARCH_USERNAME
151
+ ? {
152
+ username: process.env.ELASTICSEARCH_USERNAME,
153
+ password: process.env.ELASTICSEARCH_PASSWORD || "",
154
+ }
155
+ : undefined,
156
+ });
157
+ }
158
+
159
+ async onModuleInit() {
160
+ try {
161
+ const info = await this.client.info();
162
+ this.logger.log(\`Connected to Elasticsearch \${info.version.number}\`);
163
+ } catch (error) {
164
+ this.logger.error("Failed to connect to Elasticsearch:", error);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Create or update an index
170
+ */
171
+ async createIndex(config: IndexConfig): Promise<void> {
172
+ const exists = await this.client.indices.exists({ index: config.index });
173
+
174
+ if (exists) {
175
+ this.logger.log(\`Index \${config.index} already exists\`);
176
+ return;
177
+ }
178
+
179
+ await this.client.indices.create({
180
+ index: config.index,
181
+ body: {
182
+ settings: config.settings,
183
+ mappings: config.mappings,
184
+ },
185
+ });
186
+
187
+ this.logger.log(\`Created index \${config.index}\`);
188
+ }
189
+
190
+ /**
191
+ * Index a document
192
+ */
193
+ async indexDocument<T>(
194
+ index: string,
195
+ id: string,
196
+ document: T,
197
+ refresh: boolean = false
198
+ ): Promise<void> {
199
+ await this.client.index({
200
+ index,
201
+ id,
202
+ body: document,
203
+ refresh: refresh ? "wait_for" : false,
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Bulk index documents
209
+ */
210
+ async bulkIndex<T>(
211
+ index: string,
212
+ documents: Array<{ id: string; document: T }>
213
+ ): Promise<{ success: number; failed: number }> {
214
+ const body = documents.flatMap(({ id, document }) => [
215
+ { index: { _index: index, _id: id } },
216
+ document,
217
+ ]);
218
+
219
+ const result = await this.client.bulk({ body, refresh: true });
220
+
221
+ let success = 0;
222
+ let failed = 0;
223
+
224
+ if (result.items) {
225
+ for (const item of result.items) {
226
+ if (item.index?.error) {
227
+ failed++;
228
+ this.logger.error(\`Failed to index: \${item.index.error.reason}\`);
229
+ } else {
230
+ success++;
231
+ }
232
+ }
233
+ }
234
+
235
+ return { success, failed };
236
+ }
237
+
238
+ /**
239
+ * Delete a document
240
+ */
241
+ async deleteDocument(index: string, id: string): Promise<void> {
242
+ await this.client.delete({ index, id });
243
+ }
244
+
245
+ /**
246
+ * Update a document
247
+ */
248
+ async updateDocument<T>(
249
+ index: string,
250
+ id: string,
251
+ partialDoc: Partial<T>
252
+ ): Promise<void> {
253
+ await this.client.update({
254
+ index,
255
+ id,
256
+ body: { doc: partialDoc },
257
+ });
258
+ }
259
+
260
+ /**
261
+ * Search documents
262
+ */
263
+ async search<T>(index: string, query: SearchQuery): Promise<SearchResult<T>> {
264
+ const { page = 1, limit = 10 } = query.pagination || {};
265
+ const from = (page - 1) * limit;
266
+
267
+ const esQuery = this.buildQuery(query);
268
+
269
+ const result = await this.client.search({
270
+ index,
271
+ body: {
272
+ from,
273
+ size: limit,
274
+ query: esQuery,
275
+ sort: this.buildSort(query.sort),
276
+ highlight: query.highlight
277
+ ? {
278
+ fields: (query.fields || ["*"]).reduce(
279
+ (acc, field) => ({ ...acc, [field]: {} }),
280
+ {}
281
+ ),
282
+ pre_tags: ["<em>"],
283
+ post_tags: ["</em>"],
284
+ }
285
+ : undefined,
286
+ },
287
+ });
288
+
289
+ const total =
290
+ typeof result.hits.total === "number"
291
+ ? result.hits.total
292
+ : result.hits.total?.value || 0;
293
+
294
+ const hits: SearchHit<T>[] = result.hits.hits.map((hit: any) => ({
295
+ id: hit._id,
296
+ score: hit._score,
297
+ source: hit._source,
298
+ highlight: hit.highlight,
299
+ }));
300
+
301
+ return {
302
+ hits,
303
+ total,
304
+ page,
305
+ limit,
306
+ totalPages: Math.ceil(total / limit),
307
+ aggregations: result.aggregations,
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Autocomplete suggestions
313
+ */
314
+ async autocomplete(
315
+ index: string,
316
+ query: AutocompleteQuery
317
+ ): Promise<AutocompleteResult> {
318
+ const { query: text, field, size = 10, filters } = query;
319
+
320
+ const must: any[] = [
321
+ {
322
+ match_phrase_prefix: {
323
+ [field]: {
324
+ query: text,
325
+ max_expansions: 50,
326
+ },
327
+ },
328
+ },
329
+ ];
330
+
331
+ if (filters) {
332
+ must.push(...this.buildFilters(filters));
333
+ }
334
+
335
+ const result = await this.client.search({
336
+ index,
337
+ body: {
338
+ size,
339
+ query: { bool: { must } },
340
+ _source: [field],
341
+ },
342
+ });
343
+
344
+ const suggestions = result.hits.hits
345
+ .map((hit: any) => hit._source[field])
346
+ .filter((v: any, i: number, a: any[]) => a.indexOf(v) === i);
347
+
348
+ return { suggestions };
349
+ }
350
+
351
+ /**
352
+ * Count documents
353
+ */
354
+ async count(index: string, query?: SearchQuery): Promise<number> {
355
+ const esQuery = query ? this.buildQuery(query) : { match_all: {} };
356
+
357
+ const result = await this.client.count({
358
+ index,
359
+ body: { query: esQuery },
360
+ });
361
+
362
+ return result.count;
363
+ }
364
+
365
+ /**
366
+ * Check if document exists
367
+ */
368
+ async exists(index: string, id: string): Promise<boolean> {
369
+ return await this.client.exists({ index, id });
370
+ }
371
+
372
+ /**
373
+ * Get document by ID
374
+ */
375
+ async getById<T>(index: string, id: string): Promise<T | null> {
376
+ try {
377
+ const result = await this.client.get({ index, id });
378
+ return result._source as T;
379
+ } catch (error: any) {
380
+ if (error.meta?.statusCode === 404) {
381
+ return null;
382
+ }
383
+ throw error;
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Aggregate data
389
+ */
390
+ async aggregate(
391
+ index: string,
392
+ aggregations: Record<string, any>,
393
+ query?: SearchQuery
394
+ ): Promise<Record<string, any>> {
395
+ const esQuery = query ? this.buildQuery(query) : { match_all: {} };
396
+
397
+ const result = await this.client.search({
398
+ index,
399
+ body: {
400
+ size: 0,
401
+ query: esQuery,
402
+ aggs: aggregations,
403
+ },
404
+ });
405
+
406
+ return result.aggregations || {};
407
+ }
408
+
409
+ /**
410
+ * Build Elasticsearch query from SearchQuery
411
+ */
412
+ private buildQuery(query: SearchQuery): any {
413
+ const must: any[] = [];
414
+ const filter: any[] = [];
415
+
416
+ // Text search
417
+ if (query.query) {
418
+ const fields = query.fields || ["*"];
419
+
420
+ if (query.fuzzy) {
421
+ must.push({
422
+ multi_match: {
423
+ query: query.query,
424
+ fields,
425
+ fuzziness: query.fuzziness || "AUTO",
426
+ prefix_length: 2,
427
+ },
428
+ });
429
+ } else {
430
+ must.push({
431
+ multi_match: {
432
+ query: query.query,
433
+ fields,
434
+ type: "best_fields",
435
+ },
436
+ });
437
+ }
438
+ }
439
+
440
+ // Filters
441
+ if (query.filters) {
442
+ filter.push(...this.buildFilters(query.filters));
443
+ }
444
+
445
+ if (must.length === 0 && filter.length === 0) {
446
+ return { match_all: {} };
447
+ }
448
+
449
+ return {
450
+ bool: {
451
+ ...(must.length > 0 ? { must } : {}),
452
+ ...(filter.length > 0 ? { filter } : {}),
453
+ },
454
+ };
455
+ }
456
+
457
+ /**
458
+ * Build filter clauses
459
+ */
460
+ private buildFilters(filters: SearchQuery["filters"]): any[] {
461
+ if (!filters) return [];
462
+
463
+ return filters.map((f) => {
464
+ switch (f.operator) {
465
+ case "eq":
466
+ return { term: { [f.field]: f.value } };
467
+ case "ne":
468
+ return { bool: { must_not: { term: { [f.field]: f.value } } } };
469
+ case "gt":
470
+ return { range: { [f.field]: { gt: f.value } } };
471
+ case "gte":
472
+ return { range: { [f.field]: { gte: f.value } } };
473
+ case "lt":
474
+ return { range: { [f.field]: { lt: f.value } } };
475
+ case "lte":
476
+ return { range: { [f.field]: { lte: f.value } } };
477
+ case "in":
478
+ return { terms: { [f.field]: f.value } };
479
+ case "range":
480
+ return { range: { [f.field]: f.value } };
481
+ case "exists":
482
+ return { exists: { field: f.field } };
483
+ default:
484
+ return { term: { [f.field]: f.value } };
485
+ }
486
+ });
487
+ }
488
+
489
+ /**
490
+ * Build sort clause
491
+ */
492
+ private buildSort(sort?: SearchQuery["sort"]): any[] | undefined {
493
+ if (!sort || sort.length === 0) return undefined;
494
+
495
+ return sort.map((s) => ({
496
+ [s.field]: { order: s.order },
497
+ }));
498
+ }
499
+
500
+ /**
501
+ * Delete an index
502
+ */
503
+ async deleteIndex(index: string): Promise<void> {
504
+ await this.client.indices.delete({ index });
505
+ this.logger.log(\`Deleted index \${index}\`);
506
+ }
507
+
508
+ /**
509
+ * Reindex documents
510
+ */
511
+ async reindex(sourceIndex: string, destIndex: string): Promise<void> {
512
+ await this.client.reindex({
513
+ body: {
514
+ source: { index: sourceIndex },
515
+ dest: { index: destIndex },
516
+ },
517
+ });
518
+ this.logger.log(\`Reindexed \${sourceIndex} to \${destIndex}\`);
519
+ }
520
+ }
521
+ `;
522
+ await (0, file_utils_1.writeFile)(path.join(searchPath, 'elasticsearch.service.ts'), esServiceContent);
523
+ // Searchable decorator
524
+ const searchableDecoratorContent = `import { SetMetadata } from "@nestjs/common";
525
+ import { IndexConfig, MappingProperty } from "../search.types";
526
+
527
+ export const SEARCHABLE_METADATA = "search:searchable";
528
+ export const SEARCH_FIELD_METADATA = "search:field";
529
+
530
+ export interface SearchableConfig {
531
+ index: string;
532
+ settings?: IndexConfig["settings"];
533
+ }
534
+
535
+ /**
536
+ * Mark an entity as searchable
537
+ */
538
+ export function Searchable(config: SearchableConfig): ClassDecorator {
539
+ return (target) => {
540
+ Reflect.defineMetadata(SEARCHABLE_METADATA, config, target);
541
+ };
542
+ }
543
+
544
+ /**
545
+ * Mark a field as searchable with mapping configuration
546
+ */
547
+ export function SearchField(mapping?: Partial<MappingProperty>): PropertyDecorator {
548
+ return (target, propertyKey) => {
549
+ const existingFields =
550
+ Reflect.getMetadata(SEARCH_FIELD_METADATA, target.constructor) || {};
551
+
552
+ existingFields[propertyKey] = {
553
+ type: "text",
554
+ ...mapping,
555
+ };
556
+
557
+ Reflect.defineMetadata(SEARCH_FIELD_METADATA, existingFields, target.constructor);
558
+ };
559
+ }
560
+
561
+ /**
562
+ * Mark a field as keyword (exact match)
563
+ */
564
+ export function SearchKeyword(): PropertyDecorator {
565
+ return SearchField({ type: "keyword" });
566
+ }
567
+
568
+ /**
569
+ * Mark a field as text with autocomplete support
570
+ */
571
+ export function SearchText(analyzer?: string): PropertyDecorator {
572
+ return SearchField({
573
+ type: "text",
574
+ analyzer,
575
+ fields: {
576
+ keyword: { type: "keyword" },
577
+ autocomplete: {
578
+ type: "text",
579
+ analyzer: "autocomplete",
580
+ },
581
+ },
582
+ });
583
+ }
584
+ `;
585
+ await (0, file_utils_1.writeFile)(path.join(searchPath, 'decorators/searchable.decorator.ts'), searchableDecoratorContent);
586
+ // Search repository base
587
+ const searchRepoContent = `import { ElasticsearchService } from "./elasticsearch.service";
588
+ import { SearchQuery, SearchResult, IndexConfig } from "./search.types";
589
+
590
+ /**
591
+ * Base class for searchable repositories
592
+ */
593
+ export abstract class SearchableRepository<T> {
594
+ protected abstract readonly index: string;
595
+ protected abstract readonly mapping: IndexConfig["mappings"];
596
+
597
+ constructor(protected readonly esService: ElasticsearchService) {}
598
+
599
+ /**
600
+ * Initialize the index with mappings
601
+ */
602
+ async initializeIndex(settings?: IndexConfig["settings"]): Promise<void> {
603
+ await this.esService.createIndex({
604
+ index: this.index,
605
+ settings: {
606
+ numberOfShards: 1,
607
+ numberOfReplicas: 0,
608
+ analysis: {
609
+ analyzer: {
610
+ autocomplete: {
611
+ type: "custom",
612
+ tokenizer: "autocomplete",
613
+ filter: ["lowercase"],
614
+ },
615
+ autocomplete_search: {
616
+ type: "custom",
617
+ tokenizer: "standard",
618
+ filter: ["lowercase"],
619
+ },
620
+ },
621
+ tokenizer: {
622
+ autocomplete: {
623
+ type: "edge_ngram",
624
+ min_gram: 2,
625
+ max_gram: 20,
626
+ token_chars: ["letter", "digit"],
627
+ },
628
+ },
629
+ },
630
+ ...settings,
631
+ },
632
+ mappings: this.mapping,
633
+ });
634
+ }
635
+
636
+ /**
637
+ * Index a single document
638
+ */
639
+ async index(id: string, document: T): Promise<void> {
640
+ await this.esService.indexDocument(this.index, id, document);
641
+ }
642
+
643
+ /**
644
+ * Bulk index multiple documents
645
+ */
646
+ async bulkIndex(
647
+ documents: Array<{ id: string; document: T }>
648
+ ): Promise<{ success: number; failed: number }> {
649
+ return this.esService.bulkIndex(this.index, documents);
650
+ }
651
+
652
+ /**
653
+ * Remove document from index
654
+ */
655
+ async remove(id: string): Promise<void> {
656
+ await this.esService.deleteDocument(this.index, id);
657
+ }
658
+
659
+ /**
660
+ * Update indexed document
661
+ */
662
+ async update(id: string, partialDoc: Partial<T>): Promise<void> {
663
+ await this.esService.updateDocument(this.index, id, partialDoc);
664
+ }
665
+
666
+ /**
667
+ * Search documents
668
+ */
669
+ async search(query: SearchQuery): Promise<SearchResult<T>> {
670
+ return this.esService.search<T>(this.index, query);
671
+ }
672
+
673
+ /**
674
+ * Get document by ID
675
+ */
676
+ async findById(id: string): Promise<T | null> {
677
+ return this.esService.getById<T>(this.index, id);
678
+ }
679
+
680
+ /**
681
+ * Autocomplete query
682
+ */
683
+ async autocomplete(
684
+ text: string,
685
+ field: string,
686
+ size?: number
687
+ ): Promise<string[]> {
688
+ const result = await this.esService.autocomplete(this.index, {
689
+ query: text,
690
+ field,
691
+ size,
692
+ });
693
+ return result.suggestions;
694
+ }
695
+
696
+ /**
697
+ * Sync entity with search index
698
+ */
699
+ async syncFromDatabase(
700
+ entities: T[],
701
+ idExtractor: (entity: T) => string
702
+ ): Promise<{ success: number; failed: number }> {
703
+ const documents = entities.map((entity) => ({
704
+ id: idExtractor(entity),
705
+ document: entity,
706
+ }));
707
+ return this.bulkIndex(documents);
708
+ }
709
+ }
710
+ `;
711
+ await (0, file_utils_1.writeFile)(path.join(searchPath, 'searchable.repository.ts'), searchRepoContent);
712
+ // Search module
713
+ const moduleContent = `import { Module, Global, DynamicModule } from "@nestjs/common";
714
+ import { ElasticsearchService } from "./elasticsearch.service";
715
+
716
+ export interface SearchModuleOptions {
717
+ url?: string;
718
+ username?: string;
719
+ password?: string;
720
+ }
721
+
722
+ @Global()
723
+ @Module({})
724
+ export class SearchModule {
725
+ static forRoot(options: SearchModuleOptions = {}): DynamicModule {
726
+ // Set environment variables if provided
727
+ if (options.url) {
728
+ process.env.ELASTICSEARCH_URL = options.url;
729
+ }
730
+ if (options.username) {
731
+ process.env.ELASTICSEARCH_USERNAME = options.username;
732
+ }
733
+ if (options.password) {
734
+ process.env.ELASTICSEARCH_PASSWORD = options.password;
735
+ }
736
+
737
+ return {
738
+ module: SearchModule,
739
+ providers: [ElasticsearchService],
740
+ exports: [ElasticsearchService],
741
+ };
742
+ }
743
+ }
744
+ `;
745
+ await (0, file_utils_1.writeFile)(path.join(searchPath, 'search.module.ts'), moduleContent);
746
+ // Index exports
747
+ await (0, file_utils_1.writeFile)(path.join(searchPath, 'index.ts'), `export * from "./search.types";
748
+ export * from "./elasticsearch.service";
749
+ export * from "./searchable.repository";
750
+ export * from "./decorators/searchable.decorator";
751
+ export * from "./search.module";
752
+ `);
753
+ await (0, file_utils_1.writeFile)(path.join(searchPath, 'decorators/index.ts'), `export * from "./searchable.decorator";
754
+ `);
755
+ console.log(chalk_1.default.green(' ✓ Search types and interfaces'));
756
+ console.log(chalk_1.default.green(' ✓ Elasticsearch service (CRUD, search, autocomplete, aggregations)'));
757
+ console.log(chalk_1.default.green(' ✓ Searchable repository base class'));
758
+ console.log(chalk_1.default.green(' ✓ @Searchable, @SearchField decorators'));
759
+ console.log(chalk_1.default.green(' ✓ Search module'));
760
+ }
761
+ //# sourceMappingURL=elasticsearch.recipe.js.map