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,824 @@
1
+ "use strict";
2
+ /**
3
+ * Feature Flags & Toggles Framework Generator
4
+ * Runtime feature management with A/B testing support
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.setupFeatureFlags = setupFeatureFlags;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const chalk_1 = __importDefault(require("chalk"));
47
+ async function setupFeatureFlags(basePath, options = {}) {
48
+ console.log(chalk_1.default.bold.blue('\n🚩 Setting up Feature Flags Framework\n'));
49
+ const sharedPath = path.join(basePath, 'src/shared/features');
50
+ if (!fs.existsSync(sharedPath)) {
51
+ fs.mkdirSync(sharedPath, { recursive: true });
52
+ }
53
+ // Generate feature flag service
54
+ fs.writeFileSync(path.join(sharedPath, 'feature-flag.service.ts'), generateFeatureFlagService());
55
+ console.log(chalk_1.default.green(` ✓ Created feature flag service`));
56
+ // Generate feature decorator
57
+ fs.writeFileSync(path.join(sharedPath, 'feature.decorator.ts'), generateFeatureDecorator());
58
+ console.log(chalk_1.default.green(` ✓ Created feature decorator`));
59
+ // Generate feature guard
60
+ fs.writeFileSync(path.join(sharedPath, 'feature.guard.ts'), generateFeatureGuard());
61
+ console.log(chalk_1.default.green(` ✓ Created feature guard`));
62
+ // Generate A/B testing service
63
+ fs.writeFileSync(path.join(sharedPath, 'ab-testing.service.ts'), generateABTestingService());
64
+ console.log(chalk_1.default.green(` ✓ Created A/B testing service`));
65
+ // Generate feature flag entity
66
+ fs.writeFileSync(path.join(sharedPath, 'feature-flag.entity.ts'), generateFeatureFlagEntity());
67
+ console.log(chalk_1.default.green(` ✓ Created feature flag entity`));
68
+ // Generate feature module
69
+ fs.writeFileSync(path.join(sharedPath, 'feature.module.ts'), generateFeatureModule());
70
+ console.log(chalk_1.default.green(` ✓ Created feature module`));
71
+ console.log(chalk_1.default.bold.green('\n✅ Feature flags framework ready!\n'));
72
+ }
73
+ function generateFeatureFlagService() {
74
+ return `/**
75
+ * Feature Flag Service
76
+ * Manages feature toggles at runtime
77
+ */
78
+
79
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
80
+ import { EventEmitter2 } from '@nestjs/event-emitter';
81
+
82
+ export interface FeatureFlag {
83
+ key: string;
84
+ enabled: boolean;
85
+ description?: string;
86
+ conditions?: FeatureCondition[];
87
+ variants?: FeatureVariant[];
88
+ percentage?: number;
89
+ metadata?: Record<string, any>;
90
+ createdAt: Date;
91
+ updatedAt: Date;
92
+ }
93
+
94
+ export interface FeatureCondition {
95
+ type: 'user' | 'group' | 'percentage' | 'date' | 'custom';
96
+ operator: 'equals' | 'contains' | 'gt' | 'lt' | 'in' | 'not_in';
97
+ field?: string;
98
+ value: any;
99
+ }
100
+
101
+ export interface FeatureVariant {
102
+ name: string;
103
+ weight: number;
104
+ payload?: any;
105
+ }
106
+
107
+ export interface FeatureContext {
108
+ userId?: string;
109
+ userGroups?: string[];
110
+ attributes?: Record<string, any>;
111
+ sessionId?: string;
112
+ }
113
+
114
+ @Injectable()
115
+ export class FeatureFlagService implements OnModuleInit {
116
+ private readonly logger = new Logger(FeatureFlagService.name);
117
+ private readonly flags = new Map<string, FeatureFlag>();
118
+ private readonly overrides = new Map<string, Map<string, boolean>>();
119
+
120
+ constructor(private readonly eventEmitter: EventEmitter2) {}
121
+
122
+ async onModuleInit() {
123
+ await this.loadFlags();
124
+ }
125
+
126
+ /**
127
+ * Check if a feature is enabled
128
+ */
129
+ isEnabled(key: string, context?: FeatureContext): boolean {
130
+ // Check for user override
131
+ if (context?.userId) {
132
+ const userOverrides = this.overrides.get(context.userId);
133
+ if (userOverrides?.has(key)) {
134
+ return userOverrides.get(key)!;
135
+ }
136
+ }
137
+
138
+ const flag = this.flags.get(key);
139
+ if (!flag) {
140
+ this.logger.warn(\`Feature flag '\${key}' not found\`);
141
+ return false;
142
+ }
143
+
144
+ if (!flag.enabled) {
145
+ return false;
146
+ }
147
+
148
+ // Check conditions
149
+ if (flag.conditions && flag.conditions.length > 0) {
150
+ return this.evaluateConditions(flag.conditions, context);
151
+ }
152
+
153
+ // Check percentage rollout
154
+ if (flag.percentage !== undefined) {
155
+ return this.evaluatePercentage(key, flag.percentage, context);
156
+ }
157
+
158
+ return true;
159
+ }
160
+
161
+ /**
162
+ * Get feature variant
163
+ */
164
+ getVariant(key: string, context?: FeatureContext): FeatureVariant | null {
165
+ const flag = this.flags.get(key);
166
+ if (!flag || !flag.variants || flag.variants.length === 0) {
167
+ return null;
168
+ }
169
+
170
+ if (!this.isEnabled(key, context)) {
171
+ return null;
172
+ }
173
+
174
+ return this.selectVariant(key, flag.variants, context);
175
+ }
176
+
177
+ /**
178
+ * Create or update a feature flag
179
+ */
180
+ async setFlag(key: string, config: Partial<FeatureFlag>): Promise<FeatureFlag> {
181
+ const existing = this.flags.get(key);
182
+ const flag: FeatureFlag = {
183
+ key,
184
+ enabled: config.enabled ?? existing?.enabled ?? false,
185
+ description: config.description ?? existing?.description,
186
+ conditions: config.conditions ?? existing?.conditions,
187
+ variants: config.variants ?? existing?.variants,
188
+ percentage: config.percentage ?? existing?.percentage,
189
+ metadata: config.metadata ?? existing?.metadata,
190
+ createdAt: existing?.createdAt ?? new Date(),
191
+ updatedAt: new Date(),
192
+ };
193
+
194
+ this.flags.set(key, flag);
195
+ this.eventEmitter.emit('feature-flag.updated', { key, flag });
196
+
197
+ return flag;
198
+ }
199
+
200
+ /**
201
+ * Enable a feature
202
+ */
203
+ async enable(key: string): Promise<void> {
204
+ await this.setFlag(key, { enabled: true });
205
+ this.logger.log(\`Feature '\${key}' enabled\`);
206
+ }
207
+
208
+ /**
209
+ * Disable a feature
210
+ */
211
+ async disable(key: string): Promise<void> {
212
+ await this.setFlag(key, { enabled: false });
213
+ this.logger.log(\`Feature '\${key}' disabled\`);
214
+ }
215
+
216
+ /**
217
+ * Set user override
218
+ */
219
+ setUserOverride(userId: string, key: string, enabled: boolean): void {
220
+ let userOverrides = this.overrides.get(userId);
221
+ if (!userOverrides) {
222
+ userOverrides = new Map();
223
+ this.overrides.set(userId, userOverrides);
224
+ }
225
+ userOverrides.set(key, enabled);
226
+ }
227
+
228
+ /**
229
+ * Remove user override
230
+ */
231
+ removeUserOverride(userId: string, key: string): void {
232
+ const userOverrides = this.overrides.get(userId);
233
+ if (userOverrides) {
234
+ userOverrides.delete(key);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Get all flags
240
+ */
241
+ getAllFlags(): FeatureFlag[] {
242
+ return Array.from(this.flags.values());
243
+ }
244
+
245
+ /**
246
+ * Get flag by key
247
+ */
248
+ getFlag(key: string): FeatureFlag | undefined {
249
+ return this.flags.get(key);
250
+ }
251
+
252
+ /**
253
+ * Delete a flag
254
+ */
255
+ async deleteFlag(key: string): Promise<void> {
256
+ this.flags.delete(key);
257
+ this.eventEmitter.emit('feature-flag.deleted', { key });
258
+ }
259
+
260
+ private async loadFlags(): Promise<void> {
261
+ // Override this method to load flags from database/external source
262
+ this.logger.log('Feature flags loaded');
263
+ }
264
+
265
+ private evaluateConditions(conditions: FeatureCondition[], context?: FeatureContext): boolean {
266
+ if (!context) return false;
267
+
268
+ return conditions.every(condition => {
269
+ switch (condition.type) {
270
+ case 'user':
271
+ return this.evaluateUserCondition(condition, context);
272
+ case 'group':
273
+ return this.evaluateGroupCondition(condition, context);
274
+ case 'percentage':
275
+ return this.evaluatePercentageCondition(condition, context);
276
+ case 'date':
277
+ return this.evaluateDateCondition(condition);
278
+ case 'custom':
279
+ return this.evaluateCustomCondition(condition, context);
280
+ default:
281
+ return false;
282
+ }
283
+ });
284
+ }
285
+
286
+ private evaluateUserCondition(condition: FeatureCondition, context: FeatureContext): boolean {
287
+ if (!context.userId) return false;
288
+
289
+ switch (condition.operator) {
290
+ case 'equals':
291
+ return context.userId === condition.value;
292
+ case 'in':
293
+ return Array.isArray(condition.value) && condition.value.includes(context.userId);
294
+ case 'not_in':
295
+ return Array.isArray(condition.value) && !condition.value.includes(context.userId);
296
+ default:
297
+ return false;
298
+ }
299
+ }
300
+
301
+ private evaluateGroupCondition(condition: FeatureCondition, context: FeatureContext): boolean {
302
+ if (!context.userGroups) return false;
303
+
304
+ switch (condition.operator) {
305
+ case 'contains':
306
+ return context.userGroups.includes(condition.value);
307
+ case 'in':
308
+ return Array.isArray(condition.value) &&
309
+ condition.value.some(g => context.userGroups!.includes(g));
310
+ default:
311
+ return false;
312
+ }
313
+ }
314
+
315
+ private evaluatePercentageCondition(condition: FeatureCondition, context: FeatureContext): boolean {
316
+ const hash = this.hashString(context.userId || context.sessionId || 'anonymous');
317
+ return (hash % 100) < condition.value;
318
+ }
319
+
320
+ private evaluateDateCondition(condition: FeatureCondition): boolean {
321
+ const now = new Date();
322
+ const targetDate = new Date(condition.value);
323
+
324
+ switch (condition.operator) {
325
+ case 'gt':
326
+ return now > targetDate;
327
+ case 'lt':
328
+ return now < targetDate;
329
+ default:
330
+ return false;
331
+ }
332
+ }
333
+
334
+ private evaluateCustomCondition(condition: FeatureCondition, context: FeatureContext): boolean {
335
+ if (!context.attributes || !condition.field) return false;
336
+
337
+ const value = context.attributes[condition.field];
338
+
339
+ switch (condition.operator) {
340
+ case 'equals':
341
+ return value === condition.value;
342
+ case 'contains':
343
+ return String(value).includes(condition.value);
344
+ case 'in':
345
+ return Array.isArray(condition.value) && condition.value.includes(value);
346
+ default:
347
+ return false;
348
+ }
349
+ }
350
+
351
+ private evaluatePercentage(key: string, percentage: number, context?: FeatureContext): boolean {
352
+ const identifier = context?.userId || context?.sessionId || 'anonymous';
353
+ const hash = this.hashString(\`\${key}:\${identifier}\`);
354
+ return (hash % 100) < percentage;
355
+ }
356
+
357
+ private selectVariant(key: string, variants: FeatureVariant[], context?: FeatureContext): FeatureVariant {
358
+ const identifier = context?.userId || context?.sessionId || 'anonymous';
359
+ const hash = this.hashString(\`\${key}:variant:\${identifier}\`);
360
+ const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);
361
+ let random = hash % totalWeight;
362
+
363
+ for (const variant of variants) {
364
+ random -= variant.weight;
365
+ if (random < 0) {
366
+ return variant;
367
+ }
368
+ }
369
+
370
+ return variants[variants.length - 1];
371
+ }
372
+
373
+ private hashString(str: string): number {
374
+ let hash = 0;
375
+ for (let i = 0; i < str.length; i++) {
376
+ const char = str.charCodeAt(i);
377
+ hash = ((hash << 5) - hash) + char;
378
+ hash = hash & hash;
379
+ }
380
+ return Math.abs(hash);
381
+ }
382
+ }
383
+ `;
384
+ }
385
+ function generateFeatureDecorator() {
386
+ return `/**
387
+ * Feature Decorators
388
+ * Mark endpoints and methods with feature flag requirements
389
+ */
390
+
391
+ import { SetMetadata, applyDecorators, UseGuards } from '@nestjs/common';
392
+ import { FeatureGuard } from './feature.guard';
393
+
394
+ export const FEATURE_KEY = 'feature_flag_key';
395
+ export const FEATURE_FALLBACK = 'feature_fallback';
396
+
397
+ /**
398
+ * Require a feature flag to be enabled
399
+ */
400
+ export function Feature(key: string, options?: { fallback?: any }) {
401
+ return applyDecorators(
402
+ SetMetadata(FEATURE_KEY, key),
403
+ SetMetadata(FEATURE_FALLBACK, options?.fallback),
404
+ UseGuards(FeatureGuard),
405
+ );
406
+ }
407
+
408
+ /**
409
+ * Mark as beta feature
410
+ */
411
+ export function BetaFeature(key: string) {
412
+ return Feature(\`beta:\${key}\`);
413
+ }
414
+
415
+ /**
416
+ * Mark as experimental feature
417
+ */
418
+ export function ExperimentalFeature(key: string) {
419
+ return Feature(\`experimental:\${key}\`);
420
+ }
421
+
422
+ /**
423
+ * Mark feature with variant requirement
424
+ */
425
+ export function FeatureVariant(key: string, variant: string) {
426
+ return applyDecorators(
427
+ SetMetadata(FEATURE_KEY, key),
428
+ SetMetadata('feature_variant', variant),
429
+ UseGuards(FeatureGuard),
430
+ );
431
+ }
432
+
433
+ /**
434
+ * Skip feature flag check (useful for overriding in tests)
435
+ */
436
+ export function SkipFeatureCheck() {
437
+ return SetMetadata('skip_feature_check', true);
438
+ }
439
+ `;
440
+ }
441
+ function generateFeatureGuard() {
442
+ return `/**
443
+ * Feature Guard
444
+ * Protects routes based on feature flags
445
+ */
446
+
447
+ import {
448
+ Injectable,
449
+ CanActivate,
450
+ ExecutionContext,
451
+ ForbiddenException,
452
+ } from '@nestjs/common';
453
+ import { Reflector } from '@nestjs/core';
454
+ import { FeatureFlagService, FeatureContext } from './feature-flag.service';
455
+ import { FEATURE_KEY, FEATURE_FALLBACK } from './feature.decorator';
456
+
457
+ @Injectable()
458
+ export class FeatureGuard implements CanActivate {
459
+ constructor(
460
+ private readonly reflector: Reflector,
461
+ private readonly featureService: FeatureFlagService,
462
+ ) {}
463
+
464
+ async canActivate(context: ExecutionContext): Promise<boolean> {
465
+ const skipCheck = this.reflector.get<boolean>('skip_feature_check', context.getHandler());
466
+ if (skipCheck) {
467
+ return true;
468
+ }
469
+
470
+ const featureKey = this.reflector.get<string>(FEATURE_KEY, context.getHandler());
471
+ if (!featureKey) {
472
+ return true;
473
+ }
474
+
475
+ const request = context.switchToHttp().getRequest();
476
+ const featureContext = this.extractContext(request);
477
+
478
+ const isEnabled = this.featureService.isEnabled(featureKey, featureContext);
479
+
480
+ if (!isEnabled) {
481
+ const fallback = this.reflector.get<any>(FEATURE_FALLBACK, context.getHandler());
482
+ if (fallback !== undefined) {
483
+ // Store fallback in request for controller to use
484
+ request.featureFallback = fallback;
485
+ return true;
486
+ }
487
+
488
+ throw new ForbiddenException(\`Feature '\${featureKey}' is not available\`);
489
+ }
490
+
491
+ // Check variant if specified
492
+ const requiredVariant = this.reflector.get<string>('feature_variant', context.getHandler());
493
+ if (requiredVariant) {
494
+ const variant = this.featureService.getVariant(featureKey, featureContext);
495
+ if (!variant || variant.name !== requiredVariant) {
496
+ throw new ForbiddenException(\`Feature variant '\${requiredVariant}' is not available\`);
497
+ }
498
+ request.featureVariant = variant;
499
+ }
500
+
501
+ return true;
502
+ }
503
+
504
+ private extractContext(request: any): FeatureContext {
505
+ return {
506
+ userId: request.user?.id,
507
+ userGroups: request.user?.groups || request.user?.roles,
508
+ sessionId: request.sessionID || request.headers['x-session-id'],
509
+ attributes: {
510
+ ip: request.ip,
511
+ userAgent: request.headers['user-agent'],
512
+ ...request.user,
513
+ },
514
+ };
515
+ }
516
+ }
517
+ `;
518
+ }
519
+ function generateABTestingService() {
520
+ return `/**
521
+ * A/B Testing Service
522
+ * Manage experiments and variant assignments
523
+ */
524
+
525
+ import { Injectable, Logger } from '@nestjs/common';
526
+ import { EventEmitter2 } from '@nestjs/event-emitter';
527
+
528
+ export interface Experiment {
529
+ id: string;
530
+ name: string;
531
+ description?: string;
532
+ variants: ExperimentVariant[];
533
+ status: 'draft' | 'running' | 'paused' | 'completed';
534
+ targetPercentage: number;
535
+ startDate?: Date;
536
+ endDate?: Date;
537
+ metrics: string[];
538
+ createdAt: Date;
539
+ updatedAt: Date;
540
+ }
541
+
542
+ export interface ExperimentVariant {
543
+ id: string;
544
+ name: string;
545
+ weight: number;
546
+ isControl: boolean;
547
+ payload?: any;
548
+ }
549
+
550
+ export interface ExperimentAssignment {
551
+ experimentId: string;
552
+ variantId: string;
553
+ userId: string;
554
+ assignedAt: Date;
555
+ }
556
+
557
+ export interface ExperimentResult {
558
+ experimentId: string;
559
+ variantId: string;
560
+ metric: string;
561
+ value: number;
562
+ sampleSize: number;
563
+ confidence?: number;
564
+ }
565
+
566
+ @Injectable()
567
+ export class ABTestingService {
568
+ private readonly logger = new Logger(ABTestingService.name);
569
+ private readonly experiments = new Map<string, Experiment>();
570
+ private readonly assignments = new Map<string, ExperimentAssignment>();
571
+
572
+ constructor(private readonly eventEmitter: EventEmitter2) {}
573
+
574
+ /**
575
+ * Create an experiment
576
+ */
577
+ createExperiment(config: Omit<Experiment, 'createdAt' | 'updatedAt' | 'status'>): Experiment {
578
+ const experiment: Experiment = {
579
+ ...config,
580
+ status: 'draft',
581
+ createdAt: new Date(),
582
+ updatedAt: new Date(),
583
+ };
584
+
585
+ this.experiments.set(experiment.id, experiment);
586
+ this.eventEmitter.emit('experiment.created', experiment);
587
+
588
+ return experiment;
589
+ }
590
+
591
+ /**
592
+ * Start an experiment
593
+ */
594
+ startExperiment(id: string): Experiment {
595
+ const experiment = this.experiments.get(id);
596
+ if (!experiment) {
597
+ throw new Error(\`Experiment '\${id}' not found\`);
598
+ }
599
+
600
+ experiment.status = 'running';
601
+ experiment.startDate = new Date();
602
+ experiment.updatedAt = new Date();
603
+
604
+ this.eventEmitter.emit('experiment.started', experiment);
605
+ return experiment;
606
+ }
607
+
608
+ /**
609
+ * Stop an experiment
610
+ */
611
+ stopExperiment(id: string): Experiment {
612
+ const experiment = this.experiments.get(id);
613
+ if (!experiment) {
614
+ throw new Error(\`Experiment '\${id}' not found\`);
615
+ }
616
+
617
+ experiment.status = 'completed';
618
+ experiment.endDate = new Date();
619
+ experiment.updatedAt = new Date();
620
+
621
+ this.eventEmitter.emit('experiment.stopped', experiment);
622
+ return experiment;
623
+ }
624
+
625
+ /**
626
+ * Get variant for user
627
+ */
628
+ getVariant(experimentId: string, userId: string): ExperimentVariant | null {
629
+ const experiment = this.experiments.get(experimentId);
630
+ if (!experiment || experiment.status !== 'running') {
631
+ return null;
632
+ }
633
+
634
+ // Check existing assignment
635
+ const assignmentKey = \`\${experimentId}:\${userId}\`;
636
+ const existing = this.assignments.get(assignmentKey);
637
+ if (existing) {
638
+ return experiment.variants.find(v => v.id === existing.variantId) || null;
639
+ }
640
+
641
+ // Check if user is in target percentage
642
+ if (!this.isInTarget(experimentId, userId, experiment.targetPercentage)) {
643
+ return null;
644
+ }
645
+
646
+ // Assign variant
647
+ const variant = this.selectVariant(experimentId, userId, experiment.variants);
648
+
649
+ const assignment: ExperimentAssignment = {
650
+ experimentId,
651
+ variantId: variant.id,
652
+ userId,
653
+ assignedAt: new Date(),
654
+ };
655
+ this.assignments.set(assignmentKey, assignment);
656
+
657
+ this.eventEmitter.emit('experiment.assigned', {
658
+ experimentId,
659
+ variantId: variant.id,
660
+ userId,
661
+ });
662
+
663
+ return variant;
664
+ }
665
+
666
+ /**
667
+ * Track experiment event
668
+ */
669
+ trackEvent(experimentId: string, userId: string, metric: string, value: number = 1): void {
670
+ const assignmentKey = \`\${experimentId}:\${userId}\`;
671
+ const assignment = this.assignments.get(assignmentKey);
672
+
673
+ if (!assignment) {
674
+ this.logger.warn(\`No assignment found for user \${userId} in experiment \${experimentId}\`);
675
+ return;
676
+ }
677
+
678
+ this.eventEmitter.emit('experiment.event', {
679
+ experimentId,
680
+ variantId: assignment.variantId,
681
+ userId,
682
+ metric,
683
+ value,
684
+ timestamp: new Date(),
685
+ });
686
+ }
687
+
688
+ /**
689
+ * Get experiment results
690
+ */
691
+ getResults(experimentId: string): ExperimentResult[] {
692
+ // This would aggregate tracked events
693
+ // Implementation depends on your analytics storage
694
+ return [];
695
+ }
696
+
697
+ /**
698
+ * List all experiments
699
+ */
700
+ listExperiments(): Experiment[] {
701
+ return Array.from(this.experiments.values());
702
+ }
703
+
704
+ /**
705
+ * Get experiment by ID
706
+ */
707
+ getExperiment(id: string): Experiment | undefined {
708
+ return this.experiments.get(id);
709
+ }
710
+
711
+ private isInTarget(experimentId: string, userId: string, percentage: number): boolean {
712
+ const hash = this.hashString(\`\${experimentId}:target:\${userId}\`);
713
+ return (hash % 100) < percentage;
714
+ }
715
+
716
+ private selectVariant(experimentId: string, userId: string, variants: ExperimentVariant[]): ExperimentVariant {
717
+ const hash = this.hashString(\`\${experimentId}:variant:\${userId}\`);
718
+ const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);
719
+ let random = hash % totalWeight;
720
+
721
+ for (const variant of variants) {
722
+ random -= variant.weight;
723
+ if (random < 0) {
724
+ return variant;
725
+ }
726
+ }
727
+
728
+ return variants[variants.length - 1];
729
+ }
730
+
731
+ private hashString(str: string): number {
732
+ let hash = 0;
733
+ for (let i = 0; i < str.length; i++) {
734
+ const char = str.charCodeAt(i);
735
+ hash = ((hash << 5) - hash) + char;
736
+ hash = hash & hash;
737
+ }
738
+ return Math.abs(hash);
739
+ }
740
+ }
741
+ `;
742
+ }
743
+ function generateFeatureFlagEntity() {
744
+ return `import {
745
+ Entity,
746
+ PrimaryColumn,
747
+ Column,
748
+ CreateDateColumn,
749
+ UpdateDateColumn,
750
+ } from 'typeorm';
751
+
752
+ @Entity('feature_flags')
753
+ export class FeatureFlagEntity {
754
+ @PrimaryColumn()
755
+ key: string;
756
+
757
+ @Column({ default: false })
758
+ enabled: boolean;
759
+
760
+ @Column({ nullable: true })
761
+ description: string;
762
+
763
+ @Column({ type: 'jsonb', nullable: true })
764
+ conditions: any;
765
+
766
+ @Column({ type: 'jsonb', nullable: true })
767
+ variants: any;
768
+
769
+ @Column({ type: 'float', nullable: true })
770
+ percentage: number;
771
+
772
+ @Column({ type: 'jsonb', nullable: true })
773
+ metadata: any;
774
+
775
+ @CreateDateColumn()
776
+ createdAt: Date;
777
+
778
+ @UpdateDateColumn()
779
+ updatedAt: Date;
780
+ }
781
+ `;
782
+ }
783
+ function generateFeatureModule() {
784
+ return `import { Module, Global, DynamicModule } from '@nestjs/common';
785
+ import { TypeOrmModule } from '@nestjs/typeorm';
786
+ import { FeatureFlagService } from './feature-flag.service';
787
+ import { FeatureGuard } from './feature.guard';
788
+ import { ABTestingService } from './ab-testing.service';
789
+ import { FeatureFlagEntity } from './feature-flag.entity';
790
+
791
+ export interface FeatureModuleOptions {
792
+ provider?: 'memory' | 'database' | 'redis';
793
+ refreshInterval?: number;
794
+ }
795
+
796
+ @Global()
797
+ @Module({})
798
+ export class FeatureModule {
799
+ static forRoot(options: FeatureModuleOptions = {}): DynamicModule {
800
+ const imports = [];
801
+
802
+ if (options.provider === 'database') {
803
+ imports.push(TypeOrmModule.forFeature([FeatureFlagEntity]));
804
+ }
805
+
806
+ return {
807
+ module: FeatureModule,
808
+ imports,
809
+ providers: [
810
+ {
811
+ provide: 'FEATURE_OPTIONS',
812
+ useValue: options,
813
+ },
814
+ FeatureFlagService,
815
+ ABTestingService,
816
+ FeatureGuard,
817
+ ],
818
+ exports: [FeatureFlagService, ABTestingService, FeatureGuard],
819
+ };
820
+ }
821
+ }
822
+ `;
823
+ }
824
+ //# sourceMappingURL=feature-flags.js.map