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.
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 +24 -5
  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,889 @@
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.applyEventSourcingRecipe = applyEventSourcingRecipe;
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 applyEventSourcingRecipe(basePath) {
44
+ const sharedPath = path.join(basePath, 'src/shared');
45
+ const esPath = path.join(sharedPath, 'event-sourcing');
46
+ await (0, file_utils_1.ensureDir)(esPath);
47
+ await (0, file_utils_1.ensureDir)(path.join(esPath, 'store'));
48
+ await (0, file_utils_1.ensureDir)(path.join(esPath, 'aggregate'));
49
+ await (0, file_utils_1.ensureDir)(path.join(esPath, 'projections'));
50
+ // Event sourcing types
51
+ const typesContent = `export interface DomainEvent<T = any> {
52
+ eventId: string;
53
+ eventType: string;
54
+ aggregateId: string;
55
+ aggregateType: string;
56
+ version: number;
57
+ timestamp: Date;
58
+ payload: T;
59
+ metadata: EventMetadata;
60
+ }
61
+
62
+ export interface EventMetadata {
63
+ correlationId?: string;
64
+ causationId?: string;
65
+ userId?: string;
66
+ tenantId?: string;
67
+ [key: string]: any;
68
+ }
69
+
70
+ export interface StoredEvent {
71
+ id: string;
72
+ streamId: string;
73
+ eventType: string;
74
+ version: number;
75
+ data: string;
76
+ metadata: string;
77
+ timestamp: Date;
78
+ }
79
+
80
+ export interface EventStream {
81
+ streamId: string;
82
+ version: number;
83
+ events: DomainEvent[];
84
+ }
85
+
86
+ export interface Snapshot<T = any> {
87
+ aggregateId: string;
88
+ aggregateType: string;
89
+ version: number;
90
+ state: T;
91
+ timestamp: Date;
92
+ }
93
+
94
+ export interface ProjectionState<T = any> {
95
+ projectionName: string;
96
+ position: number;
97
+ state: T;
98
+ lastUpdated: Date;
99
+ }
100
+
101
+ export type EventHandler<T = any> = (event: DomainEvent<T>) => Promise<void>;
102
+
103
+ export interface Projection {
104
+ name: string;
105
+ init(): Promise<void>;
106
+ handle(event: DomainEvent): Promise<void>;
107
+ getPosition(): Promise<number>;
108
+ reset(): Promise<void>;
109
+ }
110
+ `;
111
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'event-sourcing.types.ts'), typesContent);
112
+ // Event Store
113
+ const eventStoreContent = `import { Injectable, Logger } from "@nestjs/common";
114
+ import { v4 as uuid } from "uuid";
115
+ import { DomainEvent, StoredEvent, EventStream, EventMetadata } from "../event-sourcing.types";
116
+
117
+ export interface EventStoreConfig {
118
+ snapshotFrequency?: number;
119
+ }
120
+
121
+ @Injectable()
122
+ export class EventStore {
123
+ private readonly logger = new Logger(EventStore.name);
124
+ private events: Map<string, StoredEvent[]> = new Map();
125
+ private globalPosition = 0;
126
+ private subscribers: Map<string, Array<(event: DomainEvent) => Promise<void>>> = new Map();
127
+
128
+ /**
129
+ * Append events to a stream
130
+ */
131
+ async append(
132
+ streamId: string,
133
+ events: Array<{ eventType: string; payload: any }>,
134
+ expectedVersion: number,
135
+ metadata: EventMetadata = {}
136
+ ): Promise<DomainEvent[]> {
137
+ const stream = this.events.get(streamId) || [];
138
+ const currentVersion = stream.length;
139
+
140
+ // Optimistic concurrency check
141
+ if (expectedVersion !== -1 && expectedVersion !== currentVersion) {
142
+ throw new Error(
143
+ \`Concurrency conflict: expected version \${expectedVersion}, but stream is at \${currentVersion}\`
144
+ );
145
+ }
146
+
147
+ const domainEvents: DomainEvent[] = [];
148
+
149
+ for (let i = 0; i < events.length; i++) {
150
+ const { eventType, payload } = events[i];
151
+ const version = currentVersion + i + 1;
152
+ this.globalPosition++;
153
+
154
+ const event: DomainEvent = {
155
+ eventId: uuid(),
156
+ eventType,
157
+ aggregateId: streamId.split("-")[1] || streamId,
158
+ aggregateType: streamId.split("-")[0],
159
+ version,
160
+ timestamp: new Date(),
161
+ payload,
162
+ metadata: {
163
+ ...metadata,
164
+ position: this.globalPosition,
165
+ },
166
+ };
167
+
168
+ const storedEvent: StoredEvent = {
169
+ id: event.eventId,
170
+ streamId,
171
+ eventType,
172
+ version,
173
+ data: JSON.stringify(payload),
174
+ metadata: JSON.stringify(event.metadata),
175
+ timestamp: event.timestamp,
176
+ };
177
+
178
+ stream.push(storedEvent);
179
+ domainEvents.push(event);
180
+ }
181
+
182
+ this.events.set(streamId, stream);
183
+
184
+ // Notify subscribers
185
+ for (const event of domainEvents) {
186
+ await this.notifySubscribers(event);
187
+ }
188
+
189
+ this.logger.debug(\`Appended \${events.length} events to stream \${streamId}\`);
190
+ return domainEvents;
191
+ }
192
+
193
+ /**
194
+ * Read events from a stream
195
+ */
196
+ async readStream(
197
+ streamId: string,
198
+ fromVersion: number = 0,
199
+ toVersion?: number
200
+ ): Promise<EventStream> {
201
+ const stored = this.events.get(streamId) || [];
202
+ const filtered = stored.filter(
203
+ (e) => e.version > fromVersion && (!toVersion || e.version <= toVersion)
204
+ );
205
+
206
+ const events: DomainEvent[] = filtered.map((e) => ({
207
+ eventId: e.id,
208
+ eventType: e.eventType,
209
+ aggregateId: streamId.split("-")[1] || streamId,
210
+ aggregateType: streamId.split("-")[0],
211
+ version: e.version,
212
+ timestamp: e.timestamp,
213
+ payload: JSON.parse(e.data),
214
+ metadata: JSON.parse(e.metadata),
215
+ }));
216
+
217
+ return {
218
+ streamId,
219
+ version: stored.length,
220
+ events,
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Read all events from a position (for projections)
226
+ */
227
+ async readAll(fromPosition: number = 0, limit: number = 100): Promise<DomainEvent[]> {
228
+ const allEvents: DomainEvent[] = [];
229
+
230
+ for (const [streamId, stored] of this.events) {
231
+ for (const e of stored) {
232
+ const metadata = JSON.parse(e.metadata);
233
+ if (metadata.position > fromPosition) {
234
+ allEvents.push({
235
+ eventId: e.id,
236
+ eventType: e.eventType,
237
+ aggregateId: streamId.split("-")[1] || streamId,
238
+ aggregateType: streamId.split("-")[0],
239
+ version: e.version,
240
+ timestamp: e.timestamp,
241
+ payload: JSON.parse(e.data),
242
+ metadata,
243
+ });
244
+ }
245
+ }
246
+ }
247
+
248
+ return allEvents
249
+ .sort((a, b) => (a.metadata.position || 0) - (b.metadata.position || 0))
250
+ .slice(0, limit);
251
+ }
252
+
253
+ /**
254
+ * Subscribe to events
255
+ */
256
+ subscribe(
257
+ eventType: string | "*",
258
+ handler: (event: DomainEvent) => Promise<void>
259
+ ): () => void {
260
+ if (!this.subscribers.has(eventType)) {
261
+ this.subscribers.set(eventType, []);
262
+ }
263
+ this.subscribers.get(eventType)!.push(handler);
264
+
265
+ // Return unsubscribe function
266
+ return () => {
267
+ const handlers = this.subscribers.get(eventType);
268
+ if (handlers) {
269
+ const index = handlers.indexOf(handler);
270
+ if (index > -1) {
271
+ handlers.splice(index, 1);
272
+ }
273
+ }
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Get current stream version
279
+ */
280
+ async getStreamVersion(streamId: string): Promise<number> {
281
+ const stream = this.events.get(streamId) || [];
282
+ return stream.length;
283
+ }
284
+
285
+ /**
286
+ * Check if stream exists
287
+ */
288
+ async streamExists(streamId: string): Promise<boolean> {
289
+ return this.events.has(streamId) && (this.events.get(streamId)?.length || 0) > 0;
290
+ }
291
+
292
+ /**
293
+ * Delete stream (soft delete - mark as deleted)
294
+ */
295
+ async deleteStream(streamId: string): Promise<void> {
296
+ await this.append(
297
+ streamId,
298
+ [{ eventType: "StreamDeleted", payload: { deletedAt: new Date() } }],
299
+ -1
300
+ );
301
+ }
302
+
303
+ private async notifySubscribers(event: DomainEvent): Promise<void> {
304
+ const handlers = [
305
+ ...(this.subscribers.get(event.eventType) || []),
306
+ ...(this.subscribers.get("*") || []),
307
+ ];
308
+
309
+ for (const handler of handlers) {
310
+ try {
311
+ await handler(event);
312
+ } catch (error) {
313
+ this.logger.error(\`Subscriber error for \${event.eventType}:\`, error);
314
+ }
315
+ }
316
+ }
317
+ }
318
+ `;
319
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'store/event.store.ts'), eventStoreContent);
320
+ // Snapshot Store
321
+ const snapshotStoreContent = `import { Injectable, Logger } from "@nestjs/common";
322
+ import { Snapshot } from "../event-sourcing.types";
323
+
324
+ @Injectable()
325
+ export class SnapshotStore {
326
+ private readonly logger = new Logger(SnapshotStore.name);
327
+ private snapshots: Map<string, Snapshot[]> = new Map();
328
+
329
+ /**
330
+ * Save a snapshot
331
+ */
332
+ async save<T>(snapshot: Snapshot<T>): Promise<void> {
333
+ const key = \`\${snapshot.aggregateType}-\${snapshot.aggregateId}\`;
334
+
335
+ if (!this.snapshots.has(key)) {
336
+ this.snapshots.set(key, []);
337
+ }
338
+
339
+ this.snapshots.get(key)!.push(snapshot);
340
+ this.logger.debug(\`Saved snapshot for \${key} at version \${snapshot.version}\`);
341
+ }
342
+
343
+ /**
344
+ * Get latest snapshot for an aggregate
345
+ */
346
+ async getLatest<T>(
347
+ aggregateType: string,
348
+ aggregateId: string
349
+ ): Promise<Snapshot<T> | null> {
350
+ const key = \`\${aggregateType}-\${aggregateId}\`;
351
+ const snapshots = this.snapshots.get(key);
352
+
353
+ if (!snapshots || snapshots.length === 0) {
354
+ return null;
355
+ }
356
+
357
+ return snapshots[snapshots.length - 1] as Snapshot<T>;
358
+ }
359
+
360
+ /**
361
+ * Get snapshot at specific version
362
+ */
363
+ async getAtVersion<T>(
364
+ aggregateType: string,
365
+ aggregateId: string,
366
+ version: number
367
+ ): Promise<Snapshot<T> | null> {
368
+ const key = \`\${aggregateType}-\${aggregateId}\`;
369
+ const snapshots = this.snapshots.get(key);
370
+
371
+ if (!snapshots) return null;
372
+
373
+ // Find snapshot at or before the requested version
374
+ for (let i = snapshots.length - 1; i >= 0; i--) {
375
+ if (snapshots[i].version <= version) {
376
+ return snapshots[i] as Snapshot<T>;
377
+ }
378
+ }
379
+
380
+ return null;
381
+ }
382
+
383
+ /**
384
+ * Delete old snapshots, keeping only the latest N
385
+ */
386
+ async pruneSnapshots(
387
+ aggregateType: string,
388
+ aggregateId: string,
389
+ keepCount: number = 3
390
+ ): Promise<number> {
391
+ const key = \`\${aggregateType}-\${aggregateId}\`;
392
+ const snapshots = this.snapshots.get(key);
393
+
394
+ if (!snapshots || snapshots.length <= keepCount) {
395
+ return 0;
396
+ }
397
+
398
+ const removed = snapshots.length - keepCount;
399
+ this.snapshots.set(key, snapshots.slice(-keepCount));
400
+
401
+ this.logger.debug(\`Pruned \${removed} old snapshots for \${key}\`);
402
+ return removed;
403
+ }
404
+ }
405
+ `;
406
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'store/snapshot.store.ts'), snapshotStoreContent);
407
+ // Aggregate Root
408
+ const aggregateRootContent = `import { DomainEvent, EventMetadata } from "../event-sourcing.types";
409
+
410
+ export abstract class AggregateRoot<TState = any> {
411
+ protected _id: string;
412
+ protected _version: number = 0;
413
+ protected _state: TState;
414
+ protected _uncommittedEvents: Array<{ eventType: string; payload: any }> = [];
415
+
416
+ get id(): string {
417
+ return this._id;
418
+ }
419
+
420
+ get version(): number {
421
+ return this._version;
422
+ }
423
+
424
+ get state(): TState {
425
+ return this._state;
426
+ }
427
+
428
+ /**
429
+ * Get the aggregate type name
430
+ */
431
+ abstract getType(): string;
432
+
433
+ /**
434
+ * Create initial state
435
+ */
436
+ protected abstract createInitialState(): TState;
437
+
438
+ /**
439
+ * Apply an event to update state
440
+ */
441
+ protected abstract applyEvent(event: DomainEvent): void;
442
+
443
+ /**
444
+ * Initialize a new aggregate
445
+ */
446
+ protected initialize(id: string): void {
447
+ this._id = id;
448
+ this._state = this.createInitialState();
449
+ }
450
+
451
+ /**
452
+ * Record a new event
453
+ */
454
+ protected recordEvent(eventType: string, payload: any): void {
455
+ this._uncommittedEvents.push({ eventType, payload });
456
+
457
+ // Apply immediately to update state
458
+ const event: DomainEvent = {
459
+ eventId: "",
460
+ eventType,
461
+ aggregateId: this._id,
462
+ aggregateType: this.getType(),
463
+ version: this._version + this._uncommittedEvents.length,
464
+ timestamp: new Date(),
465
+ payload,
466
+ metadata: {},
467
+ };
468
+
469
+ this.applyEvent(event);
470
+ }
471
+
472
+ /**
473
+ * Get uncommitted events
474
+ */
475
+ getUncommittedEvents(): Array<{ eventType: string; payload: any }> {
476
+ return [...this._uncommittedEvents];
477
+ }
478
+
479
+ /**
480
+ * Clear uncommitted events after persisting
481
+ */
482
+ clearUncommittedEvents(): void {
483
+ this._uncommittedEvents = [];
484
+ }
485
+
486
+ /**
487
+ * Reconstitute aggregate from events
488
+ */
489
+ loadFromHistory(events: DomainEvent[]): void {
490
+ for (const event of events) {
491
+ this.applyEvent(event);
492
+ this._version = event.version;
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Load from snapshot and subsequent events
498
+ */
499
+ loadFromSnapshot(state: TState, version: number, events: DomainEvent[]): void {
500
+ this._state = state;
501
+ this._version = version;
502
+
503
+ for (const event of events) {
504
+ this.applyEvent(event);
505
+ this._version = event.version;
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Create a snapshot of current state
511
+ */
512
+ createSnapshot(): { state: TState; version: number } {
513
+ return {
514
+ state: { ...this._state },
515
+ version: this._version,
516
+ };
517
+ }
518
+ }
519
+ `;
520
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'aggregate/aggregate-root.ts'), aggregateRootContent);
521
+ // Aggregate Repository
522
+ const aggregateRepoContent = `import { Logger } from "@nestjs/common";
523
+ import { EventStore } from "../store/event.store";
524
+ import { SnapshotStore } from "../store/snapshot.store";
525
+ import { AggregateRoot } from "./aggregate-root";
526
+ import { DomainEvent, EventMetadata, Snapshot } from "../event-sourcing.types";
527
+
528
+ export interface AggregateRepositoryConfig {
529
+ snapshotFrequency?: number;
530
+ }
531
+
532
+ export abstract class AggregateRepository<T extends AggregateRoot> {
533
+ protected readonly logger = new Logger(this.constructor.name);
534
+ protected config: AggregateRepositoryConfig = {
535
+ snapshotFrequency: 10,
536
+ };
537
+
538
+ constructor(
539
+ protected readonly eventStore: EventStore,
540
+ protected readonly snapshotStore: SnapshotStore
541
+ ) {}
542
+
543
+ /**
544
+ * Get the aggregate type
545
+ */
546
+ protected abstract getAggregateType(): string;
547
+
548
+ /**
549
+ * Create a new aggregate instance
550
+ */
551
+ protected abstract createAggregate(): T;
552
+
553
+ /**
554
+ * Get stream ID for an aggregate
555
+ */
556
+ protected getStreamId(aggregateId: string): string {
557
+ return \`\${this.getAggregateType()}-\${aggregateId}\`;
558
+ }
559
+
560
+ /**
561
+ * Load an aggregate by ID
562
+ */
563
+ async load(aggregateId: string): Promise<T | null> {
564
+ const streamId = this.getStreamId(aggregateId);
565
+ const exists = await this.eventStore.streamExists(streamId);
566
+
567
+ if (!exists) {
568
+ return null;
569
+ }
570
+
571
+ const aggregate = this.createAggregate();
572
+ (aggregate as any)._id = aggregateId;
573
+
574
+ // Try to load from snapshot
575
+ const snapshot = await this.snapshotStore.getLatest(
576
+ this.getAggregateType(),
577
+ aggregateId
578
+ );
579
+
580
+ let fromVersion = 0;
581
+ if (snapshot) {
582
+ (aggregate as any)._state = snapshot.state;
583
+ (aggregate as any)._version = snapshot.version;
584
+ fromVersion = snapshot.version;
585
+ }
586
+
587
+ // Load events after snapshot
588
+ const { events } = await this.eventStore.readStream(streamId, fromVersion);
589
+
590
+ if (snapshot) {
591
+ aggregate.loadFromSnapshot(snapshot.state, snapshot.version, events);
592
+ } else {
593
+ aggregate.loadFromHistory(events);
594
+ }
595
+
596
+ return aggregate;
597
+ }
598
+
599
+ /**
600
+ * Save an aggregate
601
+ */
602
+ async save(aggregate: T, metadata: EventMetadata = {}): Promise<void> {
603
+ const uncommittedEvents = aggregate.getUncommittedEvents();
604
+
605
+ if (uncommittedEvents.length === 0) {
606
+ return;
607
+ }
608
+
609
+ const streamId = this.getStreamId(aggregate.id);
610
+ const expectedVersion = aggregate.version - uncommittedEvents.length;
611
+
612
+ await this.eventStore.append(
613
+ streamId,
614
+ uncommittedEvents,
615
+ expectedVersion,
616
+ metadata
617
+ );
618
+
619
+ aggregate.clearUncommittedEvents();
620
+
621
+ // Create snapshot if needed
622
+ const newVersion = aggregate.version;
623
+ if (
624
+ this.config.snapshotFrequency &&
625
+ newVersion % this.config.snapshotFrequency === 0
626
+ ) {
627
+ await this.createSnapshot(aggregate);
628
+ }
629
+ }
630
+
631
+ /**
632
+ * Create a snapshot of the aggregate
633
+ */
634
+ protected async createSnapshot(aggregate: T): Promise<void> {
635
+ const { state, version } = aggregate.createSnapshot();
636
+
637
+ const snapshot: Snapshot = {
638
+ aggregateId: aggregate.id,
639
+ aggregateType: this.getAggregateType(),
640
+ version,
641
+ state,
642
+ timestamp: new Date(),
643
+ };
644
+
645
+ await this.snapshotStore.save(snapshot);
646
+ this.logger.debug(\`Created snapshot for \${aggregate.id} at version \${version}\`);
647
+ }
648
+
649
+ /**
650
+ * Check if aggregate exists
651
+ */
652
+ async exists(aggregateId: string): Promise<boolean> {
653
+ const streamId = this.getStreamId(aggregateId);
654
+ return this.eventStore.streamExists(streamId);
655
+ }
656
+ }
657
+ `;
658
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'aggregate/aggregate-repository.ts'), aggregateRepoContent);
659
+ // Projection Manager
660
+ const projectionManagerContent = `import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
661
+ import { EventStore } from "../store/event.store";
662
+ import { DomainEvent, Projection, ProjectionState } from "../event-sourcing.types";
663
+
664
+ @Injectable()
665
+ export class ProjectionManager implements OnModuleInit {
666
+ private readonly logger = new Logger(ProjectionManager.name);
667
+ private projections: Map<string, Projection> = new Map();
668
+ private positions: Map<string, number> = new Map();
669
+ private isRunning = false;
670
+
671
+ constructor(private eventStore: EventStore) {}
672
+
673
+ async onModuleInit() {
674
+ // Start projection processing after a short delay
675
+ setTimeout(() => this.start(), 1000);
676
+ }
677
+
678
+ /**
679
+ * Register a projection
680
+ */
681
+ register(projection: Projection): void {
682
+ this.projections.set(projection.name, projection);
683
+ this.logger.log(\`Registered projection: \${projection.name}\`);
684
+ }
685
+
686
+ /**
687
+ * Start processing projections
688
+ */
689
+ async start(): Promise<void> {
690
+ if (this.isRunning) return;
691
+ this.isRunning = true;
692
+
693
+ // Initialize all projections
694
+ for (const [name, projection] of this.projections) {
695
+ await projection.init();
696
+ const position = await projection.getPosition();
697
+ this.positions.set(name, position);
698
+ this.logger.log(\`Projection \${name} at position \${position}\`);
699
+ }
700
+
701
+ // Subscribe to all events
702
+ this.eventStore.subscribe("*", async (event) => {
703
+ await this.processEvent(event);
704
+ });
705
+
706
+ this.logger.log("Projection manager started");
707
+ }
708
+
709
+ /**
710
+ * Stop processing projections
711
+ */
712
+ stop(): void {
713
+ this.isRunning = false;
714
+ this.logger.log("Projection manager stopped");
715
+ }
716
+
717
+ /**
718
+ * Process a single event for all projections
719
+ */
720
+ private async processEvent(event: DomainEvent): Promise<void> {
721
+ const eventPosition = event.metadata.position || 0;
722
+
723
+ for (const [name, projection] of this.projections) {
724
+ const currentPosition = this.positions.get(name) || 0;
725
+
726
+ if (eventPosition > currentPosition) {
727
+ try {
728
+ await projection.handle(event);
729
+ this.positions.set(name, eventPosition);
730
+ } catch (error) {
731
+ this.logger.error(\`Projection \${name} failed on event \${event.eventType}:\`, error);
732
+ }
733
+ }
734
+ }
735
+ }
736
+
737
+ /**
738
+ * Rebuild a projection from scratch
739
+ */
740
+ async rebuild(projectionName: string): Promise<void> {
741
+ const projection = this.projections.get(projectionName);
742
+ if (!projection) {
743
+ throw new Error(\`Projection \${projectionName} not found\`);
744
+ }
745
+
746
+ this.logger.log(\`Rebuilding projection: \${projectionName}\`);
747
+
748
+ await projection.reset();
749
+ this.positions.set(projectionName, 0);
750
+
751
+ // Process all events
752
+ let position = 0;
753
+ const batchSize = 100;
754
+
755
+ while (true) {
756
+ const events = await this.eventStore.readAll(position, batchSize);
757
+ if (events.length === 0) break;
758
+
759
+ for (const event of events) {
760
+ await projection.handle(event);
761
+ position = event.metadata.position || position + 1;
762
+ }
763
+
764
+ this.positions.set(projectionName, position);
765
+ }
766
+
767
+ this.logger.log(\`Projection \${projectionName} rebuilt to position \${position}\`);
768
+ }
769
+
770
+ /**
771
+ * Get projection status
772
+ */
773
+ getStatus(): Array<{ name: string; position: number }> {
774
+ return Array.from(this.projections.keys()).map((name) => ({
775
+ name,
776
+ position: this.positions.get(name) || 0,
777
+ }));
778
+ }
779
+ }
780
+ `;
781
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'projections/projection-manager.ts'), projectionManagerContent);
782
+ // Base Projection
783
+ const baseProjectionContent = `import { Logger } from "@nestjs/common";
784
+ import { DomainEvent, Projection } from "../event-sourcing.types";
785
+
786
+ export abstract class BaseProjection implements Projection {
787
+ protected readonly logger = new Logger(this.constructor.name);
788
+ protected position = 0;
789
+
790
+ abstract readonly name: string;
791
+
792
+ /**
793
+ * Initialize the projection
794
+ */
795
+ async init(): Promise<void> {
796
+ this.logger.log(\`Initializing projection: \${this.name}\`);
797
+ }
798
+
799
+ /**
800
+ * Handle an event
801
+ */
802
+ async handle(event: DomainEvent): Promise<void> {
803
+ const handlerName = \`on\${event.eventType}\`;
804
+ const handler = (this as any)[handlerName];
805
+
806
+ if (typeof handler === "function") {
807
+ await handler.call(this, event);
808
+ }
809
+
810
+ this.position = event.metadata.position || this.position + 1;
811
+ }
812
+
813
+ /**
814
+ * Get current position
815
+ */
816
+ async getPosition(): Promise<number> {
817
+ return this.position;
818
+ }
819
+
820
+ /**
821
+ * Reset the projection
822
+ */
823
+ async reset(): Promise<void> {
824
+ this.position = 0;
825
+ this.logger.log(\`Reset projection: \${this.name}\`);
826
+ }
827
+ }
828
+ `;
829
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'projections/base-projection.ts'), baseProjectionContent);
830
+ // Event Sourcing Module
831
+ const moduleContent = `import { Module, Global, DynamicModule } from "@nestjs/common";
832
+ import { EventStore } from "./store/event.store";
833
+ import { SnapshotStore } from "./store/snapshot.store";
834
+ import { ProjectionManager } from "./projections/projection-manager";
835
+
836
+ export interface EventSourcingModuleOptions {
837
+ snapshotFrequency?: number;
838
+ }
839
+
840
+ @Global()
841
+ @Module({})
842
+ export class EventSourcingModule {
843
+ static forRoot(options: EventSourcingModuleOptions = {}): DynamicModule {
844
+ return {
845
+ module: EventSourcingModule,
846
+ providers: [
847
+ EventStore,
848
+ SnapshotStore,
849
+ ProjectionManager,
850
+ {
851
+ provide: "EVENT_SOURCING_OPTIONS",
852
+ useValue: options,
853
+ },
854
+ ],
855
+ exports: [EventStore, SnapshotStore, ProjectionManager],
856
+ };
857
+ }
858
+ }
859
+ `;
860
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'event-sourcing.module.ts'), moduleContent);
861
+ // Index exports
862
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'index.ts'), `export * from "./event-sourcing.types";
863
+ export * from "./store/event.store";
864
+ export * from "./store/snapshot.store";
865
+ export * from "./aggregate/aggregate-root";
866
+ export * from "./aggregate/aggregate-repository";
867
+ export * from "./projections/projection-manager";
868
+ export * from "./projections/base-projection";
869
+ export * from "./event-sourcing.module";
870
+ `);
871
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'store/index.ts'), `export * from "./event.store";
872
+ export * from "./snapshot.store";
873
+ `);
874
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'aggregate/index.ts'), `export * from "./aggregate-root";
875
+ export * from "./aggregate-repository";
876
+ `);
877
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'projections/index.ts'), `export * from "./projection-manager";
878
+ export * from "./base-projection";
879
+ `);
880
+ console.log(chalk_1.default.green(' ✓ Event sourcing types'));
881
+ console.log(chalk_1.default.green(' ✓ Event store with optimistic concurrency'));
882
+ console.log(chalk_1.default.green(' ✓ Snapshot store'));
883
+ console.log(chalk_1.default.green(' ✓ Aggregate root base class'));
884
+ console.log(chalk_1.default.green(' ✓ Aggregate repository with snapshot support'));
885
+ console.log(chalk_1.default.green(' ✓ Projection manager with rebuild capability'));
886
+ console.log(chalk_1.default.green(' ✓ Base projection class'));
887
+ console.log(chalk_1.default.green(' ✓ Event sourcing module'));
888
+ }
889
+ //# sourceMappingURL=event-sourcing.recipe.js.map