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,1107 @@
1
+ "use strict";
2
+ /**
3
+ * Complete Event Sourcing Framework
4
+ * Full infrastructure for event sourcing architecture
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.setupEventSourcingFramework = setupEventSourcingFramework;
44
+ const path = __importStar(require("path"));
45
+ const chalk_1 = __importDefault(require("chalk"));
46
+ const file_utils_1 = require("../utils/file.utils");
47
+ async function setupEventSourcingFramework(basePath, options = {}) {
48
+ console.log(chalk_1.default.bold.blue('\n📦 Setting up Event Sourcing Framework\n'));
49
+ const esPath = path.join(basePath, 'src/shared/event-sourcing');
50
+ await (0, file_utils_1.ensureDir)(esPath);
51
+ await (0, file_utils_1.ensureDir)(path.join(esPath, 'decorators'));
52
+ await (0, file_utils_1.ensureDir)(path.join(esPath, 'stores'));
53
+ await (0, file_utils_1.ensureDir)(path.join(esPath, 'handlers'));
54
+ await (0, file_utils_1.ensureDir)(path.join(esPath, 'projections'));
55
+ // Generate core types
56
+ await generateEventTypes(esPath);
57
+ // Generate aggregate root
58
+ await generateAggregateRoot(esPath);
59
+ // Generate event store
60
+ await generateEventStore(esPath, options);
61
+ // Generate snapshot store
62
+ await generateSnapshotStore(esPath, options);
63
+ // Generate event bus
64
+ await generateEventBus(esPath);
65
+ // Generate projection manager
66
+ await generateProjectionManager(esPath);
67
+ // Generate decorators
68
+ await generateDecorators(esPath);
69
+ // Generate module
70
+ await generateEventSourcingModule(esPath);
71
+ // Generate index
72
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'index.ts'), `export * from './event.types';
73
+ export * from './aggregate-root';
74
+ export * from './stores/event.store';
75
+ export * from './stores/snapshot.store';
76
+ export * from './event-bus';
77
+ export * from './projections/projection.manager';
78
+ export * from './decorators';
79
+ export * from './event-sourcing.module';
80
+ `);
81
+ console.log(chalk_1.default.green('\n✅ Event Sourcing Framework set up!'));
82
+ }
83
+ async function generateEventTypes(esPath) {
84
+ const content = `/**
85
+ * Core Event Sourcing Types
86
+ */
87
+
88
+ export interface DomainEvent<T = any> {
89
+ eventId: string;
90
+ eventType: string;
91
+ aggregateId: string;
92
+ aggregateType: string;
93
+ version: number;
94
+ timestamp: Date;
95
+ payload: T;
96
+ metadata?: EventMetadata;
97
+ }
98
+
99
+ export interface EventMetadata {
100
+ correlationId?: string;
101
+ causationId?: string;
102
+ userId?: string;
103
+ tenantId?: string;
104
+ timestamp?: Date;
105
+ [key: string]: any;
106
+ }
107
+
108
+ export interface StoredEvent extends DomainEvent {
109
+ id: string;
110
+ createdAt: Date;
111
+ sequence: number;
112
+ }
113
+
114
+ export interface EventStream {
115
+ aggregateId: string;
116
+ aggregateType: string;
117
+ version: number;
118
+ events: StoredEvent[];
119
+ }
120
+
121
+ export interface Snapshot<T = any> {
122
+ aggregateId: string;
123
+ aggregateType: string;
124
+ version: number;
125
+ state: T;
126
+ timestamp: Date;
127
+ }
128
+
129
+ export interface EventEnvelope<T = any> {
130
+ event: DomainEvent<T>;
131
+ position: number;
132
+ timestamp: Date;
133
+ }
134
+
135
+ export type EventHandler<T = any> = (event: DomainEvent<T>) => Promise<void> | void;
136
+
137
+ export type EventApplier<TState, TEvent> = (state: TState, event: TEvent) => TState;
138
+
139
+ export interface EventStoreOptions {
140
+ batchSize?: number;
141
+ timeout?: number;
142
+ }
143
+
144
+ export interface AppendResult {
145
+ success: boolean;
146
+ nextVersion: number;
147
+ position: number;
148
+ }
149
+
150
+ export interface ReadOptions {
151
+ fromVersion?: number;
152
+ toVersion?: number;
153
+ limit?: number;
154
+ }
155
+
156
+ /**
157
+ * Event versioning for schema evolution
158
+ */
159
+ export interface EventVersion {
160
+ version: number;
161
+ upcast: (oldEvent: any) => any;
162
+ downcast?: (newEvent: any) => any;
163
+ }
164
+
165
+ export interface VersionedEvent {
166
+ schemaVersion: number;
167
+ [key: string]: any;
168
+ }
169
+
170
+ /**
171
+ * Create a domain event
172
+ */
173
+ export function createEvent<T>(
174
+ eventType: string,
175
+ aggregateId: string,
176
+ aggregateType: string,
177
+ payload: T,
178
+ metadata?: EventMetadata
179
+ ): DomainEvent<T> {
180
+ return {
181
+ eventId: generateEventId(),
182
+ eventType,
183
+ aggregateId,
184
+ aggregateType,
185
+ version: 0, // Will be set by aggregate
186
+ timestamp: new Date(),
187
+ payload,
188
+ metadata: {
189
+ timestamp: new Date(),
190
+ ...metadata,
191
+ },
192
+ };
193
+ }
194
+
195
+ function generateEventId(): string {
196
+ return \`evt_\${Date.now()}_\${Math.random().toString(36).slice(2, 11)}\`;
197
+ }
198
+
199
+ /**
200
+ * Event serialization helpers
201
+ */
202
+ export function serializeEvent(event: DomainEvent): string {
203
+ return JSON.stringify({
204
+ ...event,
205
+ timestamp: event.timestamp.toISOString(),
206
+ metadata: event.metadata ? {
207
+ ...event.metadata,
208
+ timestamp: event.metadata.timestamp?.toISOString(),
209
+ } : undefined,
210
+ });
211
+ }
212
+
213
+ export function deserializeEvent(json: string): DomainEvent {
214
+ const parsed = JSON.parse(json);
215
+ return {
216
+ ...parsed,
217
+ timestamp: new Date(parsed.timestamp),
218
+ metadata: parsed.metadata ? {
219
+ ...parsed.metadata,
220
+ timestamp: parsed.metadata.timestamp ? new Date(parsed.metadata.timestamp) : undefined,
221
+ } : undefined,
222
+ };
223
+ }
224
+ `;
225
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'event.types.ts'), content);
226
+ console.log(chalk_1.default.green(' ✓ Event types'));
227
+ }
228
+ async function generateAggregateRoot(esPath) {
229
+ const content = `import { DomainEvent, createEvent, EventMetadata } from './event.types';
230
+
231
+ /**
232
+ * Base class for Event Sourced Aggregates
233
+ */
234
+ export abstract class AggregateRoot<TState = any> {
235
+ private _id: string;
236
+ private _version: number = 0;
237
+ private _uncommittedEvents: DomainEvent[] = [];
238
+ protected _state: TState;
239
+
240
+ constructor(id: string) {
241
+ this._id = id;
242
+ this._state = this.getInitialState();
243
+ }
244
+
245
+ get id(): string {
246
+ return this._id;
247
+ }
248
+
249
+ get version(): number {
250
+ return this._version;
251
+ }
252
+
253
+ get state(): TState {
254
+ return this._state;
255
+ }
256
+
257
+ get uncommittedEvents(): DomainEvent[] {
258
+ return [...this._uncommittedEvents];
259
+ }
260
+
261
+ /**
262
+ * Get the aggregate type name
263
+ */
264
+ abstract getAggregateType(): string;
265
+
266
+ /**
267
+ * Get the initial state for the aggregate
268
+ */
269
+ protected abstract getInitialState(): TState;
270
+
271
+ /**
272
+ * Apply an event to update state
273
+ */
274
+ protected abstract applyEvent(event: DomainEvent): void;
275
+
276
+ /**
277
+ * Raise a new domain event
278
+ */
279
+ protected raise<T>(eventType: string, payload: T, metadata?: EventMetadata): void {
280
+ const event = createEvent(
281
+ eventType,
282
+ this._id,
283
+ this.getAggregateType(),
284
+ payload,
285
+ metadata
286
+ );
287
+
288
+ event.version = this._version + this._uncommittedEvents.length + 1;
289
+ this._uncommittedEvents.push(event);
290
+ this.applyEvent(event);
291
+ }
292
+
293
+ /**
294
+ * Apply historical events to rebuild state
295
+ */
296
+ loadFromHistory(events: DomainEvent[]): void {
297
+ for (const event of events) {
298
+ this.applyEvent(event);
299
+ this._version = event.version;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Apply events from a snapshot
305
+ */
306
+ loadFromSnapshot(snapshot: { version: number; state: TState }): void {
307
+ this._version = snapshot.version;
308
+ this._state = snapshot.state;
309
+ }
310
+
311
+ /**
312
+ * Mark events as committed
313
+ */
314
+ markEventsAsCommitted(): void {
315
+ this._version += this._uncommittedEvents.length;
316
+ this._uncommittedEvents = [];
317
+ }
318
+
319
+ /**
320
+ * Check if there are uncommitted events
321
+ */
322
+ hasUncommittedEvents(): boolean {
323
+ return this._uncommittedEvents.length > 0;
324
+ }
325
+
326
+ /**
327
+ * Get snapshot of current state
328
+ */
329
+ getSnapshot(): { aggregateId: string; aggregateType: string; version: number; state: TState } {
330
+ return {
331
+ aggregateId: this._id,
332
+ aggregateType: this.getAggregateType(),
333
+ version: this._version,
334
+ state: this._state,
335
+ };
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Decorator to mark a method as an event applier
341
+ */
342
+ export function Apply(eventType: string): MethodDecorator {
343
+ return (target, propertyKey, descriptor) => {
344
+ const appliers = Reflect.getMetadata('event:appliers', target.constructor) || new Map();
345
+ appliers.set(eventType, propertyKey);
346
+ Reflect.defineMetadata('event:appliers', appliers, target.constructor);
347
+ return descriptor;
348
+ };
349
+ }
350
+
351
+ /**
352
+ * Helper to create an aggregate class with automatic event application
353
+ */
354
+ export function createAggregate<TState>(config: {
355
+ aggregateType: string;
356
+ initialState: () => TState;
357
+ appliers: Record<string, (state: TState, payload: any) => TState>;
358
+ }): new (id: string) => AggregateRoot<TState> {
359
+ return class extends AggregateRoot<TState> {
360
+ getAggregateType(): string {
361
+ return config.aggregateType;
362
+ }
363
+
364
+ protected getInitialState(): TState {
365
+ return config.initialState();
366
+ }
367
+
368
+ protected applyEvent(event: DomainEvent): void {
369
+ const applier = config.appliers[event.eventType];
370
+ if (applier) {
371
+ this._state = applier(this._state, event.payload);
372
+ }
373
+ }
374
+ };
375
+ }
376
+ `;
377
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'aggregate-root.ts'), content);
378
+ console.log(chalk_1.default.green(' ✓ Aggregate root'));
379
+ }
380
+ async function generateEventStore(esPath, options) {
381
+ const content = `import { Injectable, Logger } from '@nestjs/common';
382
+ import {
383
+ DomainEvent,
384
+ StoredEvent,
385
+ EventStream,
386
+ AppendResult,
387
+ ReadOptions,
388
+ serializeEvent,
389
+ deserializeEvent,
390
+ } from '../event.types';
391
+
392
+ export interface IEventStore {
393
+ append(aggregateId: string, events: DomainEvent[], expectedVersion: number): Promise<AppendResult>;
394
+ getEvents(aggregateId: string, options?: ReadOptions): Promise<StoredEvent[]>;
395
+ getEventStream(aggregateId: string): Promise<EventStream | null>;
396
+ getAllEvents(options?: { fromPosition?: number; limit?: number }): Promise<StoredEvent[]>;
397
+ }
398
+
399
+ /**
400
+ * In-memory event store (for development/testing)
401
+ */
402
+ @Injectable()
403
+ export class InMemoryEventStore implements IEventStore {
404
+ private readonly logger = new Logger(InMemoryEventStore.name);
405
+ private events: Map<string, StoredEvent[]> = new Map();
406
+ private allEvents: StoredEvent[] = [];
407
+ private sequence: number = 0;
408
+
409
+ async append(
410
+ aggregateId: string,
411
+ events: DomainEvent[],
412
+ expectedVersion: number
413
+ ): Promise<AppendResult> {
414
+ const existingEvents = this.events.get(aggregateId) || [];
415
+ const currentVersion = existingEvents.length > 0
416
+ ? existingEvents[existingEvents.length - 1].version
417
+ : 0;
418
+
419
+ // Optimistic concurrency check
420
+ if (currentVersion !== expectedVersion) {
421
+ throw new Error(
422
+ \`Concurrency conflict: expected version \${expectedVersion}, but found \${currentVersion}\`
423
+ );
424
+ }
425
+
426
+ const storedEvents: StoredEvent[] = events.map((event, index) => ({
427
+ ...event,
428
+ id: \`\${aggregateId}_\${currentVersion + index + 1}\`,
429
+ version: currentVersion + index + 1,
430
+ sequence: ++this.sequence,
431
+ createdAt: new Date(),
432
+ }));
433
+
434
+ this.events.set(aggregateId, [...existingEvents, ...storedEvents]);
435
+ this.allEvents.push(...storedEvents);
436
+
437
+ this.logger.debug(\`Appended \${events.length} events to aggregate \${aggregateId}\`);
438
+
439
+ return {
440
+ success: true,
441
+ nextVersion: currentVersion + events.length,
442
+ position: this.sequence,
443
+ };
444
+ }
445
+
446
+ async getEvents(aggregateId: string, options?: ReadOptions): Promise<StoredEvent[]> {
447
+ let events = this.events.get(aggregateId) || [];
448
+
449
+ if (options?.fromVersion !== undefined) {
450
+ events = events.filter(e => e.version >= options.fromVersion!);
451
+ }
452
+
453
+ if (options?.toVersion !== undefined) {
454
+ events = events.filter(e => e.version <= options.toVersion!);
455
+ }
456
+
457
+ if (options?.limit !== undefined) {
458
+ events = events.slice(0, options.limit);
459
+ }
460
+
461
+ return events;
462
+ }
463
+
464
+ async getEventStream(aggregateId: string): Promise<EventStream | null> {
465
+ const events = this.events.get(aggregateId);
466
+ if (!events || events.length === 0) return null;
467
+
468
+ return {
469
+ aggregateId,
470
+ aggregateType: events[0].aggregateType,
471
+ version: events[events.length - 1].version,
472
+ events,
473
+ };
474
+ }
475
+
476
+ async getAllEvents(options?: { fromPosition?: number; limit?: number }): Promise<StoredEvent[]> {
477
+ let events = [...this.allEvents];
478
+
479
+ if (options?.fromPosition !== undefined) {
480
+ events = events.filter(e => e.sequence > options.fromPosition!);
481
+ }
482
+
483
+ if (options?.limit !== undefined) {
484
+ events = events.slice(0, options.limit);
485
+ }
486
+
487
+ return events;
488
+ }
489
+
490
+ // For testing
491
+ clear(): void {
492
+ this.events.clear();
493
+ this.allEvents = [];
494
+ this.sequence = 0;
495
+ }
496
+ }
497
+
498
+ /**
499
+ * PostgreSQL event store implementation
500
+ */
501
+ @Injectable()
502
+ export class PostgresEventStore implements IEventStore {
503
+ private readonly logger = new Logger(PostgresEventStore.name);
504
+
505
+ constructor(
506
+ // Inject your database connection/repository here
507
+ // private readonly eventRepository: Repository<EventEntity>
508
+ ) {}
509
+
510
+ async append(
511
+ aggregateId: string,
512
+ events: DomainEvent[],
513
+ expectedVersion: number
514
+ ): Promise<AppendResult> {
515
+ // Implementation would use a database transaction with optimistic locking
516
+ // Example SQL:
517
+ // BEGIN;
518
+ // SELECT version FROM event_streams WHERE aggregate_id = $1 FOR UPDATE;
519
+ // -- Check version matches expectedVersion
520
+ // INSERT INTO events (aggregate_id, event_type, payload, version, ...)
521
+ // UPDATE event_streams SET version = $newVersion WHERE aggregate_id = $1;
522
+ // COMMIT;
523
+
524
+ throw new Error('PostgresEventStore not fully implemented - inject your database connection');
525
+ }
526
+
527
+ async getEvents(aggregateId: string, options?: ReadOptions): Promise<StoredEvent[]> {
528
+ throw new Error('PostgresEventStore not fully implemented');
529
+ }
530
+
531
+ async getEventStream(aggregateId: string): Promise<EventStream | null> {
532
+ throw new Error('PostgresEventStore not fully implemented');
533
+ }
534
+
535
+ async getAllEvents(options?: { fromPosition?: number; limit?: number }): Promise<StoredEvent[]> {
536
+ throw new Error('PostgresEventStore not fully implemented');
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Event store factory
542
+ */
543
+ export const EVENT_STORE = Symbol('EVENT_STORE');
544
+
545
+ export function createEventStore(type: 'memory' | 'postgres' | 'mongodb' = 'memory'): IEventStore {
546
+ switch (type) {
547
+ case 'memory':
548
+ return new InMemoryEventStore();
549
+ case 'postgres':
550
+ return new PostgresEventStore();
551
+ default:
552
+ return new InMemoryEventStore();
553
+ }
554
+ }
555
+ `;
556
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'stores/event.store.ts'), content);
557
+ console.log(chalk_1.default.green(' ✓ Event store'));
558
+ }
559
+ async function generateSnapshotStore(esPath, options) {
560
+ const content = `import { Injectable, Logger } from '@nestjs/common';
561
+ import { Snapshot } from '../event.types';
562
+
563
+ export interface ISnapshotStore {
564
+ save(snapshot: Snapshot): Promise<void>;
565
+ get(aggregateId: string, aggregateType: string): Promise<Snapshot | null>;
566
+ getLatest(aggregateId: string): Promise<Snapshot | null>;
567
+ }
568
+
569
+ /**
570
+ * In-memory snapshot store
571
+ */
572
+ @Injectable()
573
+ export class InMemorySnapshotStore implements ISnapshotStore {
574
+ private readonly logger = new Logger(InMemorySnapshotStore.name);
575
+ private snapshots: Map<string, Snapshot[]> = new Map();
576
+
577
+ async save(snapshot: Snapshot): Promise<void> {
578
+ const key = \`\${snapshot.aggregateType}:\${snapshot.aggregateId}\`;
579
+ const existing = this.snapshots.get(key) || [];
580
+ existing.push(snapshot);
581
+ this.snapshots.set(key, existing);
582
+ this.logger.debug(\`Saved snapshot for \${key} at version \${snapshot.version}\`);
583
+ }
584
+
585
+ async get(aggregateId: string, aggregateType: string): Promise<Snapshot | null> {
586
+ const key = \`\${aggregateType}:\${aggregateId}\`;
587
+ const snapshots = this.snapshots.get(key);
588
+ return snapshots?.[snapshots.length - 1] || null;
589
+ }
590
+
591
+ async getLatest(aggregateId: string): Promise<Snapshot | null> {
592
+ for (const [key, snapshots] of this.snapshots) {
593
+ if (key.endsWith(\`:\${aggregateId}\`)) {
594
+ return snapshots[snapshots.length - 1];
595
+ }
596
+ }
597
+ return null;
598
+ }
599
+
600
+ // For testing
601
+ clear(): void {
602
+ this.snapshots.clear();
603
+ }
604
+ }
605
+
606
+ /**
607
+ * Snapshot strategy - determines when to take snapshots
608
+ */
609
+ export interface SnapshotStrategy {
610
+ shouldSnapshot(aggregateId: string, version: number, eventsSinceSnapshot: number): boolean;
611
+ }
612
+
613
+ /**
614
+ * Take snapshot every N events
615
+ */
616
+ export class EventCountSnapshotStrategy implements SnapshotStrategy {
617
+ constructor(private readonly threshold: number = ${options.snapshotThreshold || 100}) {}
618
+
619
+ shouldSnapshot(aggregateId: string, version: number, eventsSinceSnapshot: number): boolean {
620
+ return eventsSinceSnapshot >= this.threshold;
621
+ }
622
+ }
623
+
624
+ /**
625
+ * Take snapshot at specific version intervals
626
+ */
627
+ export class VersionIntervalSnapshotStrategy implements SnapshotStrategy {
628
+ constructor(private readonly interval: number = 100) {}
629
+
630
+ shouldSnapshot(aggregateId: string, version: number, eventsSinceSnapshot: number): boolean {
631
+ return version % this.interval === 0;
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Snapshot store factory
637
+ */
638
+ export const SNAPSHOT_STORE = Symbol('SNAPSHOT_STORE');
639
+
640
+ export function createSnapshotStore(type: 'memory' | 'postgres' | 'redis' = 'memory'): ISnapshotStore {
641
+ switch (type) {
642
+ case 'memory':
643
+ return new InMemorySnapshotStore();
644
+ default:
645
+ return new InMemorySnapshotStore();
646
+ }
647
+ }
648
+ `;
649
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'stores/snapshot.store.ts'), content);
650
+ console.log(chalk_1.default.green(' ✓ Snapshot store'));
651
+ }
652
+ async function generateEventBus(esPath) {
653
+ const content = `import { Injectable, Logger, Type } from '@nestjs/common';
654
+ import { ModuleRef } from '@nestjs/core';
655
+ import { DomainEvent, EventHandler } from './event.types';
656
+
657
+ export interface IEventBus {
658
+ publish<T>(event: DomainEvent<T>): Promise<void>;
659
+ publishAll(events: DomainEvent[]): Promise<void>;
660
+ subscribe<T>(eventType: string, handler: EventHandler<T>): void;
661
+ unsubscribe(eventType: string, handler: EventHandler): void;
662
+ }
663
+
664
+ /**
665
+ * Simple in-process event bus
666
+ */
667
+ @Injectable()
668
+ export class EventBus implements IEventBus {
669
+ private readonly logger = new Logger(EventBus.name);
670
+ private handlers: Map<string, Set<EventHandler>> = new Map();
671
+
672
+ constructor(private readonly moduleRef: ModuleRef) {}
673
+
674
+ async publish<T>(event: DomainEvent<T>): Promise<void> {
675
+ const handlers = this.handlers.get(event.eventType);
676
+ if (!handlers || handlers.size === 0) {
677
+ this.logger.debug(\`No handlers for event type: \${event.eventType}\`);
678
+ return;
679
+ }
680
+
681
+ this.logger.debug(\`Publishing event \${event.eventType} to \${handlers.size} handlers\`);
682
+
683
+ const promises = Array.from(handlers).map(async (handler) => {
684
+ try {
685
+ await handler(event);
686
+ } catch (error) {
687
+ this.logger.error(
688
+ \`Error in event handler for \${event.eventType}: \${(error as Error).message}\`,
689
+ (error as Error).stack
690
+ );
691
+ }
692
+ });
693
+
694
+ await Promise.all(promises);
695
+ }
696
+
697
+ async publishAll(events: DomainEvent[]): Promise<void> {
698
+ for (const event of events) {
699
+ await this.publish(event);
700
+ }
701
+ }
702
+
703
+ subscribe<T>(eventType: string, handler: EventHandler<T>): void {
704
+ if (!this.handlers.has(eventType)) {
705
+ this.handlers.set(eventType, new Set());
706
+ }
707
+ this.handlers.get(eventType)!.add(handler);
708
+ this.logger.debug(\`Subscribed handler to event type: \${eventType}\`);
709
+ }
710
+
711
+ unsubscribe(eventType: string, handler: EventHandler): void {
712
+ const handlers = this.handlers.get(eventType);
713
+ if (handlers) {
714
+ handlers.delete(handler);
715
+ }
716
+ }
717
+
718
+ /**
719
+ * Register a handler class
720
+ */
721
+ registerHandler(handler: Type<IEventHandler>): void {
722
+ const instance = this.moduleRef.get(handler, { strict: false });
723
+ const eventTypes = Reflect.getMetadata('event:handles', handler) || [];
724
+
725
+ for (const eventType of eventTypes) {
726
+ this.subscribe(eventType, instance.handle.bind(instance));
727
+ }
728
+ }
729
+ }
730
+
731
+ /**
732
+ * Event handler interface
733
+ */
734
+ export interface IEventHandler<T = any> {
735
+ handle(event: DomainEvent<T>): Promise<void> | void;
736
+ }
737
+
738
+ /**
739
+ * Decorator to mark a class as an event handler
740
+ */
741
+ export function EventsHandler(...eventTypes: string[]): ClassDecorator {
742
+ return (target) => {
743
+ Reflect.defineMetadata('event:handles', eventTypes, target);
744
+ };
745
+ }
746
+
747
+ /**
748
+ * Event bus with retry support
749
+ */
750
+ @Injectable()
751
+ export class RetryableEventBus extends EventBus {
752
+ private readonly maxRetries = 3;
753
+ private readonly retryDelay = 1000;
754
+
755
+ async publish<T>(event: DomainEvent<T>): Promise<void> {
756
+ const handlers = this['handlers'].get(event.eventType);
757
+ if (!handlers || handlers.size === 0) return;
758
+
759
+ const promises = Array.from(handlers).map(async (handler) => {
760
+ let lastError: Error | undefined;
761
+
762
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
763
+ try {
764
+ await handler(event);
765
+ return;
766
+ } catch (error) {
767
+ lastError = error as Error;
768
+ if (attempt < this.maxRetries - 1) {
769
+ await this.delay(this.retryDelay * Math.pow(2, attempt));
770
+ }
771
+ }
772
+ }
773
+
774
+ this['logger'].error(
775
+ \`Failed to handle event \${event.eventType} after \${this.maxRetries} attempts: \${lastError?.message}\`
776
+ );
777
+ });
778
+
779
+ await Promise.all(promises);
780
+ }
781
+
782
+ private delay(ms: number): Promise<void> {
783
+ return new Promise(resolve => setTimeout(resolve, ms));
784
+ }
785
+ }
786
+ `;
787
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'event-bus.ts'), content);
788
+ console.log(chalk_1.default.green(' ✓ Event bus'));
789
+ }
790
+ async function generateProjectionManager(esPath) {
791
+ const content = `import { Injectable, Logger, Type, OnModuleInit } from '@nestjs/common';
792
+ import { ModuleRef } from '@nestjs/core';
793
+ import { DomainEvent, StoredEvent } from '../event.types';
794
+ import { IEventStore, EVENT_STORE } from '../stores/event.store';
795
+ import { Inject } from '@nestjs/common';
796
+
797
+ export interface IProjection {
798
+ name: string;
799
+ handle(event: DomainEvent): Promise<void> | void;
800
+ getPosition(): Promise<number>;
801
+ setPosition(position: number): Promise<void>;
802
+ rebuild?(): Promise<void>;
803
+ }
804
+
805
+ /**
806
+ * Base projection class
807
+ */
808
+ export abstract class BaseProjection implements IProjection {
809
+ abstract name: string;
810
+ protected position: number = 0;
811
+
812
+ abstract handle(event: DomainEvent): Promise<void> | void;
813
+
814
+ async getPosition(): Promise<number> {
815
+ return this.position;
816
+ }
817
+
818
+ async setPosition(position: number): Promise<void> {
819
+ this.position = position;
820
+ }
821
+
822
+ async rebuild(): Promise<void> {
823
+ this.position = 0;
824
+ // Override in subclass to clear projection state
825
+ }
826
+ }
827
+
828
+ /**
829
+ * Manages projections and keeps them up to date
830
+ */
831
+ @Injectable()
832
+ export class ProjectionManager implements OnModuleInit {
833
+ private readonly logger = new Logger(ProjectionManager.name);
834
+ private projections: Map<string, IProjection> = new Map();
835
+ private running: boolean = false;
836
+ private pollInterval: number = 1000;
837
+
838
+ constructor(
839
+ @Inject(EVENT_STORE) private readonly eventStore: IEventStore,
840
+ private readonly moduleRef: ModuleRef
841
+ ) {}
842
+
843
+ async onModuleInit() {
844
+ // Auto-discover and register projections
845
+ // In practice, you'd scan for classes decorated with @Projection
846
+ }
847
+
848
+ /**
849
+ * Register a projection
850
+ */
851
+ register(projection: IProjection): void {
852
+ this.projections.set(projection.name, projection);
853
+ this.logger.log(\`Registered projection: \${projection.name}\`);
854
+ }
855
+
856
+ /**
857
+ * Start processing events for all projections
858
+ */
859
+ async start(): Promise<void> {
860
+ if (this.running) return;
861
+ this.running = true;
862
+ this.logger.log('Starting projection manager');
863
+ this.poll();
864
+ }
865
+
866
+ /**
867
+ * Stop processing events
868
+ */
869
+ stop(): void {
870
+ this.running = false;
871
+ this.logger.log('Stopped projection manager');
872
+ }
873
+
874
+ /**
875
+ * Rebuild a specific projection
876
+ */
877
+ async rebuild(projectionName: string): Promise<void> {
878
+ const projection = this.projections.get(projectionName);
879
+ if (!projection) {
880
+ throw new Error(\`Projection not found: \${projectionName}\`);
881
+ }
882
+
883
+ this.logger.log(\`Rebuilding projection: \${projectionName}\`);
884
+
885
+ await projection.rebuild?.();
886
+ await this.catchUp(projection);
887
+
888
+ this.logger.log(\`Rebuilt projection: \${projectionName}\`);
889
+ }
890
+
891
+ /**
892
+ * Rebuild all projections
893
+ */
894
+ async rebuildAll(): Promise<void> {
895
+ for (const [name, projection] of this.projections) {
896
+ await this.rebuild(name);
897
+ }
898
+ }
899
+
900
+ /**
901
+ * Poll for new events and process them
902
+ */
903
+ private async poll(): Promise<void> {
904
+ while (this.running) {
905
+ try {
906
+ for (const [name, projection] of this.projections) {
907
+ await this.catchUp(projection);
908
+ }
909
+ } catch (error) {
910
+ this.logger.error(\`Error polling events: \${(error as Error).message}\`);
911
+ }
912
+
913
+ await this.delay(this.pollInterval);
914
+ }
915
+ }
916
+
917
+ /**
918
+ * Catch up a projection to current position
919
+ */
920
+ private async catchUp(projection: IProjection): Promise<void> {
921
+ const currentPosition = await projection.getPosition();
922
+ const events = await this.eventStore.getAllEvents({
923
+ fromPosition: currentPosition,
924
+ limit: 100,
925
+ });
926
+
927
+ for (const event of events) {
928
+ try {
929
+ await projection.handle(event);
930
+ await projection.setPosition(event.sequence);
931
+ } catch (error) {
932
+ this.logger.error(
933
+ \`Error processing event \${event.eventId} in projection \${projection.name}: \${(error as Error).message}\`
934
+ );
935
+ throw error;
936
+ }
937
+ }
938
+ }
939
+
940
+ private delay(ms: number): Promise<void> {
941
+ return new Promise(resolve => setTimeout(resolve, ms));
942
+ }
943
+ }
944
+
945
+ /**
946
+ * Decorator to mark a class as a projection
947
+ */
948
+ export function Projection(name: string): ClassDecorator {
949
+ return (target) => {
950
+ Reflect.defineMetadata('projection:name', name, target);
951
+ };
952
+ }
953
+
954
+ /**
955
+ * Decorator to mark a method as handling specific event types
956
+ */
957
+ export function Handles(...eventTypes: string[]): MethodDecorator {
958
+ return (target, propertyKey, descriptor) => {
959
+ const existing = Reflect.getMetadata('projection:handles', target.constructor) || new Map();
960
+ for (const eventType of eventTypes) {
961
+ existing.set(eventType, propertyKey);
962
+ }
963
+ Reflect.defineMetadata('projection:handles', existing, target.constructor);
964
+ return descriptor;
965
+ };
966
+ }
967
+ `;
968
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'projections/projection.manager.ts'), content);
969
+ console.log(chalk_1.default.green(' ✓ Projection manager'));
970
+ }
971
+ async function generateDecorators(esPath) {
972
+ const content = `/**
973
+ * Event Sourcing Decorators
974
+ */
975
+
976
+ /**
977
+ * Mark a class as an Event Sourced Aggregate
978
+ */
979
+ export function Aggregate(name: string): ClassDecorator {
980
+ return (target) => {
981
+ Reflect.defineMetadata('aggregate:name', name, target);
982
+ };
983
+ }
984
+
985
+ /**
986
+ * Mark a method as a command handler
987
+ */
988
+ export function CommandHandler(commandType: string): MethodDecorator {
989
+ return (target, propertyKey, descriptor) => {
990
+ const handlers = Reflect.getMetadata('aggregate:commands', target.constructor) || new Map();
991
+ handlers.set(commandType, propertyKey);
992
+ Reflect.defineMetadata('aggregate:commands', handlers, target.constructor);
993
+ return descriptor;
994
+ };
995
+ }
996
+
997
+ /**
998
+ * Mark a method as an event applier
999
+ */
1000
+ export function ApplyEvent(eventType: string): MethodDecorator {
1001
+ return (target, propertyKey, descriptor) => {
1002
+ const appliers = Reflect.getMetadata('aggregate:appliers', target.constructor) || new Map();
1003
+ appliers.set(eventType, propertyKey);
1004
+ Reflect.defineMetadata('aggregate:appliers', appliers, target.constructor);
1005
+ return descriptor;
1006
+ };
1007
+ }
1008
+
1009
+ /**
1010
+ * Mark a class as an event handler (saga/process manager)
1011
+ */
1012
+ export function ProcessManager(name: string): ClassDecorator {
1013
+ return (target) => {
1014
+ Reflect.defineMetadata('process-manager:name', name, target);
1015
+ };
1016
+ }
1017
+
1018
+ /**
1019
+ * Subscribe to events in a process manager
1020
+ */
1021
+ export function OnEvent(eventType: string): MethodDecorator {
1022
+ return (target, propertyKey, descriptor) => {
1023
+ const handlers = Reflect.getMetadata('process-manager:handlers', target.constructor) || new Map();
1024
+ handlers.set(eventType, propertyKey);
1025
+ Reflect.defineMetadata('process-manager:handlers', handlers, target.constructor);
1026
+ return descriptor;
1027
+ };
1028
+ }
1029
+
1030
+ /**
1031
+ * Mark a property as the aggregate state
1032
+ */
1033
+ export function State(): PropertyDecorator {
1034
+ return (target, propertyKey) => {
1035
+ Reflect.defineMetadata('aggregate:state', propertyKey, target.constructor);
1036
+ };
1037
+ }
1038
+
1039
+ export * from './projections/projection.manager';
1040
+ export { EventsHandler } from './event-bus';
1041
+ `;
1042
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'decorators/index.ts'), content);
1043
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'decorators.ts'), `export * from './decorators/index';`);
1044
+ console.log(chalk_1.default.green(' ✓ Decorators'));
1045
+ }
1046
+ async function generateEventSourcingModule(esPath) {
1047
+ const content = `import { Module, Global, DynamicModule } from '@nestjs/common';
1048
+ import { InMemoryEventStore, EVENT_STORE, IEventStore } from './stores/event.store';
1049
+ import { InMemorySnapshotStore, SNAPSHOT_STORE, ISnapshotStore } from './stores/snapshot.store';
1050
+ import { EventBus } from './event-bus';
1051
+ import { ProjectionManager } from './projections/projection.manager';
1052
+
1053
+ export interface EventSourcingModuleOptions {
1054
+ eventStore?: 'memory' | 'postgres' | 'mongodb';
1055
+ snapshotStore?: 'memory' | 'postgres' | 'redis';
1056
+ snapshotThreshold?: number;
1057
+ autoStartProjections?: boolean;
1058
+ }
1059
+
1060
+ @Global()
1061
+ @Module({})
1062
+ export class EventSourcingModule {
1063
+ static forRoot(options: EventSourcingModuleOptions = {}): DynamicModule {
1064
+ const eventStoreProvider = {
1065
+ provide: EVENT_STORE,
1066
+ useClass: InMemoryEventStore, // Replace based on options
1067
+ };
1068
+
1069
+ const snapshotStoreProvider = {
1070
+ provide: SNAPSHOT_STORE,
1071
+ useClass: InMemorySnapshotStore, // Replace based on options
1072
+ };
1073
+
1074
+ return {
1075
+ module: EventSourcingModule,
1076
+ providers: [
1077
+ eventStoreProvider,
1078
+ snapshotStoreProvider,
1079
+ EventBus,
1080
+ ProjectionManager,
1081
+ {
1082
+ provide: 'EVENT_SOURCING_OPTIONS',
1083
+ useValue: options,
1084
+ },
1085
+ ],
1086
+ exports: [
1087
+ EVENT_STORE,
1088
+ SNAPSHOT_STORE,
1089
+ EventBus,
1090
+ ProjectionManager,
1091
+ ],
1092
+ };
1093
+ }
1094
+
1095
+ static forFeature(aggregates: any[] = []): DynamicModule {
1096
+ return {
1097
+ module: EventSourcingModule,
1098
+ providers: [...aggregates],
1099
+ exports: [...aggregates],
1100
+ };
1101
+ }
1102
+ }
1103
+ `;
1104
+ await (0, file_utils_1.writeFile)(path.join(esPath, 'event-sourcing.module.ts'), content);
1105
+ console.log(chalk_1.default.green(' ✓ Event sourcing module'));
1106
+ }
1107
+ //# sourceMappingURL=event-sourcing-full.js.map