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,1368 @@
1
+ "use strict";
2
+ /**
3
+ * API Contract Testing & Schema Validation Generator
4
+ * Generates contract testing infrastructure
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.setupApiContracts = setupApiContracts;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const chalk_1 = __importDefault(require("chalk"));
47
+ async function setupApiContracts(basePath, options = {}) {
48
+ console.log(chalk_1.default.bold.blue('\n📜 Setting up API Contract Testing Framework\n'));
49
+ const sharedPath = path.join(basePath, 'src/shared/contracts');
50
+ const testsPath = path.join(basePath, 'test/contracts');
51
+ for (const dir of [sharedPath, testsPath]) {
52
+ if (!fs.existsSync(dir)) {
53
+ fs.mkdirSync(dir, { recursive: true });
54
+ }
55
+ }
56
+ // Generate contract validator
57
+ const validatorContent = generateContractValidator();
58
+ fs.writeFileSync(path.join(sharedPath, 'contract.validator.ts'), validatorContent);
59
+ console.log(chalk_1.default.green(` ✓ Created contract validator`));
60
+ // Generate schema validator
61
+ const schemaContent = generateSchemaValidator();
62
+ fs.writeFileSync(path.join(sharedPath, 'schema.validator.ts'), schemaContent);
63
+ console.log(chalk_1.default.green(` ✓ Created schema validator`));
64
+ // Generate consumer test helpers
65
+ const consumerContent = generateConsumerTestHelpers();
66
+ fs.writeFileSync(path.join(sharedPath, 'consumer-test.helpers.ts'), consumerContent);
67
+ console.log(chalk_1.default.green(` ✓ Created consumer test helpers`));
68
+ // Generate provider test helpers
69
+ const providerContent = generateProviderTestHelpers();
70
+ fs.writeFileSync(path.join(sharedPath, 'provider-test.helpers.ts'), providerContent);
71
+ console.log(chalk_1.default.green(` ✓ Created provider test helpers`));
72
+ // Generate contract store
73
+ const storeContent = generateContractStore();
74
+ fs.writeFileSync(path.join(sharedPath, 'contract.store.ts'), storeContent);
75
+ console.log(chalk_1.default.green(` ✓ Created contract store`));
76
+ // Generate sample contract test
77
+ const sampleTestContent = generateSampleContractTest();
78
+ fs.writeFileSync(path.join(testsPath, 'sample.contract.spec.ts'), sampleTestContent);
79
+ console.log(chalk_1.default.green(` ✓ Created sample contract test`));
80
+ console.log(chalk_1.default.bold.green('\n✅ API contract testing framework ready!\n'));
81
+ }
82
+ function generateContractValidator() {
83
+ return `/**
84
+ * Contract Validator
85
+ * Validates API responses against contracts
86
+ */
87
+
88
+ import Ajv, { ValidateFunction, ErrorObject } from 'ajv';
89
+ import addFormats from 'ajv-formats';
90
+
91
+ export interface ContractValidationResult {
92
+ valid: boolean;
93
+ errors: ContractError[];
94
+ warnings: ContractWarning[];
95
+ }
96
+
97
+ export interface ContractError {
98
+ path: string;
99
+ message: string;
100
+ expected?: any;
101
+ actual?: any;
102
+ }
103
+
104
+ export interface ContractWarning {
105
+ path: string;
106
+ message: string;
107
+ }
108
+
109
+ export interface Contract {
110
+ name: string;
111
+ version: string;
112
+ provider: string;
113
+ consumer?: string;
114
+ endpoints: EndpointContract[];
115
+ }
116
+
117
+ export interface EndpointContract {
118
+ method: string;
119
+ path: string;
120
+ description?: string;
121
+ request?: {
122
+ headers?: Record<string, SchemaDefinition>;
123
+ query?: Record<string, SchemaDefinition>;
124
+ body?: SchemaDefinition;
125
+ };
126
+ response: {
127
+ statusCode: number;
128
+ headers?: Record<string, SchemaDefinition>;
129
+ body?: SchemaDefinition;
130
+ };
131
+ examples?: {
132
+ request?: any;
133
+ response?: any;
134
+ };
135
+ }
136
+
137
+ export interface SchemaDefinition {
138
+ type: string;
139
+ properties?: Record<string, SchemaDefinition>;
140
+ items?: SchemaDefinition;
141
+ required?: string[];
142
+ enum?: any[];
143
+ format?: string;
144
+ pattern?: string;
145
+ minimum?: number;
146
+ maximum?: number;
147
+ minLength?: number;
148
+ maxLength?: number;
149
+ nullable?: boolean;
150
+ }
151
+
152
+ /**
153
+ * Contract Validator
154
+ */
155
+ export class ContractValidator {
156
+ private ajv: Ajv;
157
+ private validators: Map<string, ValidateFunction> = new Map();
158
+
159
+ constructor() {
160
+ this.ajv = new Ajv({ allErrors: true, strict: false });
161
+ addFormats(this.ajv);
162
+ }
163
+
164
+ /**
165
+ * Register a contract
166
+ */
167
+ registerContract(contract: Contract): void {
168
+ for (const endpoint of contract.endpoints) {
169
+ const key = this.getEndpointKey(endpoint);
170
+
171
+ if (endpoint.request?.body) {
172
+ const requestKey = \`\${key}:request\`;
173
+ this.validators.set(
174
+ requestKey,
175
+ this.ajv.compile(this.toJsonSchema(endpoint.request.body)),
176
+ );
177
+ }
178
+
179
+ if (endpoint.response.body) {
180
+ const responseKey = \`\${key}:response:\${endpoint.response.statusCode}\`;
181
+ this.validators.set(
182
+ responseKey,
183
+ this.ajv.compile(this.toJsonSchema(endpoint.response.body)),
184
+ );
185
+ }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Validate request against contract
191
+ */
192
+ validateRequest(
193
+ method: string,
194
+ path: string,
195
+ body: any,
196
+ ): ContractValidationResult {
197
+ const key = \`\${method.toUpperCase()}:\${path}:request\`;
198
+ return this.validate(key, body);
199
+ }
200
+
201
+ /**
202
+ * Validate response against contract
203
+ */
204
+ validateResponse(
205
+ method: string,
206
+ path: string,
207
+ statusCode: number,
208
+ body: any,
209
+ ): ContractValidationResult {
210
+ const key = \`\${method.toUpperCase()}:\${path}:response:\${statusCode}\`;
211
+ return this.validate(key, body);
212
+ }
213
+
214
+ /**
215
+ * Validate data against a key
216
+ */
217
+ private validate(key: string, data: any): ContractValidationResult {
218
+ const validator = this.validators.get(key);
219
+
220
+ if (!validator) {
221
+ return {
222
+ valid: false,
223
+ errors: [{ path: '', message: \`No contract found for: \${key}\` }],
224
+ warnings: [],
225
+ };
226
+ }
227
+
228
+ const valid = validator(data);
229
+
230
+ if (valid) {
231
+ return { valid: true, errors: [], warnings: [] };
232
+ }
233
+
234
+ return {
235
+ valid: false,
236
+ errors: this.formatErrors(validator.errors || []),
237
+ warnings: [],
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Validate schema directly
243
+ */
244
+ validateSchema(schema: SchemaDefinition, data: any): ContractValidationResult {
245
+ const validate = this.ajv.compile(this.toJsonSchema(schema));
246
+ const valid = validate(data);
247
+
248
+ if (valid) {
249
+ return { valid: true, errors: [], warnings: [] };
250
+ }
251
+
252
+ return {
253
+ valid: false,
254
+ errors: this.formatErrors(validate.errors || []),
255
+ warnings: [],
256
+ };
257
+ }
258
+
259
+ private getEndpointKey(endpoint: EndpointContract): string {
260
+ return \`\${endpoint.method.toUpperCase()}:\${endpoint.path}\`;
261
+ }
262
+
263
+ private toJsonSchema(schema: SchemaDefinition): any {
264
+ return {
265
+ type: schema.type,
266
+ properties: schema.properties
267
+ ? Object.fromEntries(
268
+ Object.entries(schema.properties).map(([k, v]) => [
269
+ k,
270
+ this.toJsonSchema(v),
271
+ ]),
272
+ )
273
+ : undefined,
274
+ items: schema.items ? this.toJsonSchema(schema.items) : undefined,
275
+ required: schema.required,
276
+ enum: schema.enum,
277
+ format: schema.format,
278
+ pattern: schema.pattern,
279
+ minimum: schema.minimum,
280
+ maximum: schema.maximum,
281
+ minLength: schema.minLength,
282
+ maxLength: schema.maxLength,
283
+ nullable: schema.nullable,
284
+ };
285
+ }
286
+
287
+ private formatErrors(errors: ErrorObject[]): ContractError[] {
288
+ return errors.map(error => ({
289
+ path: error.instancePath || '/',
290
+ message: error.message || 'Validation failed',
291
+ expected: error.params,
292
+ }));
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Create contract validator
298
+ */
299
+ export function createContractValidator(): ContractValidator {
300
+ return new ContractValidator();
301
+ }
302
+ `;
303
+ }
304
+ function generateSchemaValidator() {
305
+ return `/**
306
+ * Schema Validator
307
+ * Validates data against JSON schemas
308
+ */
309
+
310
+ import Ajv, { ValidateFunction } from 'ajv';
311
+ import addFormats from 'ajv-formats';
312
+
313
+ export interface Schema {
314
+ $id?: string;
315
+ type: string;
316
+ properties?: Record<string, Schema | SchemaRef>;
317
+ items?: Schema | SchemaRef;
318
+ required?: string[];
319
+ additionalProperties?: boolean | Schema;
320
+ enum?: any[];
321
+ const?: any;
322
+ format?: string;
323
+ pattern?: string;
324
+ minimum?: number;
325
+ maximum?: number;
326
+ minLength?: number;
327
+ maxLength?: number;
328
+ minItems?: number;
329
+ maxItems?: number;
330
+ uniqueItems?: boolean;
331
+ oneOf?: (Schema | SchemaRef)[];
332
+ anyOf?: (Schema | SchemaRef)[];
333
+ allOf?: (Schema | SchemaRef)[];
334
+ not?: Schema | SchemaRef;
335
+ if?: Schema | SchemaRef;
336
+ then?: Schema | SchemaRef;
337
+ else?: Schema | SchemaRef;
338
+ }
339
+
340
+ export interface SchemaRef {
341
+ $ref: string;
342
+ }
343
+
344
+ export interface ValidationResult {
345
+ valid: boolean;
346
+ errors: ValidationError[];
347
+ }
348
+
349
+ export interface ValidationError {
350
+ path: string;
351
+ message: string;
352
+ keyword: string;
353
+ params: Record<string, any>;
354
+ }
355
+
356
+ /**
357
+ * Schema Validator
358
+ */
359
+ export class SchemaValidator {
360
+ private ajv: Ajv;
361
+ private schemas: Map<string, ValidateFunction> = new Map();
362
+
363
+ constructor() {
364
+ this.ajv = new Ajv({
365
+ allErrors: true,
366
+ strict: false,
367
+ validateFormats: true,
368
+ });
369
+ addFormats(this.ajv);
370
+ }
371
+
372
+ /**
373
+ * Register a schema
374
+ */
375
+ register(id: string, schema: Schema): void {
376
+ this.ajv.addSchema({ ...schema, $id: id });
377
+ this.schemas.set(id, this.ajv.compile({ $ref: id }));
378
+ }
379
+
380
+ /**
381
+ * Validate data against a registered schema
382
+ */
383
+ validate(schemaId: string, data: any): ValidationResult {
384
+ const validator = this.schemas.get(schemaId);
385
+
386
+ if (!validator) {
387
+ return {
388
+ valid: false,
389
+ errors: [
390
+ {
391
+ path: '',
392
+ message: \`Schema not found: \${schemaId}\`,
393
+ keyword: 'schema',
394
+ params: { schemaId },
395
+ },
396
+ ],
397
+ };
398
+ }
399
+
400
+ const valid = validator(data);
401
+
402
+ if (valid) {
403
+ return { valid: true, errors: [] };
404
+ }
405
+
406
+ return {
407
+ valid: false,
408
+ errors: (validator.errors || []).map(error => ({
409
+ path: error.instancePath || '/',
410
+ message: error.message || 'Validation failed',
411
+ keyword: error.keyword,
412
+ params: error.params,
413
+ })),
414
+ };
415
+ }
416
+
417
+ /**
418
+ * Validate data against an inline schema
419
+ */
420
+ validateInline(schema: Schema, data: any): ValidationResult {
421
+ const validate = this.ajv.compile(schema);
422
+ const valid = validate(data);
423
+
424
+ if (valid) {
425
+ return { valid: true, errors: [] };
426
+ }
427
+
428
+ return {
429
+ valid: false,
430
+ errors: (validate.errors || []).map(error => ({
431
+ path: error.instancePath || '/',
432
+ message: error.message || 'Validation failed',
433
+ keyword: error.keyword,
434
+ params: error.params,
435
+ })),
436
+ };
437
+ }
438
+
439
+ /**
440
+ * Check if schema is registered
441
+ */
442
+ has(schemaId: string): boolean {
443
+ return this.schemas.has(schemaId);
444
+ }
445
+
446
+ /**
447
+ * Remove a schema
448
+ */
449
+ remove(schemaId: string): void {
450
+ this.ajv.removeSchema(schemaId);
451
+ this.schemas.delete(schemaId);
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Schema builder for fluent API
457
+ */
458
+ export class SchemaBuilder {
459
+ private schema: Schema = { type: 'object' };
460
+
461
+ type(type: string): this {
462
+ this.schema.type = type;
463
+ return this;
464
+ }
465
+
466
+ property(name: string, schema: Schema | SchemaRef): this {
467
+ if (!this.schema.properties) {
468
+ this.schema.properties = {};
469
+ }
470
+ this.schema.properties[name] = schema;
471
+ return this;
472
+ }
473
+
474
+ required(...fields: string[]): this {
475
+ this.schema.required = fields;
476
+ return this;
477
+ }
478
+
479
+ items(schema: Schema | SchemaRef): this {
480
+ this.schema.items = schema;
481
+ return this;
482
+ }
483
+
484
+ enum(...values: any[]): this {
485
+ this.schema.enum = values;
486
+ return this;
487
+ }
488
+
489
+ format(format: string): this {
490
+ this.schema.format = format;
491
+ return this;
492
+ }
493
+
494
+ pattern(pattern: string): this {
495
+ this.schema.pattern = pattern;
496
+ return this;
497
+ }
498
+
499
+ min(value: number): this {
500
+ this.schema.minimum = value;
501
+ return this;
502
+ }
503
+
504
+ max(value: number): this {
505
+ this.schema.maximum = value;
506
+ return this;
507
+ }
508
+
509
+ build(): Schema {
510
+ return { ...this.schema };
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Create schema builder
516
+ */
517
+ export function schema(): SchemaBuilder {
518
+ return new SchemaBuilder();
519
+ }
520
+
521
+ /**
522
+ * Common schema templates
523
+ */
524
+ export const CommonSchemas = {
525
+ uuid: { type: 'string', format: 'uuid' } as Schema,
526
+ email: { type: 'string', format: 'email' } as Schema,
527
+ date: { type: 'string', format: 'date' } as Schema,
528
+ dateTime: { type: 'string', format: 'date-time' } as Schema,
529
+ uri: { type: 'string', format: 'uri' } as Schema,
530
+ positiveInteger: { type: 'integer', minimum: 1 } as Schema,
531
+ nonNegativeInteger: { type: 'integer', minimum: 0 } as Schema,
532
+
533
+ pagination: {
534
+ type: 'object',
535
+ properties: {
536
+ page: { type: 'integer', minimum: 1 },
537
+ pageSize: { type: 'integer', minimum: 1, maximum: 100 },
538
+ total: { type: 'integer', minimum: 0 },
539
+ totalPages: { type: 'integer', minimum: 0 },
540
+ },
541
+ required: ['page', 'pageSize', 'total', 'totalPages'],
542
+ } as Schema,
543
+
544
+ error: {
545
+ type: 'object',
546
+ properties: {
547
+ statusCode: { type: 'integer' },
548
+ message: { type: 'string' },
549
+ error: { type: 'string' },
550
+ },
551
+ required: ['statusCode', 'message'],
552
+ } as Schema,
553
+ };
554
+ `;
555
+ }
556
+ function generateConsumerTestHelpers() {
557
+ return `/**
558
+ * Consumer Test Helpers
559
+ * Helpers for consumer-driven contract testing
560
+ */
561
+
562
+ import { ContractValidator, Contract, EndpointContract } from './contract.validator';
563
+
564
+ export interface Interaction {
565
+ description: string;
566
+ request: {
567
+ method: string;
568
+ path: string;
569
+ headers?: Record<string, string>;
570
+ query?: Record<string, string>;
571
+ body?: any;
572
+ };
573
+ response: {
574
+ statusCode: number;
575
+ headers?: Record<string, string>;
576
+ body?: any;
577
+ };
578
+ }
579
+
580
+ export interface Pact {
581
+ consumer: string;
582
+ provider: string;
583
+ interactions: Interaction[];
584
+ }
585
+
586
+ /**
587
+ * Consumer Contract Builder
588
+ * Creates pact-style consumer contracts
589
+ */
590
+ export class ConsumerContractBuilder {
591
+ private pact: Pact;
592
+ private currentInteraction: Partial<Interaction> | null = null;
593
+
594
+ constructor(consumer: string, provider: string) {
595
+ this.pact = {
596
+ consumer,
597
+ provider,
598
+ interactions: [],
599
+ };
600
+ }
601
+
602
+ /**
603
+ * Start new interaction
604
+ */
605
+ given(description: string): this {
606
+ if (this.currentInteraction) {
607
+ this.commitInteraction();
608
+ }
609
+ this.currentInteraction = { description };
610
+ return this;
611
+ }
612
+
613
+ /**
614
+ * Define request
615
+ */
616
+ uponReceiving(method: string, path: string): this {
617
+ if (!this.currentInteraction) {
618
+ throw new Error('Call given() first');
619
+ }
620
+ this.currentInteraction.request = { method, path };
621
+ return this;
622
+ }
623
+
624
+ /**
625
+ * Add request headers
626
+ */
627
+ withHeaders(headers: Record<string, string>): this {
628
+ if (!this.currentInteraction?.request) {
629
+ throw new Error('Call uponReceiving() first');
630
+ }
631
+ this.currentInteraction.request.headers = headers;
632
+ return this;
633
+ }
634
+
635
+ /**
636
+ * Add request query
637
+ */
638
+ withQuery(query: Record<string, string>): this {
639
+ if (!this.currentInteraction?.request) {
640
+ throw new Error('Call uponReceiving() first');
641
+ }
642
+ this.currentInteraction.request.query = query;
643
+ return this;
644
+ }
645
+
646
+ /**
647
+ * Add request body
648
+ */
649
+ withBody(body: any): this {
650
+ if (!this.currentInteraction?.request) {
651
+ throw new Error('Call uponReceiving() first');
652
+ }
653
+ this.currentInteraction.request.body = body;
654
+ return this;
655
+ }
656
+
657
+ /**
658
+ * Define expected response
659
+ */
660
+ willRespondWith(statusCode: number): this {
661
+ if (!this.currentInteraction) {
662
+ throw new Error('Call given() first');
663
+ }
664
+ this.currentInteraction.response = { statusCode };
665
+ return this;
666
+ }
667
+
668
+ /**
669
+ * Add response headers
670
+ */
671
+ withResponseHeaders(headers: Record<string, string>): this {
672
+ if (!this.currentInteraction?.response) {
673
+ throw new Error('Call willRespondWith() first');
674
+ }
675
+ this.currentInteraction.response.headers = headers;
676
+ return this;
677
+ }
678
+
679
+ /**
680
+ * Add response body
681
+ */
682
+ withResponseBody(body: any): this {
683
+ if (!this.currentInteraction?.response) {
684
+ throw new Error('Call willRespondWith() first');
685
+ }
686
+ this.currentInteraction.response.body = body;
687
+ return this;
688
+ }
689
+
690
+ /**
691
+ * Build the pact
692
+ */
693
+ build(): Pact {
694
+ if (this.currentInteraction) {
695
+ this.commitInteraction();
696
+ }
697
+ return this.pact;
698
+ }
699
+
700
+ private commitInteraction(): void {
701
+ if (
702
+ this.currentInteraction?.description &&
703
+ this.currentInteraction?.request &&
704
+ this.currentInteraction?.response
705
+ ) {
706
+ this.pact.interactions.push(this.currentInteraction as Interaction);
707
+ }
708
+ this.currentInteraction = null;
709
+ }
710
+ }
711
+
712
+ /**
713
+ * Consumer test runner
714
+ */
715
+ export class ConsumerTestRunner {
716
+ private validator: ContractValidator;
717
+
718
+ constructor() {
719
+ this.validator = new ContractValidator();
720
+ }
721
+
722
+ /**
723
+ * Run consumer tests against a mock provider
724
+ */
725
+ async runTests(
726
+ pact: Pact,
727
+ mockProvider: (interaction: Interaction) => Promise<any>,
728
+ ): Promise<TestResult[]> {
729
+ const results: TestResult[] = [];
730
+
731
+ for (const interaction of pact.interactions) {
732
+ const result = await this.runInteraction(interaction, mockProvider);
733
+ results.push(result);
734
+ }
735
+
736
+ return results;
737
+ }
738
+
739
+ private async runInteraction(
740
+ interaction: Interaction,
741
+ mockProvider: (interaction: Interaction) => Promise<any>,
742
+ ): Promise<TestResult> {
743
+ try {
744
+ const response = await mockProvider(interaction);
745
+
746
+ // Validate response matches expected
747
+ const matches = this.matchResponse(interaction.response, response);
748
+
749
+ return {
750
+ description: interaction.description,
751
+ passed: matches.passed,
752
+ errors: matches.errors,
753
+ };
754
+ } catch (error) {
755
+ return {
756
+ description: interaction.description,
757
+ passed: false,
758
+ errors: [(error as Error).message],
759
+ };
760
+ }
761
+ }
762
+
763
+ private matchResponse(
764
+ expected: Interaction['response'],
765
+ actual: any,
766
+ ): { passed: boolean; errors: string[] } {
767
+ const errors: string[] = [];
768
+
769
+ if (actual.statusCode !== expected.statusCode) {
770
+ errors.push(
771
+ \`Status code mismatch: expected \${expected.statusCode}, got \${actual.statusCode}\`,
772
+ );
773
+ }
774
+
775
+ // Deep compare bodies if provided
776
+ if (expected.body) {
777
+ const bodyMatch = this.deepMatch(expected.body, actual.body);
778
+ if (!bodyMatch.matches) {
779
+ errors.push(\`Body mismatch: \${bodyMatch.path}\`);
780
+ }
781
+ }
782
+
783
+ return {
784
+ passed: errors.length === 0,
785
+ errors,
786
+ };
787
+ }
788
+
789
+ private deepMatch(
790
+ expected: any,
791
+ actual: any,
792
+ path: string = '',
793
+ ): { matches: boolean; path: string } {
794
+ if (typeof expected !== typeof actual) {
795
+ return { matches: false, path: path || 'root' };
796
+ }
797
+
798
+ if (expected === null || actual === null) {
799
+ return { matches: expected === actual, path };
800
+ }
801
+
802
+ if (typeof expected !== 'object') {
803
+ return { matches: expected === actual, path };
804
+ }
805
+
806
+ if (Array.isArray(expected)) {
807
+ if (!Array.isArray(actual) || expected.length !== actual.length) {
808
+ return { matches: false, path };
809
+ }
810
+ for (let i = 0; i < expected.length; i++) {
811
+ const result = this.deepMatch(expected[i], actual[i], \`\${path}[\${i}]\`);
812
+ if (!result.matches) return result;
813
+ }
814
+ return { matches: true, path };
815
+ }
816
+
817
+ for (const key of Object.keys(expected)) {
818
+ const result = this.deepMatch(
819
+ expected[key],
820
+ actual[key],
821
+ path ? \`\${path}.\${key}\` : key,
822
+ );
823
+ if (!result.matches) return result;
824
+ }
825
+
826
+ return { matches: true, path };
827
+ }
828
+ }
829
+
830
+ interface TestResult {
831
+ description: string;
832
+ passed: boolean;
833
+ errors: string[];
834
+ }
835
+
836
+ /**
837
+ * Create consumer contract builder
838
+ */
839
+ export function consumerContract(consumer: string, provider: string): ConsumerContractBuilder {
840
+ return new ConsumerContractBuilder(consumer, provider);
841
+ }
842
+ `;
843
+ }
844
+ function generateProviderTestHelpers() {
845
+ return `/**
846
+ * Provider Test Helpers
847
+ * Helpers for provider-side contract verification
848
+ */
849
+
850
+ import { INestApplication } from '@nestjs/common';
851
+ import * as request from 'supertest';
852
+ import { Pact, Interaction } from './consumer-test.helpers';
853
+ import { ContractValidator } from './contract.validator';
854
+
855
+ export interface ProviderState {
856
+ name: string;
857
+ setup: () => Promise<void>;
858
+ teardown?: () => Promise<void>;
859
+ }
860
+
861
+ export interface VerificationResult {
862
+ interaction: string;
863
+ passed: boolean;
864
+ errors: string[];
865
+ duration: number;
866
+ }
867
+
868
+ /**
869
+ * Provider Verifier
870
+ * Verifies provider against consumer contracts
871
+ */
872
+ export class ProviderVerifier {
873
+ private states: Map<string, ProviderState> = new Map();
874
+ private validator: ContractValidator;
875
+
876
+ constructor(private readonly app: INestApplication) {
877
+ this.validator = new ContractValidator();
878
+ }
879
+
880
+ /**
881
+ * Register provider state
882
+ */
883
+ registerState(state: ProviderState): this {
884
+ this.states.set(state.name, state);
885
+ return this;
886
+ }
887
+
888
+ /**
889
+ * Verify all interactions in a pact
890
+ */
891
+ async verify(pact: Pact): Promise<VerificationResult[]> {
892
+ const results: VerificationResult[] = [];
893
+
894
+ for (const interaction of pact.interactions) {
895
+ const result = await this.verifyInteraction(interaction);
896
+ results.push(result);
897
+ }
898
+
899
+ return results;
900
+ }
901
+
902
+ /**
903
+ * Verify single interaction
904
+ */
905
+ async verifyInteraction(interaction: Interaction): Promise<VerificationResult> {
906
+ const startTime = Date.now();
907
+ const errors: string[] = [];
908
+
909
+ try {
910
+ // Setup provider state if exists
911
+ const state = this.states.get(interaction.description);
912
+ if (state) {
913
+ await state.setup();
914
+ }
915
+
916
+ // Make request
917
+ const response = await this.makeRequest(interaction);
918
+
919
+ // Verify response
920
+ if (response.status !== interaction.response.statusCode) {
921
+ errors.push(
922
+ \`Status code mismatch: expected \${interaction.response.statusCode}, got \${response.status}\`,
923
+ );
924
+ }
925
+
926
+ // Verify response body
927
+ if (interaction.response.body) {
928
+ const bodyErrors = this.verifyBody(interaction.response.body, response.body);
929
+ errors.push(...bodyErrors);
930
+ }
931
+
932
+ // Verify response headers
933
+ if (interaction.response.headers) {
934
+ const headerErrors = this.verifyHeaders(interaction.response.headers, response.headers);
935
+ errors.push(...headerErrors);
936
+ }
937
+
938
+ // Teardown provider state
939
+ if (state?.teardown) {
940
+ await state.teardown();
941
+ }
942
+
943
+ return {
944
+ interaction: interaction.description,
945
+ passed: errors.length === 0,
946
+ errors,
947
+ duration: Date.now() - startTime,
948
+ };
949
+ } catch (error) {
950
+ return {
951
+ interaction: interaction.description,
952
+ passed: false,
953
+ errors: [(error as Error).message],
954
+ duration: Date.now() - startTime,
955
+ };
956
+ }
957
+ }
958
+
959
+ private async makeRequest(interaction: Interaction): Promise<any> {
960
+ const req = interaction.request;
961
+ let testRequest = request(this.app.getHttpServer());
962
+
963
+ switch (req.method.toUpperCase()) {
964
+ case 'GET':
965
+ testRequest = testRequest.get(req.path);
966
+ break;
967
+ case 'POST':
968
+ testRequest = testRequest.post(req.path);
969
+ break;
970
+ case 'PUT':
971
+ testRequest = testRequest.put(req.path);
972
+ break;
973
+ case 'PATCH':
974
+ testRequest = testRequest.patch(req.path);
975
+ break;
976
+ case 'DELETE':
977
+ testRequest = testRequest.delete(req.path);
978
+ break;
979
+ }
980
+
981
+ if (req.headers) {
982
+ for (const [key, value] of Object.entries(req.headers)) {
983
+ testRequest = testRequest.set(key, value);
984
+ }
985
+ }
986
+
987
+ if (req.query) {
988
+ testRequest = testRequest.query(req.query);
989
+ }
990
+
991
+ if (req.body) {
992
+ testRequest = testRequest.send(req.body);
993
+ }
994
+
995
+ return testRequest;
996
+ }
997
+
998
+ private verifyBody(expected: any, actual: any): string[] {
999
+ const errors: string[] = [];
1000
+ this.compareObjects(expected, actual, '', errors);
1001
+ return errors;
1002
+ }
1003
+
1004
+ private verifyHeaders(expected: Record<string, string>, actual: any): string[] {
1005
+ const errors: string[] = [];
1006
+
1007
+ for (const [key, value] of Object.entries(expected)) {
1008
+ const actualValue = actual[key.toLowerCase()];
1009
+ if (actualValue !== value) {
1010
+ errors.push(\`Header '\${key}' mismatch: expected '\${value}', got '\${actualValue}'\`);
1011
+ }
1012
+ }
1013
+
1014
+ return errors;
1015
+ }
1016
+
1017
+ private compareObjects(expected: any, actual: any, path: string, errors: string[]): void {
1018
+ if (expected === null || expected === undefined) {
1019
+ if (actual !== null && actual !== undefined) {
1020
+ errors.push(\`\${path || 'root'}: expected null/undefined, got \${typeof actual}\`);
1021
+ }
1022
+ return;
1023
+ }
1024
+
1025
+ if (typeof expected !== typeof actual) {
1026
+ errors.push(\`\${path || 'root'}: type mismatch - expected \${typeof expected}, got \${typeof actual}\`);
1027
+ return;
1028
+ }
1029
+
1030
+ if (Array.isArray(expected)) {
1031
+ if (!Array.isArray(actual)) {
1032
+ errors.push(\`\${path || 'root'}: expected array, got \${typeof actual}\`);
1033
+ return;
1034
+ }
1035
+
1036
+ for (let i = 0; i < expected.length; i++) {
1037
+ this.compareObjects(expected[i], actual[i], \`\${path}[\${i}]\`, errors);
1038
+ }
1039
+ return;
1040
+ }
1041
+
1042
+ if (typeof expected === 'object') {
1043
+ for (const key of Object.keys(expected)) {
1044
+ this.compareObjects(
1045
+ expected[key],
1046
+ actual?.[key],
1047
+ path ? \`\${path}.\${key}\` : key,
1048
+ errors,
1049
+ );
1050
+ }
1051
+ return;
1052
+ }
1053
+
1054
+ if (expected !== actual) {
1055
+ errors.push(\`\${path || 'root'}: value mismatch - expected \${expected}, got \${actual}\`);
1056
+ }
1057
+ }
1058
+ }
1059
+
1060
+ /**
1061
+ * Create provider verifier
1062
+ */
1063
+ export function createVerifier(app: INestApplication): ProviderVerifier {
1064
+ return new ProviderVerifier(app);
1065
+ }
1066
+ `;
1067
+ }
1068
+ function generateContractStore() {
1069
+ return `/**
1070
+ * Contract Store
1071
+ * Centralized storage for API contracts
1072
+ */
1073
+
1074
+ import * as fs from 'fs';
1075
+ import * as path from 'path';
1076
+ import { Contract } from './contract.validator';
1077
+ import { Pact } from './consumer-test.helpers';
1078
+
1079
+ export interface StoredContract {
1080
+ id: string;
1081
+ contract: Contract | Pact;
1082
+ version: string;
1083
+ createdAt: Date;
1084
+ updatedAt: Date;
1085
+ metadata?: Record<string, any>;
1086
+ }
1087
+
1088
+ /**
1089
+ * Contract Store
1090
+ */
1091
+ export class ContractStore {
1092
+ private contracts: Map<string, StoredContract> = new Map();
1093
+ private basePath: string;
1094
+
1095
+ constructor(basePath?: string) {
1096
+ this.basePath = basePath || path.join(process.cwd(), 'contracts');
1097
+ this.ensureDirectory();
1098
+ this.loadContracts();
1099
+ }
1100
+
1101
+ /**
1102
+ * Store a contract
1103
+ */
1104
+ store(id: string, contract: Contract | Pact, version: string, metadata?: Record<string, any>): void {
1105
+ const stored: StoredContract = {
1106
+ id,
1107
+ contract,
1108
+ version,
1109
+ createdAt: new Date(),
1110
+ updatedAt: new Date(),
1111
+ metadata,
1112
+ };
1113
+
1114
+ this.contracts.set(id, stored);
1115
+ this.persist(id, stored);
1116
+ }
1117
+
1118
+ /**
1119
+ * Get a contract
1120
+ */
1121
+ get(id: string): StoredContract | undefined {
1122
+ return this.contracts.get(id);
1123
+ }
1124
+
1125
+ /**
1126
+ * Get contract by provider and consumer
1127
+ */
1128
+ getByProviderConsumer(provider: string, consumer: string): StoredContract[] {
1129
+ return Array.from(this.contracts.values()).filter(stored => {
1130
+ const contract = stored.contract;
1131
+ if ('provider' in contract) {
1132
+ return contract.provider === provider &&
1133
+ ('consumer' in contract ? contract.consumer === consumer : true);
1134
+ }
1135
+ return false;
1136
+ });
1137
+ }
1138
+
1139
+ /**
1140
+ * List all contracts
1141
+ */
1142
+ list(): StoredContract[] {
1143
+ return Array.from(this.contracts.values());
1144
+ }
1145
+
1146
+ /**
1147
+ * Remove a contract
1148
+ */
1149
+ remove(id: string): boolean {
1150
+ const existed = this.contracts.delete(id);
1151
+ if (existed) {
1152
+ this.deleteFile(id);
1153
+ }
1154
+ return existed;
1155
+ }
1156
+
1157
+ /**
1158
+ * Get contract versions
1159
+ */
1160
+ getVersions(providerId: string): string[] {
1161
+ return Array.from(this.contracts.values())
1162
+ .filter(stored => stored.id.startsWith(providerId))
1163
+ .map(stored => stored.version)
1164
+ .filter((v, i, a) => a.indexOf(v) === i)
1165
+ .sort();
1166
+ }
1167
+
1168
+ /**
1169
+ * Compare two contract versions
1170
+ */
1171
+ compareVersions(id: string, version1: string, version2: string): ContractDiff | null {
1172
+ const c1 = this.contracts.get(\`\${id}:\${version1}\`);
1173
+ const c2 = this.contracts.get(\`\${id}:\${version2}\`);
1174
+
1175
+ if (!c1 || !c2) return null;
1176
+
1177
+ return this.diffContracts(c1.contract, c2.contract);
1178
+ }
1179
+
1180
+ private ensureDirectory(): void {
1181
+ if (!fs.existsSync(this.basePath)) {
1182
+ fs.mkdirSync(this.basePath, { recursive: true });
1183
+ }
1184
+ }
1185
+
1186
+ private loadContracts(): void {
1187
+ if (!fs.existsSync(this.basePath)) return;
1188
+
1189
+ const files = fs.readdirSync(this.basePath).filter(f => f.endsWith('.json'));
1190
+
1191
+ for (const file of files) {
1192
+ try {
1193
+ const content = fs.readFileSync(path.join(this.basePath, file), 'utf-8');
1194
+ const stored = JSON.parse(content) as StoredContract;
1195
+ stored.createdAt = new Date(stored.createdAt);
1196
+ stored.updatedAt = new Date(stored.updatedAt);
1197
+ this.contracts.set(stored.id, stored);
1198
+ } catch (error) {
1199
+ console.warn(\`Failed to load contract: \${file}\`);
1200
+ }
1201
+ }
1202
+ }
1203
+
1204
+ private persist(id: string, stored: StoredContract): void {
1205
+ const filePath = path.join(this.basePath, \`\${id.replace(/[:/]/g, '_')}.json\`);
1206
+ fs.writeFileSync(filePath, JSON.stringify(stored, null, 2));
1207
+ }
1208
+
1209
+ private deleteFile(id: string): void {
1210
+ const filePath = path.join(this.basePath, \`\${id.replace(/[:/]/g, '_')}.json\`);
1211
+ if (fs.existsSync(filePath)) {
1212
+ fs.unlinkSync(filePath);
1213
+ }
1214
+ }
1215
+
1216
+ private diffContracts(c1: Contract | Pact, c2: Contract | Pact): ContractDiff {
1217
+ const diff: ContractDiff = {
1218
+ added: [],
1219
+ removed: [],
1220
+ modified: [],
1221
+ };
1222
+
1223
+ // Simple diff based on endpoints/interactions
1224
+ const e1 = this.getEndpoints(c1);
1225
+ const e2 = this.getEndpoints(c2);
1226
+
1227
+ const keys1 = new Set(e1.map(e => e.key));
1228
+ const keys2 = new Set(e2.map(e => e.key));
1229
+
1230
+ for (const e of e2) {
1231
+ if (!keys1.has(e.key)) {
1232
+ diff.added.push(e.key);
1233
+ }
1234
+ }
1235
+
1236
+ for (const e of e1) {
1237
+ if (!keys2.has(e.key)) {
1238
+ diff.removed.push(e.key);
1239
+ }
1240
+ }
1241
+
1242
+ return diff;
1243
+ }
1244
+
1245
+ private getEndpoints(contract: Contract | Pact): { key: string; data: any }[] {
1246
+ if ('endpoints' in contract) {
1247
+ return contract.endpoints.map(e => ({
1248
+ key: \`\${e.method}:\${e.path}\`,
1249
+ data: e,
1250
+ }));
1251
+ }
1252
+ if ('interactions' in contract) {
1253
+ return contract.interactions.map(i => ({
1254
+ key: \`\${i.request.method}:\${i.request.path}\`,
1255
+ data: i,
1256
+ }));
1257
+ }
1258
+ return [];
1259
+ }
1260
+ }
1261
+
1262
+ interface ContractDiff {
1263
+ added: string[];
1264
+ removed: string[];
1265
+ modified: string[];
1266
+ }
1267
+
1268
+ /**
1269
+ * Create contract store
1270
+ */
1271
+ export function createContractStore(basePath?: string): ContractStore {
1272
+ return new ContractStore(basePath);
1273
+ }
1274
+ `;
1275
+ }
1276
+ function generateSampleContractTest() {
1277
+ return `/**
1278
+ * Sample Contract Test
1279
+ * Example of consumer-driven contract testing
1280
+ */
1281
+
1282
+ import { consumerContract, ConsumerTestRunner } from '../src/shared/contracts/consumer-test.helpers';
1283
+ import { createVerifier } from '../src/shared/contracts/provider-test.helpers';
1284
+ import { ContractValidator } from '../src/shared/contracts/contract.validator';
1285
+
1286
+ describe('User API Contract', () => {
1287
+ describe('Consumer Contract', () => {
1288
+ it('should define user creation contract', () => {
1289
+ const pact = consumerContract('web-app', 'user-service')
1290
+ .given('user creation')
1291
+ .uponReceiving('POST', '/api/users')
1292
+ .withHeaders({ 'Content-Type': 'application/json' })
1293
+ .withBody({
1294
+ email: 'test@example.com',
1295
+ name: 'Test User',
1296
+ })
1297
+ .willRespondWith(201)
1298
+ .withResponseBody({
1299
+ id: expect.any(String),
1300
+ email: 'test@example.com',
1301
+ name: 'Test User',
1302
+ createdAt: expect.any(String),
1303
+ })
1304
+ .given('get user by id')
1305
+ .uponReceiving('GET', '/api/users/123')
1306
+ .willRespondWith(200)
1307
+ .withResponseBody({
1308
+ id: '123',
1309
+ email: 'test@example.com',
1310
+ name: 'Test User',
1311
+ })
1312
+ .build();
1313
+
1314
+ expect(pact.consumer).toBe('web-app');
1315
+ expect(pact.provider).toBe('user-service');
1316
+ expect(pact.interactions).toHaveLength(2);
1317
+ });
1318
+ });
1319
+
1320
+ describe('Schema Validation', () => {
1321
+ it('should validate user response schema', () => {
1322
+ const validator = new ContractValidator();
1323
+
1324
+ validator.registerContract({
1325
+ name: 'user-api',
1326
+ version: '1.0.0',
1327
+ provider: 'user-service',
1328
+ endpoints: [
1329
+ {
1330
+ method: 'GET',
1331
+ path: '/api/users/:id',
1332
+ response: {
1333
+ statusCode: 200,
1334
+ body: {
1335
+ type: 'object',
1336
+ properties: {
1337
+ id: { type: 'string' },
1338
+ email: { type: 'string', format: 'email' },
1339
+ name: { type: 'string', minLength: 1 },
1340
+ },
1341
+ required: ['id', 'email', 'name'],
1342
+ },
1343
+ },
1344
+ },
1345
+ ],
1346
+ });
1347
+
1348
+ const validResult = validator.validateResponse('GET', '/api/users/:id', 200, {
1349
+ id: '123',
1350
+ email: 'test@example.com',
1351
+ name: 'Test User',
1352
+ });
1353
+
1354
+ expect(validResult.valid).toBe(true);
1355
+
1356
+ const invalidResult = validator.validateResponse('GET', '/api/users/:id', 200, {
1357
+ id: '123',
1358
+ email: 'invalid-email',
1359
+ name: '',
1360
+ });
1361
+
1362
+ expect(invalidResult.valid).toBe(false);
1363
+ });
1364
+ });
1365
+ });
1366
+ `;
1367
+ }
1368
+ //# sourceMappingURL=api-contracts.js.map