@plyaz/core 1.0.0

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 (82) hide show
  1. package/README.md +439 -0
  2. package/dist/backend/featureFlags/feature-flag.controller.d.ts +135 -0
  3. package/dist/backend/featureFlags/feature-flag.controller.d.ts.map +1 -0
  4. package/dist/backend/featureFlags/feature-flag.module.d.ts +114 -0
  5. package/dist/backend/featureFlags/feature-flag.module.d.ts.map +1 -0
  6. package/dist/backend/featureFlags/feature-flag.repository.d.ts +85 -0
  7. package/dist/backend/featureFlags/feature-flag.repository.d.ts.map +1 -0
  8. package/dist/backend/featureFlags/feature-flag.service.d.ts +123 -0
  9. package/dist/backend/featureFlags/feature-flag.service.d.ts.map +1 -0
  10. package/dist/backend/featureFlags/index.d.ts +49 -0
  11. package/dist/backend/featureFlags/index.d.ts.map +1 -0
  12. package/dist/backend/index.d.ts +5 -0
  13. package/dist/backend/index.d.ts.map +1 -0
  14. package/dist/cache/index.d.ts +98 -0
  15. package/dist/cache/index.d.ts.map +1 -0
  16. package/dist/cache/strategies/memory.d.ts +103 -0
  17. package/dist/cache/strategies/memory.d.ts.map +1 -0
  18. package/dist/cache/strategies/redis.d.ts +105 -0
  19. package/dist/cache/strategies/redis.d.ts.map +1 -0
  20. package/dist/domain/featureFlags/index.d.ts +49 -0
  21. package/dist/domain/featureFlags/index.d.ts.map +1 -0
  22. package/dist/domain/featureFlags/provider.d.ts +166 -0
  23. package/dist/domain/featureFlags/provider.d.ts.map +1 -0
  24. package/dist/domain/featureFlags/providers/api.d.ts +78 -0
  25. package/dist/domain/featureFlags/providers/api.d.ts.map +1 -0
  26. package/dist/domain/featureFlags/providers/database.d.ts +102 -0
  27. package/dist/domain/featureFlags/providers/database.d.ts.map +1 -0
  28. package/dist/domain/featureFlags/providers/factory.d.ts +116 -0
  29. package/dist/domain/featureFlags/providers/factory.d.ts.map +1 -0
  30. package/dist/domain/featureFlags/providers/file.d.ts +84 -0
  31. package/dist/domain/featureFlags/providers/file.d.ts.map +1 -0
  32. package/dist/domain/featureFlags/providers/memory.d.ts +179 -0
  33. package/dist/domain/featureFlags/providers/memory.d.ts.map +1 -0
  34. package/dist/domain/featureFlags/providers/redis.d.ts +80 -0
  35. package/dist/domain/featureFlags/providers/redis.d.ts.map +1 -0
  36. package/dist/domain/index.d.ts +6 -0
  37. package/dist/domain/index.d.ts.map +1 -0
  38. package/dist/domain/types.d.ts +18 -0
  39. package/dist/domain/types.d.ts.map +1 -0
  40. package/dist/engine/featureFlags/engine.d.ts +193 -0
  41. package/dist/engine/featureFlags/engine.d.ts.map +1 -0
  42. package/dist/engine/featureFlags/index.d.ts +10 -0
  43. package/dist/engine/featureFlags/index.d.ts.map +1 -0
  44. package/dist/engine/index.d.ts +5 -0
  45. package/dist/engine/index.d.ts.map +1 -0
  46. package/dist/frontend/featureFlags/hooks/useFeatureFlag.d.ts +103 -0
  47. package/dist/frontend/featureFlags/hooks/useFeatureFlag.d.ts.map +1 -0
  48. package/dist/frontend/featureFlags/hooks/useFeatureFlagActions.d.ts +35 -0
  49. package/dist/frontend/featureFlags/hooks/useFeatureFlagActions.d.ts.map +1 -0
  50. package/dist/frontend/featureFlags/hooks/useFeatureFlagHelpers.d.ts +55 -0
  51. package/dist/frontend/featureFlags/hooks/useFeatureFlagHelpers.d.ts.map +1 -0
  52. package/dist/frontend/featureFlags/hooks/useFeatureFlagProvider.d.ts +57 -0
  53. package/dist/frontend/featureFlags/hooks/useFeatureFlagProvider.d.ts.map +1 -0
  54. package/dist/frontend/featureFlags/index.d.ts +14 -0
  55. package/dist/frontend/featureFlags/index.d.ts.map +1 -0
  56. package/dist/frontend/featureFlags/providers/FeatureFlagProvider.d.ts +99 -0
  57. package/dist/frontend/featureFlags/providers/FeatureFlagProvider.d.ts.map +1 -0
  58. package/dist/frontend/featureFlags/providers/FeatureFlagProviderHelpers.d.ts +45 -0
  59. package/dist/frontend/featureFlags/providers/FeatureFlagProviderHelpers.d.ts.map +1 -0
  60. package/dist/frontend/index.d.ts +2 -0
  61. package/dist/frontend/index.d.ts.map +1 -0
  62. package/dist/index.cjs +3951 -0
  63. package/dist/index.cjs.map +1 -0
  64. package/dist/index.d.ts +7 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.mjs +3902 -0
  67. package/dist/index.mjs.map +1 -0
  68. package/dist/utils/common/hash.d.ts +81 -0
  69. package/dist/utils/common/hash.d.ts.map +1 -0
  70. package/dist/utils/common/index.d.ts +11 -0
  71. package/dist/utils/common/index.d.ts.map +1 -0
  72. package/dist/utils/common/values.d.ts +48 -0
  73. package/dist/utils/common/values.d.ts.map +1 -0
  74. package/dist/utils/featureFlags/conditions.d.ts +114 -0
  75. package/dist/utils/featureFlags/conditions.d.ts.map +1 -0
  76. package/dist/utils/featureFlags/context.d.ts +241 -0
  77. package/dist/utils/featureFlags/context.d.ts.map +1 -0
  78. package/dist/utils/featureFlags/index.d.ts +11 -0
  79. package/dist/utils/featureFlags/index.d.ts.map +1 -0
  80. package/dist/utils/index.d.ts +6 -0
  81. package/dist/utils/index.d.ts.map +1 -0
  82. package/package.json +144 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,3951 @@
1
+ 'use strict';
2
+
3
+ var config = require('@plyaz/config');
4
+ var common = require('@nestjs/common');
5
+ var React = require('react');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var React__default = /*#__PURE__*/_interopDefault(React);
11
+
12
+ // @plyaz package - Built with tsup
13
+ var __defProp = Object.defineProperty;
14
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
15
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
16
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
17
+ var __decorateClass = (decorators, target, key, kind) => {
18
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
19
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
20
+ if (decorator = decorators[i])
21
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
22
+ if (kind && result) __defProp(target, key, result);
23
+ return result;
24
+ };
25
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
26
+ var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
27
+
28
+ // src/utils/common/hash.ts
29
+ function hashString(str) {
30
+ const HASH_SHIFT = 5;
31
+ let hash = 0;
32
+ for (let i = 0; i < str.length; i++) {
33
+ const char = str.charCodeAt(i);
34
+ hash = (hash << HASH_SHIFT) - hash + char;
35
+ hash = hash & hash;
36
+ }
37
+ return Math.abs(hash);
38
+ }
39
+ __name(hashString, "hashString");
40
+ function isInRollout(identifier, percentage) {
41
+ if (percentage >= 100) return true;
42
+ if (percentage <= 0) return false;
43
+ const hash = hashString(identifier);
44
+ return hash % 100 < percentage;
45
+ }
46
+ __name(isInRollout, "isInRollout");
47
+ function createRolloutIdentifier(featureKey, userId) {
48
+ const trimmedUserId = userId?.trim();
49
+ const effectiveUserId = trimmedUserId && trimmedUserId.length > 0 ? trimmedUserId : "anonymous";
50
+ return `${featureKey}:${effectiveUserId}`;
51
+ }
52
+ __name(createRolloutIdentifier, "createRolloutIdentifier");
53
+ var HashUtils = {
54
+ /**
55
+ * Generates a hash-based bucket for load balancing or distribution.
56
+ *
57
+ * @param identifier - Unique identifier
58
+ * @param bucketCount - Number of buckets (default: 10)
59
+ * @returns Bucket number (0 to bucketCount-1)
60
+ */
61
+ getBucket: /* @__PURE__ */ __name((identifier, bucketCount = 10) => {
62
+ return hashString(identifier) % bucketCount;
63
+ }, "getBucket"),
64
+ /**
65
+ * Checks if an identifier falls within a specific bucket range.
66
+ *
67
+ * @param identifier - Unique identifier
68
+ * @param startBucket - Starting bucket (inclusive)
69
+ * @param endBucket - Ending bucket (inclusive)
70
+ * @param totalBuckets - Total number of buckets (default: 100)
71
+ * @returns true if identifier is in the bucket range
72
+ */
73
+ isInBucketRange: /* @__PURE__ */ __name((identifier, startBucket, endBucket, totalBuckets = 100) => {
74
+ const bucket = hashString(identifier) % totalBuckets;
75
+ return bucket >= startBucket && bucket <= endBucket;
76
+ }, "isInBucketRange"),
77
+ /**
78
+ * Creates a deterministic random seed from a string.
79
+ *
80
+ * @param str - String to convert to seed
81
+ * @returns Deterministic seed value
82
+ */
83
+ createSeed: /* @__PURE__ */ __name((str) => {
84
+ const SAFE_INT = 2147483647;
85
+ return hashString(str) % SAFE_INT;
86
+ }, "createSeed")
87
+ };
88
+
89
+ // src/utils/common/values.ts
90
+ function isStringFalsy(value) {
91
+ if (value === "") return true;
92
+ const lower = value.toLowerCase().trim();
93
+ return ["false", "no", "0", "off", "disabled"].includes(lower);
94
+ }
95
+ __name(isStringFalsy, "isStringFalsy");
96
+ function isObjectTruthy(value) {
97
+ if (Array.isArray(value)) return value.length > 0;
98
+ if (value instanceof Map || value instanceof Set) return value.size > 0;
99
+ if (value.constructor === Object) return Object.keys(value).length > 0;
100
+ return true;
101
+ }
102
+ __name(isObjectTruthy, "isObjectTruthy");
103
+ function isTruthy(value) {
104
+ if (value === null || value === void 0) return false;
105
+ switch (typeof value) {
106
+ case "boolean":
107
+ return value;
108
+ case "string":
109
+ return !isStringFalsy(value);
110
+ case "number":
111
+ return value !== 0 && !isNaN(value);
112
+ case "function":
113
+ case "symbol":
114
+ return true;
115
+ case "object":
116
+ return isObjectTruthy(value);
117
+ default:
118
+ return false;
119
+ }
120
+ }
121
+ __name(isTruthy, "isTruthy");
122
+ function parseStringToBoolean(value, defaultValue) {
123
+ const lower = value.toLowerCase();
124
+ if (["true", "yes", "1", "on", "enabled"].includes(lower)) return true;
125
+ if (["false", "no", "0", "off", "disabled"].includes(lower)) return false;
126
+ return defaultValue;
127
+ }
128
+ __name(parseStringToBoolean, "parseStringToBoolean");
129
+ function toBoolean(value, defaultValue = false) {
130
+ if (value === null || value === void 0) return false;
131
+ switch (typeof value) {
132
+ case "boolean":
133
+ return value;
134
+ case "string":
135
+ return parseStringToBoolean(value, defaultValue);
136
+ case "number":
137
+ return value !== 0 && !isNaN(value);
138
+ case "object":
139
+ case "function":
140
+ return true;
141
+ default:
142
+ return defaultValue;
143
+ }
144
+ }
145
+ __name(toBoolean, "toBoolean");
146
+ var ValueUtils = {
147
+ /**
148
+ * Checks if a value is a valid percentage (0-100).
149
+ *
150
+ * @param value - Value to check
151
+ * @returns true if valid percentage
152
+ */
153
+ isValidPercentage: /* @__PURE__ */ __name((value) => {
154
+ if (typeof value !== "number") return false;
155
+ return !isNaN(value) && isFinite(value) && value >= 0 && value <= 100;
156
+ }, "isValidPercentage"),
157
+ /**
158
+ * Clamps a number to a specific range.
159
+ *
160
+ * @param value - Value to clamp
161
+ * @param min - Minimum value
162
+ * @param max - Maximum value
163
+ * @returns Clamped value
164
+ */
165
+ clamp: /* @__PURE__ */ __name((value, min, max) => {
166
+ return Math.min(Math.max(value, min), max);
167
+ }, "clamp"),
168
+ /**
169
+ * Checks if a value is empty (null, undefined, empty string, empty array).
170
+ *
171
+ * @param value - Value to check
172
+ * @returns true if empty
173
+ */
174
+ isEmpty: /* @__PURE__ */ __name((value) => {
175
+ if (value === null || value === void 0) return true;
176
+ if (typeof value === "string") return value.trim() === "";
177
+ if (Array.isArray(value)) return value.length === 0;
178
+ if (typeof value === "object") return Object.keys(value).length === 0;
179
+ return false;
180
+ }, "isEmpty"),
181
+ /**
182
+ * Safely gets a nested property from an object.
183
+ *
184
+ * @param obj - Object to query
185
+ * @param path - Dot-separated path (e.g., 'user.profile.name')
186
+ * @param defaultValue - Default if path doesn't exist
187
+ * @returns Property value or default
188
+ */
189
+ getNestedProperty: /* @__PURE__ */ __name((obj, path, defaultValue) => {
190
+ if (!obj || typeof obj !== "object") return defaultValue;
191
+ const keys = path.split(".");
192
+ let current = obj;
193
+ for (const key of keys) {
194
+ if (current == null || typeof current !== "object") return defaultValue;
195
+ current = current[key];
196
+ if (current === void 0) return defaultValue;
197
+ }
198
+ return current;
199
+ }, "getNestedProperty")
200
+ };
201
+
202
+ // src/utils/featureFlags/context.ts
203
+ var FeatureFlagContextBuilder = class _FeatureFlagContextBuilder {
204
+ static {
205
+ __name(this, "FeatureFlagContextBuilder");
206
+ }
207
+ context = {};
208
+ /**
209
+ * Sets the user ID in the context.
210
+ *
211
+ * @param userId - User identifier
212
+ * @returns Builder instance for chaining
213
+ */
214
+ setUserId(userId) {
215
+ this.context.userId = userId;
216
+ return this;
217
+ }
218
+ /**
219
+ * Sets the user email in the context.
220
+ *
221
+ * @param userEmail - User email address
222
+ * @returns Builder instance for chaining
223
+ */
224
+ setUserEmail(userEmail) {
225
+ this.context.userEmail = userEmail;
226
+ return this;
227
+ }
228
+ /**
229
+ * Sets the user role in the context.
230
+ *
231
+ * @param userRole - User role or permission level
232
+ * @returns Builder instance for chaining
233
+ */
234
+ setUserRole(userRole) {
235
+ this.context.userRole = userRole;
236
+ return this;
237
+ }
238
+ /**
239
+ * Sets the country in the context.
240
+ *
241
+ * @param country - Country code (ISO 3166-1 alpha-2)
242
+ * @returns Builder instance for chaining
243
+ */
244
+ setCountry(country) {
245
+ this.context.country = country;
246
+ return this;
247
+ }
248
+ /**
249
+ * Sets the platform in the context.
250
+ *
251
+ * @param platform - Platform type
252
+ * @returns Builder instance for chaining
253
+ */
254
+ setPlatform(platform) {
255
+ this.context.platform = platform;
256
+ return this;
257
+ }
258
+ /**
259
+ * Sets the version in the context.
260
+ *
261
+ * @param version - Application version
262
+ * @returns Builder instance for chaining
263
+ */
264
+ setVersion(version) {
265
+ this.context.version = version;
266
+ return this;
267
+ }
268
+ /**
269
+ * Sets the environment in the context.
270
+ *
271
+ * @param environment - Current environment
272
+ * @returns Builder instance for chaining
273
+ */
274
+ setEnvironment(environment) {
275
+ this.context.environment = environment;
276
+ return this;
277
+ }
278
+ /**
279
+ * Sets custom context data.
280
+ *
281
+ * @param custom - Custom context properties
282
+ * @returns Builder instance for chaining
283
+ */
284
+ setCustom(custom) {
285
+ this.context.custom = { ...this.context.custom, ...custom };
286
+ return this;
287
+ }
288
+ /**
289
+ * Adds a single custom property to the context.
290
+ *
291
+ * @param key - Custom property key
292
+ * @param value - Custom property value
293
+ * @returns Builder instance for chaining
294
+ */
295
+ addCustomProperty(key, value) {
296
+ this.context.custom ??= {};
297
+ this.context.custom[key] = value;
298
+ return this;
299
+ }
300
+ /**
301
+ * Builds the final context object.
302
+ * Validates required fields and returns the context.
303
+ *
304
+ * @returns Complete feature flag context
305
+ * @throws Error if required environment is not set
306
+ */
307
+ build() {
308
+ return {
309
+ environment: this.context.environment ?? "development",
310
+ userId: this.context.userId,
311
+ userEmail: this.context.userEmail,
312
+ userRole: this.context.userRole,
313
+ country: this.context.country,
314
+ platform: this.context.platform,
315
+ version: this.context.version,
316
+ custom: this.context.custom
317
+ };
318
+ }
319
+ /**
320
+ * Clears all context data and resets the builder.
321
+ *
322
+ * @returns Builder instance for chaining
323
+ */
324
+ clear() {
325
+ this.context = {};
326
+ return this;
327
+ }
328
+ /**
329
+ * Creates a copy of the current builder state.
330
+ *
331
+ * @returns New builder instance with copied context
332
+ */
333
+ clone() {
334
+ const cloned = new _FeatureFlagContextBuilder();
335
+ cloned.context = { ...this.context };
336
+ if (this.context.custom) {
337
+ cloned.context.custom = { ...this.context.custom };
338
+ }
339
+ return cloned;
340
+ }
341
+ };
342
+ var ContextUtils = {
343
+ /**
344
+ * Creates a basic context for anonymous users using the builder.
345
+ *
346
+ * @param environment - Target environment
347
+ * @param platform - User platform
348
+ * @returns Basic anonymous context
349
+ */
350
+ createAnonymousContext(environment, platform = "web") {
351
+ return new FeatureFlagContextBuilder().setEnvironment(environment).setPlatform(platform).build();
352
+ },
353
+ /**
354
+ * Creates a context for authenticated users using the builder.
355
+ *
356
+ * @param params - User context parameters
357
+ * @returns User context
358
+ */
359
+ createUserContext(params) {
360
+ const builder = new FeatureFlagContextBuilder().setUserId(params.userId).setEnvironment(params.environment);
361
+ if (params.userEmail) builder.setUserEmail(params.userEmail);
362
+ if (params.userRole) builder.setUserRole(params.userRole);
363
+ if (params.platform) builder.setPlatform(params.platform);
364
+ if (params.country) builder.setCountry(params.country);
365
+ if (params.version) builder.setVersion(params.version);
366
+ if (params.custom) builder.setCustom(params.custom);
367
+ return builder.build();
368
+ },
369
+ /**
370
+ * Creates a testing context with minimal required fields.
371
+ *
372
+ * @param overrides - Optional context overrides
373
+ * @returns Testing context
374
+ */
375
+ createTestingContext(overrides = {}) {
376
+ return {
377
+ environment: "development",
378
+ platform: "web",
379
+ ...overrides
380
+ };
381
+ },
382
+ /**
383
+ * Validates if a context object is complete and valid.
384
+ *
385
+ * @param context - Context to validate
386
+ * @returns Validation result
387
+ */
388
+ validateContext(context) {
389
+ const errors = [];
390
+ if (!context.environment) {
391
+ errors.push("Environment is required");
392
+ } else if (!["development", "staging", "production"].includes(context.environment)) {
393
+ errors.push("Environment must be development, staging, or production");
394
+ }
395
+ if (context.platform && !["web", "mobile", "desktop"].includes(context.platform)) {
396
+ errors.push("Platform must be web, mobile, or desktop");
397
+ }
398
+ if (context.country && context.country.length !== 2) {
399
+ errors.push("Country must be a 2-letter ISO country code");
400
+ }
401
+ return {
402
+ isValid: errors.length === 0,
403
+ errors
404
+ };
405
+ },
406
+ /**
407
+ * Merges multiple context objects, with later contexts taking precedence.
408
+ *
409
+ * @param contexts - Array of contexts to merge
410
+ * @returns Merged context
411
+ */
412
+ mergeContexts(...contexts) {
413
+ const merged = contexts.reduce((acc, context) => {
414
+ const result = {
415
+ ...acc,
416
+ ...context
417
+ };
418
+ if (acc.custom || context.custom) {
419
+ result.custom = {
420
+ ...acc.custom,
421
+ ...context.custom
422
+ };
423
+ }
424
+ return result;
425
+ }, {});
426
+ merged.environment ??= "development";
427
+ return merged;
428
+ },
429
+ /**
430
+ * Extracts a specific field value from a context.
431
+ *
432
+ * @param field - Field name to extract
433
+ * @param context - Context object
434
+ * @returns Field value or undefined
435
+ */
436
+ getContextValue(field, context) {
437
+ const standardFields = {
438
+ userId: /* @__PURE__ */ __name((ctx) => ctx.userId, "userId"),
439
+ userEmail: /* @__PURE__ */ __name((ctx) => ctx.userEmail, "userEmail"),
440
+ userRole: /* @__PURE__ */ __name((ctx) => ctx.userRole, "userRole"),
441
+ country: /* @__PURE__ */ __name((ctx) => ctx.country, "country"),
442
+ platform: /* @__PURE__ */ __name((ctx) => ctx.platform, "platform"),
443
+ version: /* @__PURE__ */ __name((ctx) => ctx.version, "version"),
444
+ environment: /* @__PURE__ */ __name((ctx) => ctx.environment, "environment"),
445
+ custom: /* @__PURE__ */ __name((ctx) => ctx.custom, "custom")
446
+ };
447
+ if (field in standardFields) {
448
+ return standardFields[field](context);
449
+ }
450
+ return context.custom?.[field];
451
+ },
452
+ /**
453
+ * Creates a context fingerprint for caching and consistency.
454
+ *
455
+ * @param context - Context to fingerprint
456
+ * @returns String fingerprint
457
+ */
458
+ createFingerprint(context) {
459
+ const relevant = {
460
+ userId: context.userId,
461
+ userRole: context.userRole,
462
+ environment: context.environment,
463
+ platform: context.platform,
464
+ country: context.country,
465
+ version: context.version,
466
+ custom: context.custom
467
+ };
468
+ const filtered = Object.entries(relevant).filter(([, value]) => value !== void 0).sort(([a], [b]) => a.localeCompare(b)).reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
469
+ return JSON.stringify(filtered);
470
+ },
471
+ /**
472
+ * Sanitizes a context by removing sensitive information.
473
+ *
474
+ * @param context - Context to sanitize
475
+ * @param sensitiveFields - Fields to remove (default: ['userEmail'])
476
+ * @returns Sanitized context
477
+ */
478
+ sanitizeContext(context, sensitiveFields = ["userEmail"]) {
479
+ const sanitized = { ...context };
480
+ if (context.custom) {
481
+ sanitized.custom = { ...context.custom };
482
+ }
483
+ for (const field of sensitiveFields) {
484
+ if (field in sanitized) {
485
+ delete sanitized[field];
486
+ }
487
+ if (sanitized.custom && field in sanitized.custom) {
488
+ delete sanitized.custom[field];
489
+ }
490
+ }
491
+ return sanitized;
492
+ }
493
+ };
494
+ function createBackendContext(params) {
495
+ return {
496
+ environment: params.environment,
497
+ userId: params.userId,
498
+ userEmail: params.userEmail,
499
+ userRole: params.userRole,
500
+ platform: params.platform ?? "api",
501
+ country: params.country,
502
+ version: params.version,
503
+ custom: params.custom
504
+ };
505
+ }
506
+ __name(createBackendContext, "createBackendContext");
507
+ function createFrontendContext(params) {
508
+ return {
509
+ environment: params.environment,
510
+ userId: params.userId,
511
+ userEmail: params.userEmail,
512
+ userRole: params.userRole,
513
+ platform: params.platform ?? "web",
514
+ country: params.country,
515
+ version: params.version,
516
+ custom: params.custom
517
+ };
518
+ }
519
+ __name(createFrontendContext, "createFrontendContext");
520
+
521
+ // src/utils/featureFlags/conditions.ts
522
+ function evaluateConditionOperator(condition, contextValue) {
523
+ const { operator } = condition;
524
+ if (isEqualityOperator(operator)) {
525
+ return evaluateEqualityOperator(operator, contextValue, condition.value);
526
+ }
527
+ if (isStringOperator(operator)) {
528
+ return evaluateStringOperator(operator, contextValue, condition.value);
529
+ }
530
+ if (isArrayOperator(operator)) {
531
+ return evaluateArrayOperator(operator, condition.value, contextValue);
532
+ }
533
+ if (isNumericOperator(operator)) {
534
+ return evaluateNumericOperator(operator, contextValue, condition.value);
535
+ }
536
+ return false;
537
+ }
538
+ __name(evaluateConditionOperator, "evaluateConditionOperator");
539
+ function isEqualityOperator(operator) {
540
+ return operator === "equals" || operator === "not_equals";
541
+ }
542
+ __name(isEqualityOperator, "isEqualityOperator");
543
+ function isStringOperator(operator) {
544
+ return operator === "contains" || operator === "not_contains";
545
+ }
546
+ __name(isStringOperator, "isStringOperator");
547
+ function isArrayOperator(operator) {
548
+ return operator === "in" || operator === "not_in";
549
+ }
550
+ __name(isArrayOperator, "isArrayOperator");
551
+ function isNumericOperator(operator) {
552
+ return operator === "greater_than" || operator === "less_than";
553
+ }
554
+ __name(isNumericOperator, "isNumericOperator");
555
+ function evaluateEqualityOperator(operator, contextValue, conditionValue) {
556
+ const isEqual = contextValue === conditionValue;
557
+ return operator === "equals" ? isEqual : !isEqual;
558
+ }
559
+ __name(evaluateEqualityOperator, "evaluateEqualityOperator");
560
+ function evaluateStringOperator(operator, contextValue, conditionValue) {
561
+ if (typeof contextValue === "string" && typeof conditionValue === "string") {
562
+ const contains = contextValue.includes(conditionValue);
563
+ return operator === "contains" ? contains : !contains;
564
+ }
565
+ if (Array.isArray(contextValue) && typeof conditionValue === "string") {
566
+ const contains = contextValue.includes(conditionValue);
567
+ return operator === "contains" ? contains : !contains;
568
+ }
569
+ return operator === "not_contains";
570
+ }
571
+ __name(evaluateStringOperator, "evaluateStringOperator");
572
+ function evaluateArrayOperator(operator, conditionValue, contextValue) {
573
+ if (!Array.isArray(conditionValue)) {
574
+ return operator === "not_in";
575
+ }
576
+ const isIncluded = conditionValue.includes(contextValue);
577
+ return operator === "in" ? isIncluded : !isIncluded;
578
+ }
579
+ __name(evaluateArrayOperator, "evaluateArrayOperator");
580
+ function compareValues(operator, left, right) {
581
+ switch (operator) {
582
+ case "greater_than":
583
+ return left > right;
584
+ case "less_than":
585
+ return left < right;
586
+ default:
587
+ return false;
588
+ }
589
+ }
590
+ __name(compareValues, "compareValues");
591
+ function evaluateNumericOperator(operator, contextValue, conditionValue) {
592
+ const contextNum = Number(contextValue);
593
+ const conditionNum = Number(conditionValue);
594
+ if (!isNaN(contextNum) && !isNaN(conditionNum)) {
595
+ return compareValues(operator, contextNum, conditionNum);
596
+ }
597
+ if (typeof contextValue === "string" && typeof conditionValue === "string") {
598
+ return compareValues(operator, contextValue, conditionValue);
599
+ }
600
+ return false;
601
+ }
602
+ __name(evaluateNumericOperator, "evaluateNumericOperator");
603
+ var ConditionUtils = {
604
+ /**
605
+ * Evaluates multiple conditions with AND logic.
606
+ *
607
+ * @param conditions - Array of conditions
608
+ * @param contextValue - Context value getter function
609
+ * @returns true if all conditions match
610
+ */
611
+ evaluateConditionsAnd: /* @__PURE__ */ __name((conditions, contextValue) => {
612
+ if (conditions.length === 0) return true;
613
+ return conditions.every((condition) => {
614
+ const value = contextValue(condition.field);
615
+ if (value === void 0) return false;
616
+ return evaluateConditionOperator(condition, value);
617
+ });
618
+ }, "evaluateConditionsAnd"),
619
+ /**
620
+ * Evaluates multiple conditions with OR logic.
621
+ *
622
+ * @param conditions - Array of conditions
623
+ * @param contextValue - Context value getter function
624
+ * @returns true if any condition matches
625
+ */
626
+ evaluateConditionsOr: /* @__PURE__ */ __name((conditions, contextValue) => {
627
+ if (conditions.length === 0) return true;
628
+ return conditions.some((condition) => {
629
+ const value = contextValue(condition.field);
630
+ if (value === void 0) return false;
631
+ return evaluateConditionOperator(condition, value);
632
+ });
633
+ }, "evaluateConditionsOr"),
634
+ /**
635
+ * Validates a condition structure.
636
+ *
637
+ * @param condition - Condition to validate
638
+ * @returns Validation result
639
+ */
640
+ validateCondition: /* @__PURE__ */ __name((condition) => {
641
+ const errors = [];
642
+ if (!condition.field) {
643
+ errors.push("Field is required");
644
+ }
645
+ if (!condition.operator) {
646
+ errors.push("Operator is required");
647
+ } else {
648
+ const validOperators = [
649
+ "equals",
650
+ "not_equals",
651
+ "contains",
652
+ "not_contains",
653
+ "in",
654
+ "not_in",
655
+ "greater_than",
656
+ "less_than"
657
+ ];
658
+ if (!validOperators.includes(condition.operator)) {
659
+ errors.push(`Invalid operator: ${condition.operator}`);
660
+ }
661
+ }
662
+ if (condition.value === void 0) {
663
+ errors.push("Value is required");
664
+ }
665
+ return {
666
+ isValid: errors.length === 0,
667
+ errors
668
+ };
669
+ }, "validateCondition"),
670
+ /**
671
+ * Creates a condition object with validation.
672
+ *
673
+ * @param field - Context field to evaluate
674
+ * @param operator - Comparison operator
675
+ * @param value - Value to compare against
676
+ * @returns Valid condition object
677
+ */
678
+ createCondition: /* @__PURE__ */ __name((field, operator, value) => {
679
+ const condition = { field, operator, value };
680
+ const validation = ConditionUtils.validateCondition(condition);
681
+ if (!validation.isValid) {
682
+ throw new Error(`Invalid condition: ${validation.errors.join(", ")}`);
683
+ }
684
+ return condition;
685
+ }, "createCondition")
686
+ };
687
+
688
+ // src/engine/featureFlags/engine.ts
689
+ var FeatureFlagEngine = class {
690
+ /**
691
+ * Creates a new feature flag evaluation engine.
692
+ *
693
+ * @param defaults - Default flag values to fall back to
694
+ * @param isLoggingEnabled - Whether to enable debug logging
695
+ */
696
+ constructor(defaults, isLoggingEnabled = false) {
697
+ this.defaults = defaults;
698
+ this.isLoggingEnabled = isLoggingEnabled;
699
+ }
700
+ static {
701
+ __name(this, "FeatureFlagEngine");
702
+ }
703
+ /** Storage for active feature flags */
704
+ flags = /* @__PURE__ */ new Map();
705
+ /** Storage for targeting rules, organized by flag key */
706
+ rules = /* @__PURE__ */ new Map();
707
+ /** Storage for manual overrides (useful for testing) */
708
+ overrides = /* @__PURE__ */ new Map();
709
+ /**
710
+ * Sets the active feature flags for evaluation.
711
+ * Clears existing flags and rules before setting new ones.
712
+ *
713
+ * @param flags - Array of feature flags to activate
714
+ */
715
+ setFlags(flags) {
716
+ this.flags.clear();
717
+ this.rules.clear();
718
+ for (const flag of flags) {
719
+ this.flags.set(flag.key, flag);
720
+ }
721
+ this.log("Loaded flags:", this.flags.size);
722
+ }
723
+ /**
724
+ * Sets the targeting rules for feature flags.
725
+ * Rules are automatically sorted by priority (higher numbers first).
726
+ *
727
+ * @param rules - Array of feature flag rules
728
+ */
729
+ setRules(rules) {
730
+ this.rules.clear();
731
+ for (const rule of rules) {
732
+ const existing = this.rules.get(rule.flagKey) ?? [];
733
+ existing.push(rule);
734
+ existing.sort((a, b) => b.priority - a.priority);
735
+ this.rules.set(rule.flagKey, existing);
736
+ }
737
+ this.log("Loaded rules:", rules.length);
738
+ }
739
+ /**
740
+ * Sets a manual override for a specific flag.
741
+ * Overrides take precedence over all other evaluation logic.
742
+ *
743
+ * @param key - The flag key to override
744
+ * @param value - The value to force for this flag
745
+ */
746
+ setOverride(key, value) {
747
+ this.overrides.set(key, value);
748
+ this.log("Override set:", key, value);
749
+ }
750
+ /**
751
+ * Removes a manual override for a specific flag.
752
+ *
753
+ * @param key - The flag key to remove override for
754
+ */
755
+ removeOverride(key) {
756
+ this.overrides.delete(key);
757
+ this.log("Override removed:", key);
758
+ }
759
+ /**
760
+ * Clears all manual overrides.
761
+ */
762
+ clearOverrides() {
763
+ this.overrides.clear();
764
+ this.log("All overrides cleared");
765
+ }
766
+ /**
767
+ * Gets all current flags.
768
+ *
769
+ * @returns Array of all feature flags
770
+ */
771
+ getFlags() {
772
+ return Array.from(this.flags.values());
773
+ }
774
+ /**
775
+ * Evaluates a feature flag and returns the complete evaluation result.
776
+ *
777
+ * @param key - The feature flag key to evaluate
778
+ * @param context - Optional context for targeting
779
+ * @returns Complete evaluation result with value and metadata
780
+ */
781
+ evaluate(key, context) {
782
+ const evaluatedAt = /* @__PURE__ */ new Date();
783
+ const overrideResult = this.checkOverride(key, evaluatedAt);
784
+ if (overrideResult) return overrideResult;
785
+ const flag = this.flags.get(key);
786
+ if (!flag) {
787
+ return this.createDefaultEvaluation(key, evaluatedAt);
788
+ }
789
+ if (!flag.isEnabled) {
790
+ return this.createDisabledEvaluation(key, evaluatedAt);
791
+ }
792
+ if (!this.isEnvironmentMatch(flag, context)) {
793
+ return this.createDefaultEvaluation(key, evaluatedAt);
794
+ }
795
+ const ruleResult = this.evaluateRules(key, context, evaluatedAt);
796
+ if (ruleResult) return ruleResult;
797
+ if (!this.isInFlagRollout(key, flag, context)) {
798
+ return this.createDefaultEvaluation(key, evaluatedAt);
799
+ }
800
+ return this.createFlagEvaluation(key, flag, evaluatedAt);
801
+ }
802
+ /**
803
+ * Checks for manual override and returns evaluation if found.
804
+ *
805
+ * @private
806
+ * @param key - The feature flag key
807
+ * @param evaluatedAt - Evaluation timestamp
808
+ * @returns Evaluation result or null if no override
809
+ */
810
+ checkOverride(key, evaluatedAt) {
811
+ if (!this.overrides.has(key)) return null;
812
+ const value = this.overrides.get(key);
813
+ return {
814
+ flagKey: key,
815
+ value,
816
+ isEnabled: isTruthy(value),
817
+ reason: "override",
818
+ evaluatedAt
819
+ };
820
+ }
821
+ /**
822
+ * Creates default evaluation result.
823
+ *
824
+ * @private
825
+ * @param key - The feature flag key
826
+ * @param evaluatedAt - Evaluation timestamp
827
+ * @returns Default evaluation result
828
+ */
829
+ createDefaultEvaluation(key, evaluatedAt) {
830
+ const defaultValue = this.defaults[key] ?? false;
831
+ return {
832
+ flagKey: key,
833
+ value: defaultValue,
834
+ isEnabled: isTruthy(defaultValue),
835
+ reason: "default",
836
+ evaluatedAt
837
+ };
838
+ }
839
+ /**
840
+ * Creates disabled evaluation result.
841
+ *
842
+ * @private
843
+ * @param key - The feature flag key
844
+ * @param evaluatedAt - Evaluation timestamp
845
+ * @returns Disabled evaluation result
846
+ */
847
+ createDisabledEvaluation(key, evaluatedAt) {
848
+ return {
849
+ flagKey: key,
850
+ value: false,
851
+ isEnabled: false,
852
+ reason: "disabled",
853
+ evaluatedAt
854
+ };
855
+ }
856
+ /**
857
+ * Creates flag evaluation result.
858
+ *
859
+ * @private
860
+ * @param key - The feature flag key
861
+ * @param flag - The feature flag
862
+ * @param evaluatedAt - Evaluation timestamp
863
+ * @returns Flag evaluation result
864
+ */
865
+ createFlagEvaluation(key, flag, evaluatedAt) {
866
+ return {
867
+ flagKey: key,
868
+ value: flag.value,
869
+ isEnabled: isTruthy(flag.value),
870
+ reason: "default",
871
+ evaluatedAt
872
+ };
873
+ }
874
+ /**
875
+ * Checks if environment matches for flag evaluation.
876
+ *
877
+ * @private
878
+ * @param flag - The feature flag
879
+ * @param context - Evaluation context
880
+ * @returns true if environment matches
881
+ */
882
+ isEnvironmentMatch(flag, context) {
883
+ return flag.environment === "all" || context?.environment === flag.environment;
884
+ }
885
+ /**
886
+ * Checks if user is in flag-level rollout.
887
+ *
888
+ * @private
889
+ * @param key - The feature flag key
890
+ * @param flag - The feature flag
891
+ * @param context - Evaluation context
892
+ * @returns true if user is in rollout
893
+ */
894
+ isInFlagRollout(key, flag, context) {
895
+ if (flag.rolloutPercentage === void 0) return true;
896
+ const identifier = createRolloutIdentifier(key, context?.userId);
897
+ return isInRollout(identifier, flag.rolloutPercentage);
898
+ }
899
+ /**
900
+ * Evaluates all rules for a flag.
901
+ *
902
+ * @private
903
+ * @param key - The feature flag key
904
+ * @param context - Evaluation context
905
+ * @param evaluatedAt - Evaluation timestamp
906
+ * @returns Rule evaluation result or null if no match
907
+ */
908
+ evaluateRules(key, context, evaluatedAt) {
909
+ const rules = this.rules.get(key) ?? [];
910
+ for (const rule of rules) {
911
+ if (!rule.isEnabled) continue;
912
+ const ruleResult = this.evaluateMatchingRule(key, rule, context, evaluatedAt);
913
+ if (ruleResult) return ruleResult;
914
+ }
915
+ return null;
916
+ }
917
+ /**
918
+ * Evaluates a single matching rule and returns result if it passes.
919
+ *
920
+ * @private
921
+ * @param key - The feature flag key
922
+ * @param rule - The rule to evaluate
923
+ * @param context - Evaluation context
924
+ * @param evaluatedAt - Evaluation timestamp
925
+ * @returns Rule evaluation result or null if no match
926
+ */
927
+ evaluateMatchingRule(key, rule, context, evaluatedAt) {
928
+ if (!this.evaluateRule(rule, context)) return null;
929
+ const isInRuleRollout = rule.rolloutPercentage === void 0 || isInRollout(createRolloutIdentifier(key, context?.userId), rule.rolloutPercentage);
930
+ if (!isInRuleRollout) return null;
931
+ return {
932
+ flagKey: key,
933
+ value: rule.value,
934
+ isEnabled: isTruthy(rule.value),
935
+ reason: "rule_match",
936
+ ruleId: rule.id,
937
+ evaluatedAt: evaluatedAt ?? /* @__PURE__ */ new Date()
938
+ };
939
+ }
940
+ /**
941
+ * Evaluates a single rule against the provided context.
942
+ * All conditions in the rule must match (AND logic).
943
+ *
944
+ * @private
945
+ * @param rule - The rule to evaluate
946
+ * @param context - Context to evaluate against
947
+ * @returns true if the rule matches the context
948
+ */
949
+ evaluateRule(rule, context) {
950
+ if (rule.conditions.length === 0) return true;
951
+ return rule.conditions.every((condition) => this.evaluateCondition(condition, context));
952
+ }
953
+ /**
954
+ * Evaluates a single condition against the provided context.
955
+ *
956
+ * @private
957
+ * @param condition - The condition to evaluate
958
+ * @param context - Context to evaluate against
959
+ * @returns true if the condition matches
960
+ */
961
+ evaluateCondition(condition, context) {
962
+ if (!context) return false;
963
+ const contextValue = ContextUtils.getContextValue(condition.field, context);
964
+ if (contextValue === void 0) return false;
965
+ return evaluateConditionOperator(condition, contextValue);
966
+ }
967
+ /**
968
+ * Logs debug information if logging is enabled.
969
+ *
970
+ * @private
971
+ * @param args - Arguments to log
972
+ */
973
+ log(...args) {
974
+ if (this.isLoggingEnabled) {
975
+ console.log("[FeatureFlagEngine]", ...args);
976
+ }
977
+ }
978
+ };
979
+
980
+ // src/cache/strategies/memory.ts
981
+ var MemoryCacheStrategy = class {
982
+ static {
983
+ __name(this, "MemoryCacheStrategy");
984
+ }
985
+ cache = /* @__PURE__ */ new Map();
986
+ accessOrder = /* @__PURE__ */ new Map();
987
+ stats = {
988
+ hits: 0,
989
+ misses: 0,
990
+ sets: 0,
991
+ deletes: 0
992
+ };
993
+ cleanupTimer;
994
+ maxSize;
995
+ cleanupInterval;
996
+ onEvict;
997
+ /**
998
+ * Creates a new memory cache strategy.
999
+ *
1000
+ * @param config - Memory cache configuration
1001
+ */
1002
+ constructor(config = {}) {
1003
+ const DEFAULT_MAX_ENTRIES = 1e3;
1004
+ const DEFAULT_CLEANUP_INTERVAL = 6e4;
1005
+ const defaultConfig = {
1006
+ maxEntries: DEFAULT_MAX_ENTRIES,
1007
+ cleanupInterval: DEFAULT_CLEANUP_INTERVAL
1008
+ };
1009
+ this.maxSize = config.maxSize ?? config.maxEntries ?? defaultConfig.maxEntries;
1010
+ this.cleanupInterval = config.cleanupInterval ?? defaultConfig.cleanupInterval;
1011
+ this.onEvict = config.onEvict;
1012
+ this.startCleanup();
1013
+ }
1014
+ /**
1015
+ * Stores a cache entry in memory.
1016
+ *
1017
+ * @param key - Cache key
1018
+ * @param entry - Cache entry to store
1019
+ * @returns Promise that resolves when entry is stored
1020
+ */
1021
+ async set(key, entry) {
1022
+ if (this.maxSize === 0) {
1023
+ this.stats.sets++;
1024
+ return;
1025
+ }
1026
+ if (!this.cache.has(key) && this.cache.size >= this.maxSize) {
1027
+ this.evictOldestEntries();
1028
+ }
1029
+ this.cache.set(key, entry);
1030
+ this.accessOrder.set(key, Date.now());
1031
+ this.stats.sets++;
1032
+ }
1033
+ /**
1034
+ * Retrieves a cache entry from memory.
1035
+ *
1036
+ * @param key - Cache key
1037
+ * @returns Promise that resolves to cache entry or null if not found
1038
+ */
1039
+ async get(key) {
1040
+ const entry = this.cache.get(key);
1041
+ if (!entry) {
1042
+ this.stats.misses++;
1043
+ return null;
1044
+ }
1045
+ this.accessOrder.set(key, Date.now());
1046
+ this.stats.hits++;
1047
+ return entry;
1048
+ }
1049
+ /**
1050
+ * Removes a cache entry from memory.
1051
+ *
1052
+ * @param key - Cache key to remove
1053
+ * @returns Promise that resolves when entry is removed
1054
+ */
1055
+ async delete(key) {
1056
+ this.cache.delete(key);
1057
+ this.accessOrder.delete(key);
1058
+ this.stats.deletes++;
1059
+ }
1060
+ /**
1061
+ * Clears all cache entries from memory.
1062
+ *
1063
+ * @returns Promise that resolves when cache is cleared
1064
+ */
1065
+ async clear() {
1066
+ this.cache.clear();
1067
+ this.accessOrder.clear();
1068
+ this.stats.hits = 0;
1069
+ this.stats.misses = 0;
1070
+ this.stats.sets = 0;
1071
+ this.stats.deletes = 0;
1072
+ }
1073
+ /**
1074
+ * Gets cache statistics.
1075
+ *
1076
+ * @returns Promise that resolves to cache statistics
1077
+ */
1078
+ async getStats() {
1079
+ const totalRequests = this.stats.hits + this.stats.misses;
1080
+ const hitRatio = totalRequests > 0 ? this.stats.hits / totalRequests : 0;
1081
+ return {
1082
+ hits: this.stats.hits,
1083
+ misses: this.stats.misses,
1084
+ sets: this.stats.sets,
1085
+ deletes: this.stats.deletes,
1086
+ size: this.cache.size,
1087
+ hitRatio
1088
+ };
1089
+ }
1090
+ /**
1091
+ * Disposes of the memory cache and cleans up resources.
1092
+ *
1093
+ * @returns Promise that resolves when cleanup is complete
1094
+ */
1095
+ async dispose() {
1096
+ if (this.cleanupTimer) {
1097
+ clearInterval(this.cleanupTimer);
1098
+ this.cleanupTimer = void 0;
1099
+ }
1100
+ await this.clear();
1101
+ }
1102
+ /**
1103
+ * Starts the periodic cleanup of expired entries.
1104
+ *
1105
+ * @private
1106
+ */
1107
+ startCleanup() {
1108
+ this.cleanupTimer = setInterval(() => {
1109
+ this.cleanupExpiredEntries();
1110
+ }, this.cleanupInterval);
1111
+ }
1112
+ /**
1113
+ * Removes expired entries from the cache.
1114
+ *
1115
+ * @private
1116
+ */
1117
+ cleanupExpiredEntries() {
1118
+ const now = Date.now();
1119
+ const expiredKeys = [];
1120
+ for (const [key, entry] of Array.from(this.cache.entries())) {
1121
+ if (now > entry.expiresAt) {
1122
+ expiredKeys.push(key);
1123
+ }
1124
+ }
1125
+ for (const key of expiredKeys) {
1126
+ const entry = this.cache.get(key);
1127
+ this.cache.delete(key);
1128
+ this.accessOrder.delete(key);
1129
+ if (entry && this.onEvict) {
1130
+ this.onEvict(key, entry);
1131
+ }
1132
+ }
1133
+ }
1134
+ /**
1135
+ * Evicts the oldest entries when cache is full.
1136
+ * Uses LRU-like eviction by removing the oldest entries by creation time.
1137
+ *
1138
+ * @private
1139
+ */
1140
+ evictOldestEntries() {
1141
+ if (this.maxSize === 0) {
1142
+ this.cache.clear();
1143
+ this.accessOrder.clear();
1144
+ return;
1145
+ }
1146
+ const EVICTION_PERCENTAGE = 0.1;
1147
+ const entriesToEvict = Math.max(1, Math.ceil(this.maxSize * EVICTION_PERCENTAGE));
1148
+ const sortedEntries = Array.from(this.accessOrder.entries()).sort(([, timeA], [, timeB]) => timeA - timeB).slice(0, entriesToEvict);
1149
+ for (const [key] of sortedEntries) {
1150
+ const entry = this.cache.get(key);
1151
+ this.cache.delete(key);
1152
+ this.accessOrder.delete(key);
1153
+ if (entry && this.onEvict) {
1154
+ this.onEvict(key, entry);
1155
+ }
1156
+ }
1157
+ }
1158
+ };
1159
+
1160
+ // src/cache/strategies/redis.ts
1161
+ var RedisCacheStrategy = class {
1162
+ /**
1163
+ * Creates a new Redis cache strategy.
1164
+ *
1165
+ * @param config - Redis cache configuration
1166
+ */
1167
+ constructor(config) {
1168
+ this.config = config;
1169
+ if (!config.url) {
1170
+ throw new Error("Redis URL is required");
1171
+ }
1172
+ this.keyPrefix = config.keyPrefix ?? "cache:";
1173
+ }
1174
+ static {
1175
+ __name(this, "RedisCacheStrategy");
1176
+ }
1177
+ client;
1178
+ stats = {
1179
+ hitCount: 0,
1180
+ missCount: 0,
1181
+ setCount: 0,
1182
+ deleteCount: 0
1183
+ };
1184
+ isConnected = false;
1185
+ keyPrefix;
1186
+ /**
1187
+ * Stores a cache entry in Redis.
1188
+ *
1189
+ * @param key - Cache key
1190
+ * @param entry - Cache entry to store
1191
+ * @returns Promise that resolves when entry is stored
1192
+ */
1193
+ async set(key, entry) {
1194
+ await this.ensureConnected();
1195
+ const redisKey = this.buildRedisKey(key);
1196
+ const serializedEntry = JSON.stringify(entry);
1197
+ const ttlMs = entry.expiresAt - Date.now();
1198
+ const ttlSeconds = Math.max(1, Math.ceil(ttlMs / 1e3));
1199
+ await this.client.set(redisKey, serializedEntry, "EX", ttlSeconds);
1200
+ this.stats.setCount++;
1201
+ }
1202
+ /**
1203
+ * Retrieves a cache entry from Redis.
1204
+ *
1205
+ * @param key - Cache key
1206
+ * @returns Promise that resolves to cache entry or null if not found
1207
+ */
1208
+ async get(key) {
1209
+ await this.ensureConnected();
1210
+ const redisKey = this.buildRedisKey(key);
1211
+ const serializedEntry = await this.client.get(redisKey);
1212
+ if (!serializedEntry || typeof serializedEntry !== "string") {
1213
+ this.stats.missCount++;
1214
+ return null;
1215
+ }
1216
+ try {
1217
+ const entry = JSON.parse(serializedEntry);
1218
+ this.stats.hitCount++;
1219
+ return entry;
1220
+ } catch {
1221
+ await this.client.del(redisKey);
1222
+ this.stats.missCount++;
1223
+ return null;
1224
+ }
1225
+ }
1226
+ /**
1227
+ * Removes a cache entry from Redis.
1228
+ *
1229
+ * @param key - Cache key to remove
1230
+ * @returns Promise that resolves when entry is removed
1231
+ */
1232
+ async delete(key) {
1233
+ await this.ensureConnected();
1234
+ const redisKey = this.buildRedisKey(key);
1235
+ await this.client.del(redisKey);
1236
+ this.stats.deleteCount++;
1237
+ }
1238
+ /**
1239
+ * Clears all cache entries from Redis.
1240
+ * This removes all keys with the configured prefix.
1241
+ *
1242
+ * @returns Promise that resolves when cache is cleared
1243
+ */
1244
+ async clear() {
1245
+ await this.ensureConnected();
1246
+ const pattern = `${this.keyPrefix}*`;
1247
+ const keys = await this.client.keys(pattern);
1248
+ if (keys.length > 0) {
1249
+ await this.client.del(...keys);
1250
+ }
1251
+ this.stats.hitCount = 0;
1252
+ this.stats.missCount = 0;
1253
+ }
1254
+ /**
1255
+ * Gets cache statistics.
1256
+ *
1257
+ * @returns Promise that resolves to cache statistics
1258
+ */
1259
+ async getStats() {
1260
+ await this.ensureConnected();
1261
+ const pattern = `${this.keyPrefix}*`;
1262
+ const keys = await this.client.keys(pattern);
1263
+ const entryCount = Array.isArray(keys) ? keys.length : 0;
1264
+ const totalRequests = this.stats.hitCount + this.stats.missCount;
1265
+ const hitRatio = totalRequests > 0 ? this.stats.hitCount / totalRequests : 0;
1266
+ return {
1267
+ hits: this.stats.hitCount,
1268
+ misses: this.stats.missCount,
1269
+ sets: this.stats.setCount,
1270
+ deletes: this.stats.deleteCount,
1271
+ size: entryCount,
1272
+ hitRatio
1273
+ };
1274
+ }
1275
+ /**
1276
+ * Disposes of the Redis cache and cleans up resources.
1277
+ *
1278
+ * @returns Promise that resolves when cleanup is complete
1279
+ */
1280
+ async dispose() {
1281
+ if (this.client && this.isConnected) {
1282
+ await this.client.quit();
1283
+ this.isConnected = false;
1284
+ }
1285
+ }
1286
+ /**
1287
+ * Ensures Redis connection is established.
1288
+ *
1289
+ * @private
1290
+ * @returns Promise that resolves when connected
1291
+ */
1292
+ async ensureConnected() {
1293
+ if (this.isConnected) return;
1294
+ try {
1295
+ this.client = await this.createIoRedisClient();
1296
+ this.isConnected = true;
1297
+ } catch (error) {
1298
+ throw new Error(
1299
+ `Failed to connect to Redis: ${error instanceof Error ? error.message : "Unknown error"}. Ensure Redis is running and accessible, and install ioredis package.`
1300
+ );
1301
+ }
1302
+ }
1303
+ /**
1304
+ * Creates an ioredis client.
1305
+ *
1306
+ * @private
1307
+ * @returns Promise that resolves to ioredis client
1308
+ */
1309
+ async createIoRedisClient() {
1310
+ const DEFAULT_TIMEOUT = 5e3;
1311
+ const defaultOptions = {
1312
+ connectTimeout: DEFAULT_TIMEOUT,
1313
+ commandTimeout: DEFAULT_TIMEOUT,
1314
+ enableOfflineQueue: false
1315
+ };
1316
+ const Redis = await import('ioredis');
1317
+ const client = new Redis.default(this.config.url, {
1318
+ connectTimeout: this.config.connectTimeout ?? defaultOptions.connectTimeout,
1319
+ commandTimeout: this.config.commandTimeout ?? defaultOptions.commandTimeout,
1320
+ enableOfflineQueue: defaultOptions.enableOfflineQueue
1321
+ });
1322
+ await new Promise((resolve, reject) => {
1323
+ client.on("ready", resolve);
1324
+ client.on("error", reject);
1325
+ });
1326
+ return client;
1327
+ }
1328
+ /**
1329
+ * Builds a Redis key with the configured prefix.
1330
+ *
1331
+ * @private
1332
+ * @param key - Base cache key
1333
+ * @returns Redis key with prefix
1334
+ */
1335
+ buildRedisKey(key) {
1336
+ return `${this.keyPrefix}${key}`;
1337
+ }
1338
+ };
1339
+
1340
+ // src/cache/index.ts
1341
+ var CacheManager = class {
1342
+ /**
1343
+ * Creates a new cache manager with the specified configuration.
1344
+ *
1345
+ * @param config - Cache configuration
1346
+ */
1347
+ constructor(config) {
1348
+ this.config = config;
1349
+ this.strategy = this.createStrategy(config);
1350
+ }
1351
+ static {
1352
+ __name(this, "CacheManager");
1353
+ }
1354
+ strategy;
1355
+ /**
1356
+ * Stores a value in the cache.
1357
+ *
1358
+ * @template T - Type of the value to cache
1359
+ * @param key - Cache key
1360
+ * @param value - Value to cache
1361
+ * @param ttl - Optional TTL override in seconds
1362
+ * @returns Promise that resolves when value is cached
1363
+ */
1364
+ async set(key, value, ttl) {
1365
+ if (!this.config.isEnabled) return;
1366
+ const finalTtl = ttl ?? this.config.ttl;
1367
+ const entry = {
1368
+ data: value,
1369
+ expiresAt: Date.now() + finalTtl * 1e3,
1370
+ createdAt: Date.now()
1371
+ };
1372
+ await this.strategy.set(key, entry);
1373
+ }
1374
+ /**
1375
+ * Retrieves a value from the cache.
1376
+ *
1377
+ * @template T - Expected type of the cached value
1378
+ * @param key - Cache key
1379
+ * @returns Promise that resolves to cached value or null if not found/expired
1380
+ */
1381
+ async get(key) {
1382
+ if (!this.config.isEnabled) return null;
1383
+ const entry = await this.strategy.get(key);
1384
+ if (!entry) return null;
1385
+ if (Date.now() > entry.expiresAt) {
1386
+ await this.strategy.delete(key);
1387
+ return null;
1388
+ }
1389
+ return entry.data;
1390
+ }
1391
+ /**
1392
+ * Removes a value from the cache.
1393
+ *
1394
+ * @param key - Cache key to remove
1395
+ * @returns Promise that resolves when value is removed
1396
+ */
1397
+ async delete(key) {
1398
+ if (!this.config.isEnabled) return;
1399
+ await this.strategy.delete(key);
1400
+ }
1401
+ /**
1402
+ * Clears all cached values.
1403
+ *
1404
+ * @returns Promise that resolves when cache is cleared
1405
+ */
1406
+ async clear() {
1407
+ if (!this.config.isEnabled) return;
1408
+ await this.strategy.clear();
1409
+ }
1410
+ /**
1411
+ * Checks if a key exists in the cache.
1412
+ *
1413
+ * @param key - Cache key to check
1414
+ * @returns Promise that resolves to true if key exists and is not expired
1415
+ */
1416
+ async has(key) {
1417
+ if (!this.config.isEnabled) return false;
1418
+ const entry = await this.strategy.get(key);
1419
+ if (!entry) return false;
1420
+ if (Date.now() > entry.expiresAt) {
1421
+ await this.strategy.delete(key);
1422
+ return false;
1423
+ }
1424
+ return true;
1425
+ }
1426
+ /**
1427
+ * Gets cache statistics.
1428
+ *
1429
+ * @returns Promise that resolves to cache statistics
1430
+ */
1431
+ async getStats() {
1432
+ return this.strategy.getStats();
1433
+ }
1434
+ /**
1435
+ * Creates the appropriate cache strategy based on configuration.
1436
+ *
1437
+ * @private
1438
+ * @param config - Cache configuration
1439
+ * @returns Cache strategy instance
1440
+ */
1441
+ createStrategy(config) {
1442
+ switch (config.strategy) {
1443
+ case "redis":
1444
+ if (!config.redisConfig) {
1445
+ throw new Error("Redis configuration is required for Redis cache strategy");
1446
+ }
1447
+ return new RedisCacheStrategy(config.redisConfig);
1448
+ case "memory":
1449
+ default:
1450
+ return new MemoryCacheStrategy(config.memoryConfig);
1451
+ }
1452
+ }
1453
+ /**
1454
+ * Disposes of the cache manager and cleans up resources.
1455
+ *
1456
+ * @returns Promise that resolves when cleanup is complete
1457
+ */
1458
+ async dispose() {
1459
+ await this.strategy.dispose?.();
1460
+ }
1461
+ };
1462
+
1463
+ // src/domain/featureFlags/provider.ts
1464
+ var FeatureFlagProvider = class {
1465
+ /**
1466
+ * Creates a new feature flag provider.
1467
+ *
1468
+ * @param config - Provider configuration
1469
+ * @param features - Record of feature flag keys to their default values
1470
+ */
1471
+ constructor(config, features) {
1472
+ this.config = config;
1473
+ this.features = features;
1474
+ this.engine = new FeatureFlagEngine(features, config.isLoggingEnabled ?? false);
1475
+ this.cacheManager = new CacheManager({
1476
+ isEnabled: config.isCacheEnabled,
1477
+ ttl: config.cacheTtl,
1478
+ strategy: "memory"
1479
+ // Default to memory, can be overridden
1480
+ });
1481
+ this.setupRefreshTimer();
1482
+ }
1483
+ static {
1484
+ __name(this, "FeatureFlagProvider");
1485
+ }
1486
+ engine;
1487
+ cacheManager;
1488
+ subscribers = /* @__PURE__ */ new Set();
1489
+ refreshTimer;
1490
+ isInitialized = false;
1491
+ features;
1492
+ initializePromise;
1493
+ /**
1494
+ * Initializes the provider by loading initial data.
1495
+ *
1496
+ * @returns Promise that resolves when initialization is complete
1497
+ */
1498
+ async initialize() {
1499
+ if (this.isInitialized) {
1500
+ return;
1501
+ }
1502
+ if (this.initializePromise) {
1503
+ return this.initializePromise;
1504
+ }
1505
+ this.initializePromise = this.doInitialize();
1506
+ return this.initializePromise;
1507
+ }
1508
+ /**
1509
+ * Performs the actual initialization work.
1510
+ *
1511
+ * @private
1512
+ * @returns Promise that resolves when initialization is complete
1513
+ */
1514
+ async doInitialize() {
1515
+ try {
1516
+ await this.refresh();
1517
+ this.isInitialized = true;
1518
+ this.log("Provider initialized successfully");
1519
+ } catch (error) {
1520
+ this.log("Failed to initialize provider:", error);
1521
+ this.initializePromise = void 0;
1522
+ throw error;
1523
+ }
1524
+ }
1525
+ /**
1526
+ * Gets a feature flag evaluation for the specified key and context.
1527
+ *
1528
+ * @param key - The feature flag key
1529
+ * @param context - Optional context for evaluation
1530
+ * @returns Promise resolving to the flag evaluation
1531
+ */
1532
+ async getFlag(key, context) {
1533
+ if (!this.isInitialized) {
1534
+ await this.initialize();
1535
+ }
1536
+ if (this.config.isCacheEnabled) {
1537
+ const cacheKey = this.generateCacheKey(key, context);
1538
+ const cached = await this.cacheManager.get(cacheKey);
1539
+ if (cached) {
1540
+ return cached;
1541
+ }
1542
+ const evaluation = this.engine.evaluate(key, context);
1543
+ await this.cacheManager.set(cacheKey, evaluation);
1544
+ return evaluation;
1545
+ }
1546
+ return this.engine.evaluate(key, context);
1547
+ }
1548
+ /**
1549
+ * Checks if a feature flag is enabled.
1550
+ *
1551
+ * @param key - The feature flag key
1552
+ * @param context - Optional context for evaluation
1553
+ * @returns Promise resolving to boolean indicating if flag is enabled
1554
+ */
1555
+ async isEnabled(key, context) {
1556
+ const evaluation = await this.getFlag(key, context);
1557
+ return evaluation.isEnabled;
1558
+ }
1559
+ /**
1560
+ * Gets the value of a feature flag.
1561
+ *
1562
+ * @template T - The expected type of the flag value
1563
+ * @param key - The feature flag key
1564
+ * @param context - Optional context for evaluation
1565
+ * @returns Promise resolving to the flag value
1566
+ */
1567
+ async getValue(key, context) {
1568
+ const evaluation = await this.getFlag(key, context);
1569
+ return evaluation.value;
1570
+ }
1571
+ /**
1572
+ * Gets all feature flag evaluations for the given context.
1573
+ *
1574
+ * @param context - Optional context for evaluation
1575
+ * @returns Promise resolving to record of flag evaluations
1576
+ */
1577
+ async getAllFlags(context) {
1578
+ if (!this.isInitialized) {
1579
+ await this.initialize();
1580
+ }
1581
+ const results = {};
1582
+ for (const key of Object.keys(this.features)) {
1583
+ results[key] = await this.getFlag(key, context);
1584
+ }
1585
+ const engineFlags = this.engine.getFlags();
1586
+ for (const flag of engineFlags) {
1587
+ if (!(flag.key in results)) {
1588
+ results[flag.key] = await this.getFlag(flag.key, context);
1589
+ }
1590
+ }
1591
+ return results;
1592
+ }
1593
+ /**
1594
+ * Refreshes the provider by fetching latest data from the source.
1595
+ *
1596
+ * @returns Promise that resolves when refresh is complete
1597
+ */
1598
+ async refresh() {
1599
+ try {
1600
+ const { flags, rules } = await this.fetchData();
1601
+ this.engine.setFlags(flags);
1602
+ this.engine.setRules(rules);
1603
+ await this.cacheManager.clear();
1604
+ this.notifySubscribers();
1605
+ this.log(`Refreshed with ${flags.length} flags and ${rules.length} rules`);
1606
+ } catch (error) {
1607
+ this.log("Failed to refresh provider:", error);
1608
+ throw error;
1609
+ }
1610
+ }
1611
+ /**
1612
+ * Subscribes to provider updates.
1613
+ *
1614
+ * @param callback - Function to call when provider updates
1615
+ * @returns Unsubscribe function
1616
+ */
1617
+ subscribe(callback) {
1618
+ this.subscribers.add(callback);
1619
+ return () => {
1620
+ this.subscribers.delete(callback);
1621
+ };
1622
+ }
1623
+ /**
1624
+ * Sets an override for a specific flag key.
1625
+ *
1626
+ * @param key - The flag key to override
1627
+ * @param value - The value to force for this flag
1628
+ */
1629
+ setOverride(key, value) {
1630
+ this.engine.setOverride(key, value);
1631
+ void this.cacheManager.clear();
1632
+ this.notifySubscribers();
1633
+ }
1634
+ /**
1635
+ * Removes an override for a specific flag key.
1636
+ *
1637
+ * @param key - The flag key to remove override for
1638
+ */
1639
+ removeOverride(key) {
1640
+ this.engine.removeOverride(key);
1641
+ void this.cacheManager.clear();
1642
+ this.notifySubscribers();
1643
+ }
1644
+ /**
1645
+ * Clears all overrides.
1646
+ */
1647
+ clearOverrides() {
1648
+ this.engine.clearOverrides();
1649
+ void this.cacheManager.clear();
1650
+ this.notifySubscribers();
1651
+ }
1652
+ /**
1653
+ * Disposes of the provider, cleaning up resources.
1654
+ */
1655
+ dispose() {
1656
+ if (this.refreshTimer) {
1657
+ clearInterval(this.refreshTimer);
1658
+ this.refreshTimer = void 0;
1659
+ }
1660
+ this.subscribers.clear();
1661
+ void this.cacheManager.clear();
1662
+ this.isInitialized = false;
1663
+ this.log("Provider disposed");
1664
+ }
1665
+ /**
1666
+ * Generates a cache key for flag evaluation.
1667
+ *
1668
+ * @protected
1669
+ * @param key - Feature flag key
1670
+ * @param context - Evaluation context
1671
+ * @returns Cache key string
1672
+ */
1673
+ generateCacheKey(key, context) {
1674
+ if (!context) {
1675
+ return key;
1676
+ }
1677
+ const contextKey = JSON.stringify({
1678
+ userId: context.userId,
1679
+ userRole: context.userRole,
1680
+ environment: context.environment,
1681
+ platform: context.platform,
1682
+ country: context.country,
1683
+ version: context.version
1684
+ });
1685
+ return `${key}:${contextKey}`;
1686
+ }
1687
+ /**
1688
+ * Sets up the automatic refresh timer if configured.
1689
+ *
1690
+ * @protected
1691
+ */
1692
+ setupRefreshTimer() {
1693
+ if (this.config.refreshInterval > 0) {
1694
+ this.refreshTimer = setInterval(() => {
1695
+ void this.refresh().catch((error) => {
1696
+ this.log("Auto-refresh failed:", error);
1697
+ });
1698
+ }, this.config.refreshInterval * 1e3);
1699
+ }
1700
+ }
1701
+ /**
1702
+ * Notifies all subscribers of provider updates.
1703
+ *
1704
+ * @protected
1705
+ */
1706
+ notifySubscribers() {
1707
+ for (const callback of Array.from(this.subscribers)) {
1708
+ try {
1709
+ callback();
1710
+ } catch (error) {
1711
+ this.log("Subscriber callback error:", error);
1712
+ }
1713
+ }
1714
+ }
1715
+ /**
1716
+ * Logs a message if logging is enabled.
1717
+ *
1718
+ * @protected
1719
+ * @param args - Arguments to log
1720
+ */
1721
+ log(...args) {
1722
+ if (this.config.isLoggingEnabled) {
1723
+ console.log("[FeatureFlagProvider]", ...args);
1724
+ }
1725
+ }
1726
+ };
1727
+
1728
+ // src/domain/featureFlags/providers/memory.ts
1729
+ var MemoryFeatureFlagProvider = class extends FeatureFlagProvider {
1730
+ static {
1731
+ __name(this, "MemoryFeatureFlagProvider");
1732
+ }
1733
+ flags = [];
1734
+ rules = [];
1735
+ /**
1736
+ * Creates a new memory feature flag provider.
1737
+ *
1738
+ * @param config - Provider configuration
1739
+ * @param features - Record of feature flag keys to their default values
1740
+ */
1741
+ constructor(config, features) {
1742
+ super(config, features);
1743
+ this.validateConfig();
1744
+ }
1745
+ /**
1746
+ * Fetches flags and rules from memory (FEATURES constant).
1747
+ *
1748
+ * @protected
1749
+ * @returns Promise resolving to flags and rules from memory
1750
+ */
1751
+ async fetchData() {
1752
+ this.log("Fetching feature flags from memory (FEATURES constant)");
1753
+ const currentTime = /* @__PURE__ */ new Date();
1754
+ this.flags = Object.entries(this.features).map(
1755
+ ([key, value]) => this.createFeatureFlagFromConstant(
1756
+ key,
1757
+ value,
1758
+ currentTime
1759
+ )
1760
+ );
1761
+ this.rules = [...this.getManualRules()];
1762
+ this.log(`Loaded ${this.flags.length} flags and ${this.rules.length} rules from memory`);
1763
+ return {
1764
+ flags: this.flags,
1765
+ rules: this.rules
1766
+ };
1767
+ }
1768
+ /**
1769
+ * Creates a FeatureFlag object from a FEATURES constant entry.
1770
+ *
1771
+ * @private
1772
+ * @param key - The feature flag key
1773
+ * @param value - The value from FEATURES constant
1774
+ * @param currentTime - Current timestamp
1775
+ * @returns FeatureFlag object
1776
+ */
1777
+ createFeatureFlagFromConstant(key, value, currentTime) {
1778
+ return {
1779
+ key,
1780
+ name: this.generateFlagName(key),
1781
+ description: `Memory-based flag for ${key}`,
1782
+ isEnabled: true,
1783
+ value,
1784
+ type: this.inferFlagType(value),
1785
+ environment: "all",
1786
+ rolloutPercentage: void 0,
1787
+ createdAt: currentTime,
1788
+ updatedAt: currentTime,
1789
+ createdBy: "memory-system",
1790
+ updatedBy: "memory-system"
1791
+ };
1792
+ }
1793
+ /**
1794
+ * Generates a human-readable name from a flag key.
1795
+ *
1796
+ * @private
1797
+ * @param key - The feature flag key
1798
+ * @returns Human-readable flag name
1799
+ */
1800
+ generateFlagName(key) {
1801
+ return key.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
1802
+ }
1803
+ /**
1804
+ * Infers the flag type from its value.
1805
+ *
1806
+ * @private
1807
+ * @param value - The flag value
1808
+ * @returns The inferred type
1809
+ */
1810
+ inferFlagType(value) {
1811
+ if (typeof value === "boolean") return "boolean";
1812
+ if (typeof value === "string") return "string";
1813
+ if (typeof value === "number") return "number";
1814
+ return "json";
1815
+ }
1816
+ /**
1817
+ * Gets manually added rules for memory provider.
1818
+ *
1819
+ * @private
1820
+ * @returns Array of manually configured rules
1821
+ */
1822
+ getManualRules() {
1823
+ return this.config.memoryRules ?? [];
1824
+ }
1825
+ /**
1826
+ * Validates the memory provider configuration.
1827
+ *
1828
+ * @private
1829
+ * @throws Error if configuration is invalid
1830
+ */
1831
+ validateConfig() {
1832
+ if (this.config.provider !== "memory") {
1833
+ throw new Error('Memory provider requires provider to be set to "memory"');
1834
+ }
1835
+ if (this.config.memoryRules && !Array.isArray(this.config.memoryRules)) {
1836
+ throw new Error("memoryRules must be an array if provided");
1837
+ }
1838
+ }
1839
+ /**
1840
+ * Adds a rule to the memory provider at runtime.
1841
+ *
1842
+ * @param rule - The rule to add
1843
+ */
1844
+ addRule(rule) {
1845
+ this.rules.push(rule);
1846
+ this.engine.setRules(this.rules);
1847
+ void this.cacheManager.clear();
1848
+ this.notifySubscribers();
1849
+ this.log(`Added rule: ${rule.name} for flag: ${rule.flagKey}`);
1850
+ }
1851
+ /**
1852
+ * Removes a rule from the memory provider.
1853
+ *
1854
+ * @param ruleId - The ID of the rule to remove
1855
+ */
1856
+ removeRule(ruleId) {
1857
+ const initialCount = this.rules.length;
1858
+ this.rules = this.rules.filter((rule) => rule.id !== ruleId);
1859
+ if (this.rules.length < initialCount) {
1860
+ this.engine.setRules(this.rules);
1861
+ void this.cacheManager.clear();
1862
+ this.notifySubscribers();
1863
+ this.log(`Removed rule with ID: ${ruleId}`);
1864
+ } else {
1865
+ this.log(`Rule with ID ${ruleId} not found`);
1866
+ }
1867
+ }
1868
+ /**
1869
+ * Updates a flag value in memory.
1870
+ *
1871
+ * @param key - The flag key to update
1872
+ * @param value - The new value
1873
+ * @param updateProps - Optional properties to update
1874
+ */
1875
+ updateFlag(key, value, updateProps) {
1876
+ const flagIndex = this.flags.findIndex((flag) => flag.key === key);
1877
+ if (flagIndex === -1) {
1878
+ this.log(`Flag with key ${key} not found in memory`);
1879
+ return;
1880
+ }
1881
+ const updatedFlag = {
1882
+ ...this.flags[flagIndex],
1883
+ value,
1884
+ type: this.inferFlagType(value),
1885
+ updatedAt: /* @__PURE__ */ new Date(),
1886
+ updatedBy: "memory-runtime",
1887
+ ...updateProps
1888
+ };
1889
+ this.flags[flagIndex] = updatedFlag;
1890
+ this.engine.setFlags(this.flags);
1891
+ void this.cacheManager.clear();
1892
+ this.notifySubscribers();
1893
+ this.log(`Updated flag: ${key} with new value:`, value);
1894
+ }
1895
+ /**
1896
+ * Adds a new flag to memory at runtime.
1897
+ *
1898
+ * @param key - The flag key
1899
+ * @param value - The flag value
1900
+ * @param props - Optional flag properties
1901
+ */
1902
+ addFlag(key, value, props) {
1903
+ if (this.flagExists(key)) {
1904
+ return;
1905
+ }
1906
+ const newFlag = this.createNewFlag(key, value, props);
1907
+ this.addFlagToStore(key, newFlag, value);
1908
+ }
1909
+ /**
1910
+ * Checks if a flag with the given key exists.
1911
+ */
1912
+ flagExists(key) {
1913
+ const exists = this.flags.some((flag) => flag.key === key);
1914
+ if (exists) {
1915
+ this.log(`Flag with key ${key} already exists in memory`);
1916
+ }
1917
+ return exists;
1918
+ }
1919
+ /**
1920
+ * Creates a new flag object.
1921
+ */
1922
+ createNewFlag(key, value, props) {
1923
+ const currentTime = /* @__PURE__ */ new Date();
1924
+ const flagDefaults = this.getDefaultFlagProperties(key, value);
1925
+ const flagProps = this.mergeWithUserProps(flagDefaults, props);
1926
+ return {
1927
+ key,
1928
+ ...flagProps,
1929
+ value,
1930
+ type: flagDefaults.type,
1931
+ createdAt: currentTime,
1932
+ updatedAt: currentTime,
1933
+ createdBy: "memory-runtime",
1934
+ updatedBy: "memory-runtime"
1935
+ };
1936
+ }
1937
+ /**
1938
+ * Gets default properties for a new flag.
1939
+ */
1940
+ getDefaultFlagProperties(key, value) {
1941
+ return {
1942
+ name: this.generateFlagName(key),
1943
+ description: `Runtime-added flag for ${key}`,
1944
+ isEnabled: true,
1945
+ type: this.inferFlagType(value),
1946
+ environment: "all",
1947
+ rolloutPercentage: void 0
1948
+ };
1949
+ }
1950
+ /**
1951
+ * Merges default properties with user-provided properties.
1952
+ */
1953
+ mergeWithUserProps(defaults, props) {
1954
+ const merged = { ...defaults, ...props };
1955
+ if (props?.type) {
1956
+ merged.type = props.type;
1957
+ }
1958
+ return merged;
1959
+ }
1960
+ /**
1961
+ * Adds the flag to the store and notifies systems.
1962
+ */
1963
+ addFlagToStore(key, newFlag, value) {
1964
+ this.flags.push(newFlag);
1965
+ this.engine.setFlags(this.flags);
1966
+ void this.cacheManager.clear();
1967
+ this.notifySubscribers();
1968
+ this.log(`Added new flag: ${key} with value:`, value);
1969
+ }
1970
+ /**
1971
+ * Removes a flag from memory.
1972
+ *
1973
+ * @param key - The flag key to remove
1974
+ */
1975
+ removeFlag(key) {
1976
+ const initialCount = this.flags.length;
1977
+ this.flags = this.flags.filter((flag) => flag.key !== key);
1978
+ if (this.flags.length < initialCount) {
1979
+ this.engine.setFlags(this.flags);
1980
+ void this.cacheManager.clear();
1981
+ this.notifySubscribers();
1982
+ this.log(`Removed flag: ${key}`);
1983
+ } else {
1984
+ this.log(`Flag with key ${key} not found in memory`);
1985
+ }
1986
+ }
1987
+ /**
1988
+ * Gets all current flags in memory.
1989
+ *
1990
+ * @returns Array of current flags
1991
+ */
1992
+ getCurrentFlags() {
1993
+ return [...this.flags];
1994
+ }
1995
+ /**
1996
+ * Gets all current rules in memory.
1997
+ *
1998
+ * @returns Array of current rules
1999
+ */
2000
+ getCurrentRules() {
2001
+ return [...this.rules];
2002
+ }
2003
+ /**
2004
+ * Resets the memory provider to its initial state.
2005
+ */
2006
+ async reset() {
2007
+ this.log("Resetting memory provider to initial state");
2008
+ await this.refresh();
2009
+ }
2010
+ /**
2011
+ * Gets statistics about the memory provider.
2012
+ *
2013
+ * @returns Provider statistics
2014
+ */
2015
+ getStats() {
2016
+ return {
2017
+ flagCount: this.flags.length,
2018
+ ruleCount: this.rules.length,
2019
+ cacheSize: 0,
2020
+ // Memory cache size would need to be tracked
2021
+ subscriberCount: this.subscribers.size,
2022
+ isInitialized: this.isInitialized
2023
+ };
2024
+ }
2025
+ /**
2026
+ * Logs messages with MemoryFeatureFlagProvider prefix.
2027
+ *
2028
+ * @param args - Arguments to log
2029
+ */
2030
+ log(...args) {
2031
+ if (this.config.isLoggingEnabled) {
2032
+ console.log("[MemoryFeatureFlagProvider]", ...args);
2033
+ }
2034
+ }
2035
+ };
2036
+
2037
+ // src/domain/featureFlags/providers/file.ts
2038
+ var FileFeatureFlagProvider = class extends FeatureFlagProvider {
2039
+ static {
2040
+ __name(this, "FileFeatureFlagProvider");
2041
+ }
2042
+ fileWatcher;
2043
+ /**
2044
+ * Creates a new file feature flag provider.
2045
+ *
2046
+ * @param config - Provider configuration with file settings
2047
+ * @param features - Record of feature flag keys to their default values
2048
+ */
2049
+ constructor(config, features) {
2050
+ super(config, features);
2051
+ this.validateConfig();
2052
+ this.setupFileWatcher();
2053
+ throw new Error("File provider implementation coming soon");
2054
+ }
2055
+ /**
2056
+ * Fetches flags and rules from the configuration file.
2057
+ *
2058
+ * @protected
2059
+ * @returns Promise resolving to flags and rules from file
2060
+ */
2061
+ async fetchData() {
2062
+ throw new Error(
2063
+ 'File Provider is not yet fully implemented. This requires dedicated file paths to be configured in @plyaz/core.\n\nRequired Implementation:\n1. Define standard file paths for feature flags\n2. Implement file reading and parsing logic\n3. Add YAML and JSON format support\n4. Set up file watching for hot reload\n5. Add file validation and error handling\n\nRecommended File Structure:\n@plyaz/core/config/\n├── feature-flags.json\n├── feature-flags.yaml\n├── rules/\n│ ├── targeting-rules.json\n│ └── rollout-rules.json\n└── environments/\n ├── development.json\n ├── staging.json\n └── production.json\n\nExample Configuration:\n{\n provider: "file",\n fileConfig: {\n filePath: "@plyaz/core/config/feature-flags.json",\n format: "json",\n shouldWatchForChanges: true\n },\n isCacheEnabled: true,\n cacheTtl: 60\n}\n\nNote: Default provider should be memory for testing environments.\nFile provider is intended for configuration-driven deployments.\n\nMigration Note: This provider will be moved to @plyaz/core/src/domain/featureFlags/providers/'
2064
+ );
2065
+ }
2066
+ /**
2067
+ * Validates the file provider configuration.
2068
+ *
2069
+ * @private
2070
+ * @throws Error if configuration is invalid
2071
+ */
2072
+ validateConfig() {
2073
+ if (this.config.provider !== "file") {
2074
+ throw new Error('File provider requires provider to be set to "file"');
2075
+ }
2076
+ if (!this.config.fileConfig) {
2077
+ throw new Error("File configuration is required for file provider");
2078
+ }
2079
+ const { filePath, format } = this.config.fileConfig;
2080
+ if (!filePath) {
2081
+ throw new Error("File path is required");
2082
+ }
2083
+ if (!format || !["json", "yaml"].includes(format)) {
2084
+ throw new Error('File format must be either "json" or "yaml"');
2085
+ }
2086
+ }
2087
+ /**
2088
+ * Sets up file watching for hot reload if enabled.
2089
+ *
2090
+ * @private
2091
+ */
2092
+ setupFileWatcher() {
2093
+ if (!this.config.fileConfig?.shouldWatchForChanges) {
2094
+ return;
2095
+ }
2096
+ this.log("File watching would be enabled for hot reload");
2097
+ this.log("File path:", this.config.fileConfig.filePath);
2098
+ }
2099
+ /**
2100
+ * Disposes of the file provider and stops file watching.
2101
+ */
2102
+ dispose() {
2103
+ super.dispose();
2104
+ if (this.fileWatcher) {
2105
+ clearInterval(this.fileWatcher);
2106
+ this.fileWatcher = void 0;
2107
+ this.log("File watching stopped");
2108
+ }
2109
+ }
2110
+ /**
2111
+ * Gets information about the file provider.
2112
+ *
2113
+ * @returns File provider information
2114
+ */
2115
+ getFileInfo() {
2116
+ return {
2117
+ filePath: this.config.fileConfig?.filePath,
2118
+ format: this.config.fileConfig?.format,
2119
+ isWatchEnabled: Boolean(this.config.fileConfig?.shouldWatchForChanges),
2120
+ isImplemented: false,
2121
+ requiredImplementation: [
2122
+ "File reading and parsing logic",
2123
+ "YAML and JSON format support",
2124
+ "File watching for hot reload",
2125
+ "Standard file path configuration",
2126
+ "File validation and error handling"
2127
+ ]
2128
+ };
2129
+ }
2130
+ };
2131
+
2132
+ // src/domain/featureFlags/providers/redis.ts
2133
+ var RedisFeatureFlagProvider = class extends FeatureFlagProvider {
2134
+ static {
2135
+ __name(this, "RedisFeatureFlagProvider");
2136
+ }
2137
+ /**
2138
+ * Creates a new Redis feature flag provider.
2139
+ *
2140
+ * @param config - Provider configuration with Redis settings
2141
+ * @param features - Record of feature flag keys to their default values
2142
+ */
2143
+ constructor(config, features) {
2144
+ super(config, features);
2145
+ this.validateConfig();
2146
+ throw new Error("Redis provider requires @plyaz/core on Cache implementation");
2147
+ }
2148
+ /**
2149
+ * Fetches flags and rules from Redis storage.
2150
+ *
2151
+ * @protected
2152
+ * @returns Promise resolving to flags and rules from Redis
2153
+ */
2154
+ async fetchData() {
2155
+ throw new Error(
2156
+ 'Redis Provider is not yet fully implemented. This requires integration with the cache layer.\n\nRequired Implementation:\n1. Integrate with cache/strategies/redis.ts\n2. Implement Redis data storage patterns\n3. Add Redis client management\n4. Set up data serialization/deserialization\n5. Add connection health monitoring\n6. Implement Redis key management strategies\n\nRedis Storage Patterns:\n- Hash-based storage (recommended)\n- List-based storage\n- String/JSON storage\n\nKey Structure:\n- {prefix}:flags - Feature flags hash\n- {prefix}:rules - Targeting rules list\n- {prefix}:overrides - Manual overrides hash\n- {prefix}:meta - Metadata and versioning\n\nExample Configuration:\n{\n provider: "redis",\n redisConfig: {\n url: "redis://localhost:6379",\n keyPrefix: "plyaz:feature_flags"\n },\n isCacheEnabled: true,\n cacheTtl: 300\n}\n\nRedis Provider Benefits:\n- Distributed caching across instances\n- Real-time flag updates\n- Persistent storage option\n- High performance evaluation\n\nMigration Note: This provider will be moved to @plyaz/core/src/domain/featureFlags/providers/\nIt should integrate with the cache layer at @plyaz/core/src/cache/strategies/redis.ts'
2157
+ );
2158
+ }
2159
+ /**
2160
+ * Validates the Redis provider configuration.
2161
+ *
2162
+ * @private
2163
+ * @throws Error if configuration is invalid
2164
+ */
2165
+ validateConfig() {
2166
+ if (this.config.provider !== "redis") {
2167
+ throw new Error('Redis provider requires provider to be set to "redis"');
2168
+ }
2169
+ if (!this.config.redisConfig) {
2170
+ throw new Error("Redis configuration is required for Redis provider");
2171
+ }
2172
+ if (!this.config.redisConfig.url) {
2173
+ throw new Error("Redis URL is required");
2174
+ }
2175
+ if (!this.isValidRedisUrl(this.config.redisConfig.url)) {
2176
+ throw new Error("Redis URL must be a valid Redis connection string (redis:// or rediss://)");
2177
+ }
2178
+ this.log("Redis provider configuration is valid, but implementation is not ready");
2179
+ this.log("Redis URL:", this.config.redisConfig.url);
2180
+ this.log("Key Prefix:", this.config.redisConfig.keyPrefix ?? "feature_flags");
2181
+ }
2182
+ /**
2183
+ * Validates Redis URL format.
2184
+ *
2185
+ * @private
2186
+ * @param url - URL to validate
2187
+ * @returns True if valid Redis URL
2188
+ */
2189
+ isValidRedisUrl(url) {
2190
+ return url.startsWith("redis://") || url.startsWith("rediss://");
2191
+ }
2192
+ /**
2193
+ * Gets Redis provider information.
2194
+ *
2195
+ * @returns Redis provider status information
2196
+ */
2197
+ getRedisInfo() {
2198
+ return {
2199
+ url: this.config.redisConfig?.url,
2200
+ keyPrefix: this.config.redisConfig?.keyPrefix ?? "feature_flags",
2201
+ isImplemented: false,
2202
+ requiredImplementation: [
2203
+ "Integration with cache/strategies/redis.ts",
2204
+ "Redis data storage patterns",
2205
+ "Client management and health monitoring",
2206
+ "Data serialization/deserialization",
2207
+ "Key management strategies"
2208
+ ],
2209
+ recommendedPatterns: [
2210
+ "Hash-based storage for flags",
2211
+ "List-based storage for rules",
2212
+ "Pub/Sub for real-time updates",
2213
+ "TTL for automatic cleanup"
2214
+ ]
2215
+ };
2216
+ }
2217
+ };
2218
+
2219
+ // src/domain/featureFlags/providers/api.ts
2220
+ var ApiFeatureFlagProvider = class extends FeatureFlagProvider {
2221
+ static {
2222
+ __name(this, "ApiFeatureFlagProvider");
2223
+ }
2224
+ /**
2225
+ * Creates a new API feature flag provider.
2226
+ *
2227
+ * @param config - Provider configuration with API settings
2228
+ * @throws Error indicating that @plyaz/api implementation is required
2229
+ */
2230
+ constructor(config, features) {
2231
+ super(config, features);
2232
+ this.validateConfig();
2233
+ throw new Error("API provider requires @plyaz/api package implementation");
2234
+ }
2235
+ /**
2236
+ * Fetches flags and rules from the API endpoint.
2237
+ * Currently throws an error as the API implementation is not ready.
2238
+ *
2239
+ * @protected
2240
+ * @returns Promise that rejects with implementation error
2241
+ * @throws Error indicating missing API implementation
2242
+ */
2243
+ async fetchData() {
2244
+ throw new Error(
2245
+ 'API Provider is not yet implemented. This requires @plyaz/api package with the following endpoints:\n\nRequired API Endpoints (to be implemented in @plyaz/api):\n- GET /api/v1/feature-flags (get all flags)\n- GET /api/v1/feature-flag-rules (get all rules)\n- POST /api/v1/feature-flags/evaluate (evaluate flags)\n- POST /api/v1/feature-flags/evaluate/bulk (bulk evaluation)\n\nRequired Backend Implementation:\n1. Install and configure @plyaz/api package\n2. Implement the API endpoints in NestJS modules\n3. Set up authentication (API key or JWT)\n4. Configure CORS and rate limiting\n5. Add request/response validation\n6. Set up database integration with @plyaz/db\n\nExample Configuration:\n{\n provider: "api",\n apiEndpoint: "https://api.plyaz.co.uk",\n apiKey: "your-api-key",\n isCacheEnabled: true,\n cacheTtl: 300\n}\n\nSee /docs/feature-flag-to-implement/api-requirements.md for complete implementation details.\n\nMigration Note: This provider will be moved to @plyaz/core/src/domain/featureFlags/providers/'
2246
+ );
2247
+ }
2248
+ /**
2249
+ * Validates the API provider configuration.
2250
+ *
2251
+ * @private
2252
+ * @throws Error if configuration is invalid or incomplete
2253
+ */
2254
+ validateConfig() {
2255
+ if (this.config.provider !== "api") {
2256
+ throw new Error('API provider requires provider to be set to "api"');
2257
+ }
2258
+ if (!this.config.apiEndpoint) {
2259
+ throw new Error(
2260
+ 'API endpoint is required for API provider. Set apiEndpoint in your configuration.\nExample: apiEndpoint: "https://api.plyaz.co.uk"'
2261
+ );
2262
+ }
2263
+ if (!this.config.apiKey) {
2264
+ throw new Error(
2265
+ "API key is required for API provider. Set apiKey in your configuration.\nExample: apiKey: process.env.FEATURE_FLAG_API_KEY"
2266
+ );
2267
+ }
2268
+ if (!this.isValidUrl(this.config.apiEndpoint)) {
2269
+ throw new Error(
2270
+ `API endpoint must be a valid URL. Received: ${this.config.apiEndpoint}
2271
+ Example: "https://api.plyaz.co.uk"`
2272
+ );
2273
+ }
2274
+ this.log("API provider configuration is valid, but implementation is not ready");
2275
+ this.log("API Endpoint:", this.config.apiEndpoint);
2276
+ this.log("API Key:", this.config.apiKey ? "[SET]" : "[MISSING]");
2277
+ }
2278
+ /**
2279
+ * Validates URL format.
2280
+ *
2281
+ * @private
2282
+ * @param url - URL to validate
2283
+ * @returns True if valid URL
2284
+ */
2285
+ isValidUrl(url) {
2286
+ try {
2287
+ new URL(url);
2288
+ return true;
2289
+ } catch {
2290
+ return false;
2291
+ }
2292
+ }
2293
+ /**
2294
+ * Gets API provider status and configuration info.
2295
+ *
2296
+ * @returns API provider status information
2297
+ */
2298
+ getApiInfo() {
2299
+ return {
2300
+ endpoint: this.config.apiEndpoint,
2301
+ hasApiKey: Boolean(this.config.apiKey),
2302
+ isImplemented: false,
2303
+ requiredPackages: ["@plyaz/api"],
2304
+ documentationPath: "/docs/feature-flag-to-implement/api-requirements.md"
2305
+ };
2306
+ }
2307
+ };
2308
+
2309
+ // src/domain/featureFlags/providers/database.ts
2310
+ var DatabaseFeatureFlagProvider = class extends FeatureFlagProvider {
2311
+ static {
2312
+ __name(this, "DatabaseFeatureFlagProvider");
2313
+ }
2314
+ /**
2315
+ * Creates a new database feature flag provider.
2316
+ *
2317
+ * @param config - Provider configuration with database settings
2318
+ * @throws Error indicating that @plyaz/db implementation is required
2319
+ */
2320
+ constructor(config, features) {
2321
+ super(config, features);
2322
+ this.validateConfig();
2323
+ throw new Error("Database provider requires @plyaz/db package implementation");
2324
+ }
2325
+ /**
2326
+ * Fetches flags and rules from the database.
2327
+ * Currently throws an error as the database implementation is not ready.
2328
+ *
2329
+ * @protected
2330
+ * @returns Promise that rejects with implementation error
2331
+ * @throws Error indicating missing database implementation
2332
+ */
2333
+ async fetchData() {
2334
+ throw new Error(
2335
+ 'Database Provider is not yet implemented. This requires @plyaz/db package with the following components:\n\nRequired Database Setup:\n1. PostgreSQL or MySQL database\n2. Tables created using provided schema\n3. ORM implementation (Drizzle or Prisma)\n4. Repository pattern for data access\n5. NestJS modules and services\n\nRequired Tables:\n- feature_flags (main flags table)\n- feature_flag_rules (targeting rules table)\n- feature_flag_evaluations (audit log table)\n- feature_flag_overrides (temporary overrides table)\n\nDatabase Schema:\nThe complete schema is provided in:\n/docs/feature-flag-to-implement/database-requirements.md\n\nRequired Implementation Steps:\n1. Install and configure @plyaz/db package\n2. Set up database connection and ORM\n3. Create tables using the provided SQL schema\n4. Implement FeatureFlagsRepository interface\n5. Add NestJS modules, services, and controllers\n6. Set up database migrations\n7. Add comprehensive test coverage\n\nExample Configuration:\n{\n provider: "database",\n databaseConfig: {\n connectionString: "postgresql://user:pass@localhost:5432/plyaz",\n tableName: "feature_flags"\n },\n isCacheEnabled: true,\n cacheTtl: 300\n}\n\nSee /docs/feature-flag-to-implement/database-requirements.md for complete implementation details.\n\nMigration Note: This provider will be moved to @plyaz/core/src/domain/featureFlags/providers/'
2336
+ );
2337
+ }
2338
+ /**
2339
+ * Validates the database provider configuration.
2340
+ *
2341
+ * @private
2342
+ * @throws Error if configuration is invalid or incomplete
2343
+ */
2344
+ validateConfig() {
2345
+ this.validateProviderType();
2346
+ this.validateDatabaseConfig();
2347
+ this.validateConnectionString();
2348
+ this.logConfigurationStatus();
2349
+ }
2350
+ validateProviderType() {
2351
+ if (this.config.provider !== "database") {
2352
+ throw new Error('Database provider requires provider to be set to "database"');
2353
+ }
2354
+ }
2355
+ validateDatabaseConfig() {
2356
+ if (!this.config.databaseConfig) {
2357
+ throw new Error(
2358
+ 'Database configuration is required for database provider. Set databaseConfig in your configuration.\nExample: databaseConfig: { connectionString: "postgresql://...", tableName: "feature_flags" }'
2359
+ );
2360
+ }
2361
+ }
2362
+ validateConnectionString() {
2363
+ const { connectionString, tableName } = this.config.databaseConfig;
2364
+ if (!connectionString) {
2365
+ throw new Error(
2366
+ 'Database connection string is required. Set connectionString in your databaseConfig.\nExample: connectionString: "postgresql://user:pass@localhost:5432/plyaz"'
2367
+ );
2368
+ }
2369
+ if (!this.isValidPostgresUrl(connectionString) && !this.isValidMysqlUrl(connectionString)) {
2370
+ throw new Error(
2371
+ `Database connection string must be a valid PostgreSQL or MySQL URL. Received: ${connectionString}
2372
+ Examples:
2373
+ - PostgreSQL: "postgresql://user:pass@localhost:5432/plyaz"
2374
+ - MySQL: "mysql://user:pass@localhost:3306/plyaz"`
2375
+ );
2376
+ }
2377
+ if (!tableName || typeof tableName !== "string") {
2378
+ throw new Error("Database provider requires databaseConfig.tableName");
2379
+ }
2380
+ }
2381
+ logConfigurationStatus() {
2382
+ const { connectionString, tableName } = this.config.databaseConfig;
2383
+ this.log("Database provider configuration is valid, but implementation is not ready");
2384
+ this.log("Connection String:", this.maskConnectionString(connectionString));
2385
+ this.log("Table Name:", tableName ?? "feature_flags (default)");
2386
+ }
2387
+ /**
2388
+ * Validates PostgreSQL URL format.
2389
+ *
2390
+ * @private
2391
+ * @param url - URL to validate
2392
+ * @returns True if valid PostgreSQL URL
2393
+ */
2394
+ isValidPostgresUrl(url) {
2395
+ return url.startsWith("postgresql://") || url.startsWith("postgres://");
2396
+ }
2397
+ /**
2398
+ * Validates MySQL URL format.
2399
+ *
2400
+ * @private
2401
+ * @param url - URL to validate
2402
+ * @returns True if valid MySQL URL
2403
+ */
2404
+ isValidMysqlUrl(url) {
2405
+ return url.startsWith("mysql://");
2406
+ }
2407
+ /**
2408
+ * Masks sensitive parts of connection string for logging.
2409
+ *
2410
+ * @private
2411
+ * @param connectionString - Original connection string
2412
+ * @returns Masked connection string
2413
+ */
2414
+ maskConnectionString(connectionString) {
2415
+ try {
2416
+ const url = new URL(connectionString);
2417
+ const masked = `${url.protocol}//${url.username}:****@${url.host}${url.pathname}`;
2418
+ return masked;
2419
+ } catch {
2420
+ return "[INVALID_URL]";
2421
+ }
2422
+ }
2423
+ /**
2424
+ * Gets database provider status and configuration info.
2425
+ *
2426
+ * @returns Database provider status information
2427
+ */
2428
+ getDatabaseInfo() {
2429
+ return {
2430
+ connectionString: this.config.databaseConfig?.connectionString ? this.maskConnectionString(this.config.databaseConfig.connectionString) : void 0,
2431
+ tableName: this.config.databaseConfig?.tableName ?? "feature_flags",
2432
+ isImplemented: false,
2433
+ requiredPackages: ["@plyaz/db"],
2434
+ recommendedORM: ["drizzle-orm", "prisma"],
2435
+ documentationPath: "/docs/feature-flag-to-implement/database-requirements.md",
2436
+ schemaPath: "/docs/feature-flag-to-implement/database-requirements.md#database-schema"
2437
+ };
2438
+ }
2439
+ };
2440
+
2441
+ // src/domain/featureFlags/providers/factory.ts
2442
+ var PROVIDER_REGISTRY = {
2443
+ memory: MemoryFeatureFlagProvider,
2444
+ file: FileFeatureFlagProvider,
2445
+ redis: RedisFeatureFlagProvider,
2446
+ api: ApiFeatureFlagProvider,
2447
+ database: DatabaseFeatureFlagProvider
2448
+ };
2449
+ var FeatureFlagProviderFactory = class {
2450
+ static {
2451
+ __name(this, "FeatureFlagProviderFactory");
2452
+ }
2453
+ /**
2454
+ * Creates a new feature flag provider instance based on configuration.
2455
+ *
2456
+ * @param config - Provider configuration
2457
+ * @param features - Record of feature flag keys to their default values
2458
+ * @returns Configured provider instance
2459
+ * @throws Error if provider type is unsupported or configuration is invalid
2460
+ */
2461
+ static create(config, features) {
2462
+ this.validateConfig(config);
2463
+ const ProviderClass = PROVIDER_REGISTRY[config.provider];
2464
+ if (!ProviderClass) {
2465
+ throw new Error(
2466
+ `Unsupported provider type: ${config.provider}. Supported types: ${this.getSupportedProviders().join(", ")}`
2467
+ );
2468
+ }
2469
+ try {
2470
+ return new ProviderClass(config, features);
2471
+ } catch (error) {
2472
+ throw new Error(
2473
+ `Failed to create ${config.provider} provider: ${error instanceof Error ? error.message : "Unknown error"}`
2474
+ );
2475
+ }
2476
+ }
2477
+ /**
2478
+ * Creates a provider with automatic initialization.
2479
+ *
2480
+ * @param config - Provider configuration
2481
+ * @param features - Record of feature flag keys to their default values
2482
+ * @returns Promise resolving to initialized provider instance
2483
+ */
2484
+ static async createAndInitialize(config, features) {
2485
+ const provider = this.create(config, features);
2486
+ await provider.initialize();
2487
+ return provider;
2488
+ }
2489
+ /**
2490
+ * Gets a list of all supported provider types.
2491
+ *
2492
+ * @returns Array of supported provider names
2493
+ */
2494
+ static getSupportedProviders() {
2495
+ return Object.keys(PROVIDER_REGISTRY);
2496
+ }
2497
+ /**
2498
+ * Checks if a provider type is supported.
2499
+ *
2500
+ * @param providerType - Provider type to check
2501
+ * @returns True if provider type is supported
2502
+ */
2503
+ static isProviderSupported(providerType) {
2504
+ return providerType in PROVIDER_REGISTRY;
2505
+ }
2506
+ /**
2507
+ * Gets provider information including implementation status.
2508
+ *
2509
+ * @returns Record of provider information
2510
+ */
2511
+ static getProvidersInfo() {
2512
+ return {
2513
+ memory: {
2514
+ name: "Memory Provider",
2515
+ isImplemented: true,
2516
+ description: "In-memory provider using FEATURES constant"
2517
+ },
2518
+ file: {
2519
+ name: "File Provider",
2520
+ isImplemented: true,
2521
+ description: "File-based provider supporting JSON and YAML formats"
2522
+ },
2523
+ redis: {
2524
+ name: "Redis Provider",
2525
+ isImplemented: true,
2526
+ description: "Redis-based provider for distributed caching",
2527
+ requirements: ["ioredis or redis package"]
2528
+ },
2529
+ api: {
2530
+ name: "API Provider",
2531
+ isImplemented: false,
2532
+ description: "Remote API provider for centralized flag management",
2533
+ requirements: ["@plyaz/api package", "API endpoints implementation"]
2534
+ },
2535
+ database: {
2536
+ name: "Database Provider",
2537
+ isImplemented: false,
2538
+ description: "Database provider for persistent flag storage",
2539
+ requirements: ["@plyaz/db package", "Database schema setup"]
2540
+ }
2541
+ };
2542
+ }
2543
+ /**
2544
+ * Creates a default memory provider for testing environments.
2545
+ *
2546
+ * @param features - Record of feature flag keys to their default values
2547
+ * @param overrides - Optional configuration overrides
2548
+ * @returns Memory provider instance
2549
+ */
2550
+ static createDefault(features, overrides) {
2551
+ const defaultConfig = {
2552
+ provider: "memory",
2553
+ isCacheEnabled: true,
2554
+ cacheTtl: 300,
2555
+ // 5 minutes
2556
+ refreshInterval: 0,
2557
+ // No auto-refresh
2558
+ isLoggingEnabled: false,
2559
+ shouldFallbackToDefaults: true,
2560
+ ...overrides
2561
+ };
2562
+ return this.create(defaultConfig, features);
2563
+ }
2564
+ /**
2565
+ * Validates provider configuration before instantiation.
2566
+ *
2567
+ * @private
2568
+ * @param config - Configuration to validate
2569
+ * @throws Error if configuration is invalid
2570
+ */
2571
+ static validateConfig(config) {
2572
+ if (!config) {
2573
+ throw new Error("Provider configuration is required");
2574
+ }
2575
+ if (!config.provider) {
2576
+ throw new Error("Provider type is required in configuration");
2577
+ }
2578
+ if (!this.isProviderSupported(config.provider)) {
2579
+ throw new Error(
2580
+ `Unsupported provider type: ${config.provider}. Supported providers: ${this.getSupportedProviders().join(", ")}`
2581
+ );
2582
+ }
2583
+ if (config.isCacheEnabled && config.cacheTtl <= 0) {
2584
+ throw new Error("Cache TTL must be greater than 0 when caching is enabled");
2585
+ }
2586
+ if (config.refreshInterval < 0) {
2587
+ throw new Error("Refresh interval cannot be negative");
2588
+ }
2589
+ this.validateProviderSpecificConfig(config);
2590
+ }
2591
+ /**
2592
+ * Validates provider-specific configuration requirements.
2593
+ *
2594
+ * @private
2595
+ * @param config - Configuration to validate
2596
+ * @throws Error if provider-specific configuration is invalid
2597
+ */
2598
+ static validateProviderSpecificConfig(config) {
2599
+ const validationMap = {
2600
+ file: /* @__PURE__ */ __name(() => this.validateFileConfig(config), "file"),
2601
+ redis: /* @__PURE__ */ __name(() => this.validateRedisConfig(config), "redis"),
2602
+ api: /* @__PURE__ */ __name(() => this.validateApiConfig(config), "api"),
2603
+ database: /* @__PURE__ */ __name(() => this.validateDatabaseConfig(config), "database"),
2604
+ memory: /* @__PURE__ */ __name(() => {
2605
+ }, "memory")
2606
+ // Memory provider has no additional requirements
2607
+ };
2608
+ const validator = validationMap[config.provider];
2609
+ if (!validator) {
2610
+ throw new Error(`Unknown provider type: ${config.provider}`);
2611
+ }
2612
+ validator();
2613
+ }
2614
+ static validateFileConfig(config) {
2615
+ if (!config.fileConfig) {
2616
+ throw new Error("File configuration is required for file provider");
2617
+ }
2618
+ }
2619
+ static validateRedisConfig(config) {
2620
+ if (!config.redisConfig) {
2621
+ throw new Error("Redis configuration is required for Redis provider");
2622
+ }
2623
+ }
2624
+ static validateApiConfig(config) {
2625
+ if (!config.apiEndpoint) {
2626
+ throw new Error("API endpoint is required for API provider");
2627
+ }
2628
+ }
2629
+ static validateDatabaseConfig(config) {
2630
+ if (!config.databaseConfig) {
2631
+ throw new Error("Database configuration is required for database provider");
2632
+ }
2633
+ }
2634
+ };
2635
+ var DEFAULT_FEATURE_FLAG_CONFIG = {
2636
+ provider: "memory",
2637
+ isCacheEnabled: true,
2638
+ cacheTtl: 300,
2639
+ // 5 minutes
2640
+ refreshInterval: 0,
2641
+ // No auto-refresh
2642
+ shouldFallbackToDefaults: true,
2643
+ isLoggingEnabled: false
2644
+ };
2645
+ var FeatureFlagSystem = {
2646
+ /**
2647
+ * Initialize for frontend/client applications.
2648
+ */
2649
+ initializeForFrontend: /* @__PURE__ */ __name(async (config$1 = {}) => {
2650
+ const finalConfig = {
2651
+ ...DEFAULT_FEATURE_FLAG_CONFIG,
2652
+ ...config$1
2653
+ };
2654
+ const provider = FeatureFlagProviderFactory.create(finalConfig, config.FEATURES);
2655
+ await provider.initialize();
2656
+ return provider;
2657
+ }, "initializeForFrontend"),
2658
+ /**
2659
+ * Initialize for backend/server applications.
2660
+ */
2661
+ initializeForBackend: /* @__PURE__ */ __name(async (config$1 = {}) => {
2662
+ const finalConfig = {
2663
+ ...DEFAULT_FEATURE_FLAG_CONFIG,
2664
+ ...config$1
2665
+ };
2666
+ const provider = FeatureFlagProviderFactory.create(finalConfig, config.FEATURES);
2667
+ await provider.initialize();
2668
+ return provider;
2669
+ }, "initializeForBackend"),
2670
+ /**
2671
+ * Initialize for testing environments.
2672
+ */
2673
+ initializeForTesting: /* @__PURE__ */ __name(async (overrides = {}) => {
2674
+ const provider = FeatureFlagProviderFactory.createDefault(config.FEATURES, {
2675
+ provider: "memory",
2676
+ isCacheEnabled: false,
2677
+ isLoggingEnabled: false
2678
+ });
2679
+ await provider.initialize();
2680
+ Object.entries(overrides).forEach(([key, value]) => {
2681
+ provider.setOverride(key, value);
2682
+ });
2683
+ return provider;
2684
+ }, "initializeForTesting")
2685
+ };
2686
+ exports.FeatureFlagController = class FeatureFlagController {
2687
+ constructor(featureFlagService) {
2688
+ this.featureFlagService = featureFlagService;
2689
+ }
2690
+ async evaluateFlag(key, body = {}) {
2691
+ try {
2692
+ return await this.featureFlagService.evaluateFlag(key, body.context);
2693
+ } catch (error) {
2694
+ throw new common.HttpException(
2695
+ `Failed to evaluate flag: ${error instanceof Error ? error.message : "Unknown error"}`,
2696
+ common.HttpStatus.BAD_REQUEST
2697
+ );
2698
+ }
2699
+ }
2700
+ async isEnabled(key, body = {}) {
2701
+ try {
2702
+ const isEnabled = await this.featureFlagService.isEnabled(key, body.context);
2703
+ return { isEnabled };
2704
+ } catch (error) {
2705
+ throw new common.HttpException(
2706
+ `Failed to check flag status: ${error instanceof Error ? error.message : "Unknown error"}`,
2707
+ common.HttpStatus.BAD_REQUEST
2708
+ );
2709
+ }
2710
+ }
2711
+ async evaluateAllFlags(body = {}) {
2712
+ try {
2713
+ return await this.featureFlagService.getAllFlags(body.context);
2714
+ } catch (error) {
2715
+ throw new common.HttpException(
2716
+ `Failed to evaluate all flags: ${error instanceof Error ? error.message : "Unknown error"}`,
2717
+ common.HttpStatus.INTERNAL_SERVER_ERROR
2718
+ );
2719
+ }
2720
+ }
2721
+ async createFlag(createData) {
2722
+ try {
2723
+ return await this.featureFlagService.createFlag(createData);
2724
+ } catch (error) {
2725
+ throw new common.HttpException(
2726
+ `Failed to create flag: ${error instanceof Error ? error.message : "Unknown error"}`,
2727
+ common.HttpStatus.BAD_REQUEST
2728
+ );
2729
+ }
2730
+ }
2731
+ async updateFlag(key, updateData) {
2732
+ try {
2733
+ return await this.featureFlagService.updateFlag(key, updateData);
2734
+ } catch (error) {
2735
+ throw new common.HttpException(
2736
+ `Failed to update flag: ${error instanceof Error ? error.message : "Unknown error"}`,
2737
+ common.HttpStatus.BAD_REQUEST
2738
+ );
2739
+ }
2740
+ }
2741
+ async deleteFlag(key) {
2742
+ try {
2743
+ await this.featureFlagService.deleteFlag(key);
2744
+ return { isSuccessful: true };
2745
+ } catch (error) {
2746
+ throw new common.HttpException(
2747
+ `Failed to delete flag: ${error instanceof Error ? error.message : "Unknown error"}`,
2748
+ common.HttpStatus.BAD_REQUEST
2749
+ );
2750
+ }
2751
+ }
2752
+ async setOverride(key, value) {
2753
+ try {
2754
+ await this.featureFlagService.setOverride(key, value);
2755
+ return { isSuccessful: true };
2756
+ } catch (error) {
2757
+ throw new common.HttpException(
2758
+ `Failed to set override: ${error instanceof Error ? error.message : "Unknown error"}`,
2759
+ common.HttpStatus.BAD_REQUEST
2760
+ );
2761
+ }
2762
+ }
2763
+ async removeOverride(key) {
2764
+ try {
2765
+ await this.featureFlagService.removeOverride(key);
2766
+ return { isSuccessful: true };
2767
+ } catch (error) {
2768
+ throw new common.HttpException(
2769
+ `Failed to remove override: ${error instanceof Error ? error.message : "Unknown error"}`,
2770
+ common.HttpStatus.BAD_REQUEST
2771
+ );
2772
+ }
2773
+ }
2774
+ async getAllFeatureFlags(environment) {
2775
+ try {
2776
+ return await this.featureFlagService.getAllFeatureFlags(environment);
2777
+ } catch (error) {
2778
+ throw new common.HttpException(
2779
+ `Failed to get flags: ${error instanceof Error ? error.message : "Unknown error"}`,
2780
+ common.HttpStatus.INTERNAL_SERVER_ERROR
2781
+ );
2782
+ }
2783
+ }
2784
+ async getFlagRules(key) {
2785
+ try {
2786
+ return await this.featureFlagService.getFlagRules(key);
2787
+ } catch (error) {
2788
+ throw new common.HttpException(
2789
+ `Failed to get flag rules: ${error instanceof Error ? error.message : "Unknown error"}`,
2790
+ common.HttpStatus.BAD_REQUEST
2791
+ );
2792
+ }
2793
+ }
2794
+ async refreshCache() {
2795
+ try {
2796
+ await this.featureFlagService.refreshCache();
2797
+ return { isSuccessful: true };
2798
+ } catch (error) {
2799
+ throw new common.HttpException(
2800
+ `Failed to refresh cache: ${error instanceof Error ? error.message : "Unknown error"}`,
2801
+ common.HttpStatus.INTERNAL_SERVER_ERROR
2802
+ );
2803
+ }
2804
+ }
2805
+ };
2806
+ __name(exports.FeatureFlagController, "FeatureFlagController");
2807
+ __decorateClass([
2808
+ common.Post(":key/evaluate"),
2809
+ __decorateParam(0, common.Param("key")),
2810
+ __decorateParam(1, common.Body())
2811
+ ], exports.FeatureFlagController.prototype, "evaluateFlag", 1);
2812
+ __decorateClass([
2813
+ common.Post(":key/enabled"),
2814
+ __decorateParam(0, common.Param("key")),
2815
+ __decorateParam(1, common.Body())
2816
+ ], exports.FeatureFlagController.prototype, "isEnabled", 1);
2817
+ __decorateClass([
2818
+ common.Post("evaluate-all"),
2819
+ __decorateParam(0, common.Body())
2820
+ ], exports.FeatureFlagController.prototype, "evaluateAllFlags", 1);
2821
+ __decorateClass([
2822
+ common.Post(),
2823
+ __decorateParam(0, common.Body())
2824
+ ], exports.FeatureFlagController.prototype, "createFlag", 1);
2825
+ __decorateClass([
2826
+ common.Put(":key"),
2827
+ __decorateParam(0, common.Param("key")),
2828
+ __decorateParam(1, common.Body())
2829
+ ], exports.FeatureFlagController.prototype, "updateFlag", 1);
2830
+ __decorateClass([
2831
+ common.Delete(":key"),
2832
+ __decorateParam(0, common.Param("key"))
2833
+ ], exports.FeatureFlagController.prototype, "deleteFlag", 1);
2834
+ __decorateClass([
2835
+ common.Post(":key/override"),
2836
+ __decorateParam(0, common.Param("key")),
2837
+ __decorateParam(1, common.Body("value"))
2838
+ ], exports.FeatureFlagController.prototype, "setOverride", 1);
2839
+ __decorateClass([
2840
+ common.Delete(":key/override"),
2841
+ __decorateParam(0, common.Param("key"))
2842
+ ], exports.FeatureFlagController.prototype, "removeOverride", 1);
2843
+ __decorateClass([
2844
+ common.Get(),
2845
+ __decorateParam(0, common.Query("environment"))
2846
+ ], exports.FeatureFlagController.prototype, "getAllFeatureFlags", 1);
2847
+ __decorateClass([
2848
+ common.Get(":key/rules"),
2849
+ __decorateParam(0, common.Param("key"))
2850
+ ], exports.FeatureFlagController.prototype, "getFlagRules", 1);
2851
+ __decorateClass([
2852
+ common.Post("refresh")
2853
+ ], exports.FeatureFlagController.prototype, "refreshCache", 1);
2854
+ exports.FeatureFlagController = __decorateClass([
2855
+ common.Controller("feature-flags")
2856
+ ], exports.FeatureFlagController);
2857
+ exports.FeatureFlagService = class FeatureFlagService {
2858
+ constructor(featureFlagRepository) {
2859
+ this.featureFlagRepository = featureFlagRepository;
2860
+ }
2861
+ logger = new common.Logger(exports.FeatureFlagService.name);
2862
+ provider;
2863
+ /**
2864
+ * Initializes the service on module startup.
2865
+ */
2866
+ async onModuleInit() {
2867
+ try {
2868
+ await this.initializeProvider();
2869
+ this.logger.log("Feature flag service initialized successfully");
2870
+ } catch (error) {
2871
+ this.logger.error("Failed to initialize feature flag service", error);
2872
+ throw error;
2873
+ }
2874
+ }
2875
+ /**
2876
+ * Cleans up resources on module shutdown.
2877
+ */
2878
+ async onModuleDestroy() {
2879
+ this.provider.dispose();
2880
+ this.logger.log("Feature flag service disposed");
2881
+ }
2882
+ /**
2883
+ * Initializes the feature flag provider.
2884
+ */
2885
+ async initializeProvider() {
2886
+ const DEFAULT_CACHE_TTL = 300;
2887
+ const config$1 = {
2888
+ provider: process.env.FEATURE_FLAG_PROVIDER ?? "database",
2889
+ isCacheEnabled: process.env.FEATURE_FLAG_CACHE_ENABLED === "true",
2890
+ cacheTtl: Number(process.env.FEATURE_FLAG_CACHE_TTL) || DEFAULT_CACHE_TTL,
2891
+ refreshInterval: Number(process.env.FEATURE_FLAG_REFRESH_INTERVAL) || 0,
2892
+ shouldFallbackToDefaults: true,
2893
+ isLoggingEnabled: process.env.NODE_ENV === "development"
2894
+ };
2895
+ this.provider = FeatureFlagProviderFactory.create(config$1, config.FEATURES);
2896
+ await this.provider.initialize();
2897
+ }
2898
+ /**
2899
+ * Gets the current provider instance.
2900
+ */
2901
+ getProvider() {
2902
+ if (!this.provider) {
2903
+ throw new Error("Feature flag provider not initialized");
2904
+ }
2905
+ return this.provider;
2906
+ }
2907
+ /**
2908
+ * Evaluates a feature flag for the given context.
2909
+ *
2910
+ * @param key - Feature flag key
2911
+ * @param context - Evaluation context
2912
+ * @returns Feature flag evaluation result
2913
+ */
2914
+ async evaluateFlag(key, context) {
2915
+ try {
2916
+ const provider = this.getProvider();
2917
+ const evaluation = await provider.getFlag(key, context);
2918
+ this.logger.debug(`Evaluated flag ${key}: ${evaluation.isEnabled}`, {
2919
+ key,
2920
+ value: evaluation.value,
2921
+ reason: evaluation.reason,
2922
+ context
2923
+ });
2924
+ return evaluation;
2925
+ } catch (error) {
2926
+ this.logger.error(`Failed to evaluate flag ${key}`, error);
2927
+ throw error;
2928
+ }
2929
+ }
2930
+ /**
2931
+ * Checks if a feature flag is enabled.
2932
+ *
2933
+ * @param key - Feature flag key
2934
+ * @param context - Evaluation context
2935
+ * @returns Boolean indicating if flag is enabled
2936
+ */
2937
+ async isEnabled(key, context) {
2938
+ const evaluation = await this.evaluateFlag(key, context);
2939
+ return evaluation.isEnabled;
2940
+ }
2941
+ /**
2942
+ * Gets all feature flags with their evaluations.
2943
+ *
2944
+ * @param context - Evaluation context
2945
+ * @returns All feature flag evaluations
2946
+ */
2947
+ async getAllFlags(context) {
2948
+ try {
2949
+ const provider = this.getProvider();
2950
+ const flags = await provider.getAllFlags();
2951
+ this.logger.debug(`Evaluated all flags for context`, {
2952
+ flagCount: Object.keys(flags).length,
2953
+ context
2954
+ });
2955
+ return flags;
2956
+ } catch (error) {
2957
+ this.logger.error("Failed to evaluate all flags", error);
2958
+ throw error;
2959
+ }
2960
+ }
2961
+ /**
2962
+ * Creates a new feature flag.
2963
+ *
2964
+ * @param createData - Flag creation data
2965
+ * @returns Created feature flag
2966
+ */
2967
+ async createFlag(createData) {
2968
+ try {
2969
+ const flag = await this.featureFlagRepository.createFlag(createData);
2970
+ await this.refreshCache();
2971
+ this.logger.log(`Created feature flag: ${createData.key}`);
2972
+ return flag;
2973
+ } catch (error) {
2974
+ this.logger.error(`Failed to create flag ${createData.key}`, error);
2975
+ throw error;
2976
+ }
2977
+ }
2978
+ /**
2979
+ * Updates an existing feature flag.
2980
+ *
2981
+ * @param key - Feature flag key
2982
+ * @param updateData - Flag update data
2983
+ * @returns Updated feature flag
2984
+ */
2985
+ async updateFlag(key, updateData) {
2986
+ try {
2987
+ const flag = await this.featureFlagRepository.updateFlag(key, updateData);
2988
+ await this.refreshCache();
2989
+ this.logger.log(`Updated feature flag: ${key}`);
2990
+ return flag;
2991
+ } catch (error) {
2992
+ this.logger.error(`Failed to update flag ${key}`, error);
2993
+ throw error;
2994
+ }
2995
+ }
2996
+ /**
2997
+ * Deletes a feature flag.
2998
+ *
2999
+ * @param key - Feature flag key
3000
+ */
3001
+ async deleteFlag(key) {
3002
+ try {
3003
+ await this.featureFlagRepository.deleteFlag(key);
3004
+ await this.refreshCache();
3005
+ this.logger.log(`Deleted feature flag: ${key}`);
3006
+ } catch (error) {
3007
+ this.logger.error(`Failed to delete flag ${key}`, error);
3008
+ throw error;
3009
+ }
3010
+ }
3011
+ /**
3012
+ * Sets a manual override for a flag.
3013
+ *
3014
+ * @param key - Feature flag key
3015
+ * @param value - Override value
3016
+ */
3017
+ async setOverride(key, value) {
3018
+ try {
3019
+ const provider = this.getProvider();
3020
+ provider.setOverride(key, value);
3021
+ this.logger.log(`Set override for flag ${key}: ${value}`);
3022
+ } catch (error) {
3023
+ this.logger.error(`Failed to set override for flag ${key}`, error);
3024
+ throw error;
3025
+ }
3026
+ }
3027
+ /**
3028
+ * Removes a manual override for a flag.
3029
+ *
3030
+ * @param key - Feature flag key
3031
+ */
3032
+ async removeOverride(key) {
3033
+ try {
3034
+ const provider = this.getProvider();
3035
+ provider.removeOverride(key);
3036
+ this.logger.log(`Removed override for flag ${key}`);
3037
+ } catch (error) {
3038
+ this.logger.error(`Failed to remove override for flag ${key}`, error);
3039
+ throw error;
3040
+ }
3041
+ }
3042
+ /**
3043
+ * Gets all available feature flags.
3044
+ *
3045
+ * @param environment - Filter by environment
3046
+ * @returns List of feature flags
3047
+ */
3048
+ async getAllFeatureFlags(environment) {
3049
+ try {
3050
+ return await this.featureFlagRepository.getAllFlags(environment);
3051
+ } catch (error) {
3052
+ this.logger.error("Failed to get all feature flags", error);
3053
+ throw error;
3054
+ }
3055
+ }
3056
+ /**
3057
+ * Gets all rules for a specific flag.
3058
+ *
3059
+ * @param key - Feature flag key
3060
+ * @returns List of rules for the flag
3061
+ */
3062
+ async getFlagRules(key) {
3063
+ try {
3064
+ return await this.featureFlagRepository.getFlagRules(key);
3065
+ } catch (error) {
3066
+ this.logger.error(`Failed to get rules for flag ${key}`, error);
3067
+ throw error;
3068
+ }
3069
+ }
3070
+ /**
3071
+ * Forces a refresh of the feature flag cache.
3072
+ */
3073
+ async refreshCache() {
3074
+ try {
3075
+ const provider = this.getProvider();
3076
+ await provider.refresh();
3077
+ this.logger.log("Feature flag cache refreshed");
3078
+ } catch (error) {
3079
+ this.logger.error("Failed to refresh feature flag cache", error);
3080
+ throw error;
3081
+ }
3082
+ }
3083
+ /**
3084
+ * Gets provider health status.
3085
+ *
3086
+ * @returns Provider health information
3087
+ */
3088
+ async getHealthStatus() {
3089
+ return {
3090
+ isInitialized: !!this.provider,
3091
+ provider: "database",
3092
+ // or get from config
3093
+ isCacheEnabled: true
3094
+ // or get from config
3095
+ };
3096
+ }
3097
+ };
3098
+ __name(exports.FeatureFlagService, "FeatureFlagService");
3099
+ exports.FeatureFlagService = __decorateClass([
3100
+ common.Injectable()
3101
+ ], exports.FeatureFlagService);
3102
+ exports.FeatureFlagRepository = class FeatureFlagRepository {
3103
+ logger = new common.Logger(exports.FeatureFlagRepository.name);
3104
+ // 24 * 60 * 60 * 1000
3105
+ // TODO: Inject database service when @plyaz/db is available
3106
+ // constructor(private readonly databaseService: DatabaseService) {}
3107
+ /**
3108
+ * Creates a new feature flag in the database.
3109
+ *
3110
+ * @param createData - Flag creation data
3111
+ * @returns Created feature flag
3112
+ */
3113
+ async createFlag(createData) {
3114
+ this.logger.log(`Creating flag: ${createData.key}`);
3115
+ const currentTime = /* @__PURE__ */ new Date();
3116
+ const newFlag = {
3117
+ key: createData.key,
3118
+ name: createData.name,
3119
+ description: createData.description ?? `Feature flag for ${createData.key}`,
3120
+ isEnabled: createData.isEnabled ?? true,
3121
+ value: createData.value,
3122
+ type: this.inferFlagType(createData.value),
3123
+ environment: createData.environment ?? "all",
3124
+ rolloutPercentage: createData.rolloutPercentage,
3125
+ createdAt: currentTime,
3126
+ updatedAt: currentTime,
3127
+ createdBy: "api",
3128
+ updatedBy: "api"
3129
+ };
3130
+ this.logger.debug(`Flag created: ${createData.key}`, newFlag);
3131
+ return newFlag;
3132
+ }
3133
+ /**
3134
+ * Updates an existing feature flag.
3135
+ *
3136
+ * @param key - Feature flag key
3137
+ * @param updateData - Flag update data
3138
+ * @returns Updated feature flag
3139
+ */
3140
+ async updateFlag(key, updateData) {
3141
+ this.logger.log(`Updating flag: ${key}`);
3142
+ const updatedFlag = {
3143
+ key,
3144
+ name: updateData.name ?? `Updated ${key}`,
3145
+ description: updateData.description ?? `Updated feature flag for ${key}`,
3146
+ isEnabled: updateData.isEnabled ?? true,
3147
+ value: updateData.value ?? true,
3148
+ type: updateData.value ? this.inferFlagType(updateData.value) : "boolean",
3149
+ environment: updateData.environment ?? "all",
3150
+ rolloutPercentage: updateData.rolloutPercentage,
3151
+ createdAt: new Date(Date.now() - exports.FeatureFlagRepository.ONE_DAY_MS),
3152
+ // 1 day ago
3153
+ updatedAt: /* @__PURE__ */ new Date(),
3154
+ createdBy: "api",
3155
+ updatedBy: "api"
3156
+ };
3157
+ this.logger.debug(`Flag updated: ${key}`, updatedFlag);
3158
+ return updatedFlag;
3159
+ }
3160
+ /**
3161
+ * Deletes a feature flag from the database.
3162
+ *
3163
+ * @param key - Feature flag key
3164
+ */
3165
+ async deleteFlag(key) {
3166
+ this.logger.log(`Deleting flag: ${key}`);
3167
+ this.logger.debug(`Flag deleted: ${key}`);
3168
+ }
3169
+ /**
3170
+ * Gets all feature flags from the database.
3171
+ *
3172
+ * @param environment - Filter by environment
3173
+ * @returns List of feature flags
3174
+ */
3175
+ async getAllFlags(environment) {
3176
+ this.logger.log(`Getting all flags${environment ? ` for environment: ${environment}` : ""}`);
3177
+ const sampleFlags = this.createSampleFlags();
3178
+ const filteredFlags = this.filterFlagsByEnvironment(sampleFlags, environment);
3179
+ this.logger.debug(`Retrieved ${filteredFlags.length} flags`);
3180
+ return filteredFlags;
3181
+ }
3182
+ /**
3183
+ * Creates sample flags for stub implementation.
3184
+ *
3185
+ * @returns Array of sample feature flags
3186
+ */
3187
+ createSampleFlags() {
3188
+ return [
3189
+ {
3190
+ key: "AUTH_GOOGLE",
3191
+ name: "Google Authentication",
3192
+ description: "Enable Google OAuth authentication",
3193
+ isEnabled: true,
3194
+ value: "true",
3195
+ type: "boolean",
3196
+ environment: "all",
3197
+ rolloutPercentage: void 0,
3198
+ createdAt: new Date(Date.now() - exports.FeatureFlagRepository.ONE_DAY_MS),
3199
+ updatedAt: /* @__PURE__ */ new Date(),
3200
+ createdBy: "system",
3201
+ updatedBy: "system"
3202
+ },
3203
+ {
3204
+ key: "AUTH_DISCORD",
3205
+ name: "Discord Authentication",
3206
+ description: "Enable Discord OAuth authentication",
3207
+ isEnabled: true,
3208
+ value: "true",
3209
+ type: "boolean",
3210
+ environment: "all",
3211
+ rolloutPercentage: void 0,
3212
+ createdAt: new Date(Date.now() - exports.FeatureFlagRepository.ONE_DAY_MS),
3213
+ updatedAt: /* @__PURE__ */ new Date(),
3214
+ createdBy: "system",
3215
+ updatedBy: "system"
3216
+ }
3217
+ ];
3218
+ }
3219
+ /**
3220
+ * Filters flags by environment.
3221
+ *
3222
+ * @param flags - Array of flags to filter
3223
+ * @param environment - Environment to filter by
3224
+ * @returns Filtered flags
3225
+ */
3226
+ filterFlagsByEnvironment(flags, environment) {
3227
+ return environment ? flags.filter((flag) => flag.environment === environment || flag.environment === "all") : flags;
3228
+ }
3229
+ /**
3230
+ * Gets all rules for a specific flag.
3231
+ *
3232
+ * @param key - Feature flag key
3233
+ * @returns List of rules for the flag
3234
+ */
3235
+ async getFlagRules(key) {
3236
+ this.logger.log(`Getting rules for flag: ${key}`);
3237
+ const rules = [];
3238
+ this.logger.debug(`Retrieved ${rules.length} rules for flag: ${key}`);
3239
+ return rules;
3240
+ }
3241
+ /**
3242
+ * Gets a single feature flag by key.
3243
+ *
3244
+ * @param key - Feature flag key
3245
+ * @returns Feature flag or null if not found
3246
+ */
3247
+ async getFlagByKey(key) {
3248
+ this.logger.log(`Getting flag by key: ${key}`);
3249
+ return null;
3250
+ }
3251
+ /**
3252
+ * Infers the type of a feature flag value.
3253
+ *
3254
+ * @param value - The flag value
3255
+ * @returns Inferred type string
3256
+ */
3257
+ inferFlagType(value) {
3258
+ if (typeof value === "boolean") return "boolean";
3259
+ if (typeof value === "string") return "string";
3260
+ if (typeof value === "number") return "number";
3261
+ if (typeof value === "object" && value !== null) return "json";
3262
+ return "boolean";
3263
+ }
3264
+ /**
3265
+ * Maps a database row to a FeatureFlag object.
3266
+ * This will be used when @plyaz/db is available.
3267
+ *
3268
+ * @param row - Database row
3269
+ * @returns Mapped feature flag
3270
+ */
3271
+ // private mapRowToFlag(row: any): FeatureFlag {
3272
+ // return {
3273
+ // key: row.key,
3274
+ // name: row.name,
3275
+ // description: row.description,
3276
+ // isEnabled: row.is_enabled,
3277
+ // value: JSON.parse(row.value),
3278
+ // type: row.type,
3279
+ // environment: row.environment,
3280
+ // rolloutPercentage: row.rollout_percentage,
3281
+ // createdAt: row.created_at,
3282
+ // updatedAt: row.updated_at,
3283
+ // createdBy: row.created_by,
3284
+ // updatedBy: row.updated_by,
3285
+ // } satisfies FeatureFlag;
3286
+ // }
3287
+ /**
3288
+ * Maps a database row to a FeatureFlagRule object.
3289
+ * This will be used when @plyaz/db is available.
3290
+ *
3291
+ * @param row - Database row
3292
+ * @returns Mapped feature flag rule
3293
+ */
3294
+ // private mapRowToRule(row: any): FeatureFlagRule {
3295
+ // return {
3296
+ // id: row.id,
3297
+ // flagKey: row.flag_key,
3298
+ // name: row.name,
3299
+ // description: row.description,
3300
+ // isEnabled: row.is_enabled,
3301
+ // priority: row.priority,
3302
+ // conditions: JSON.parse(row.conditions),
3303
+ // value: JSON.parse(row.value),
3304
+ // rolloutPercentage: row.rollout_percentage,
3305
+ // createdAt: row.created_at,
3306
+ // updatedAt: row.updated_at,
3307
+ // createdBy: row.created_by,
3308
+ // updatedBy: row.updated_by,
3309
+ // } satisfies FeatureFlagRule;
3310
+ // }
3311
+ };
3312
+ __name(exports.FeatureFlagRepository, "FeatureFlagRepository");
3313
+ // Constants for time calculations
3314
+ __publicField(exports.FeatureFlagRepository, "ONE_DAY_MS", 864e5);
3315
+ exports.FeatureFlagRepository = __decorateClass([
3316
+ common.Injectable()
3317
+ ], exports.FeatureFlagRepository);
3318
+
3319
+ // src/backend/featureFlags/feature-flag.module.ts
3320
+ exports.FeatureFlagModule = class FeatureFlagModule {
3321
+ /**
3322
+ * Static method for importing the module with configuration.
3323
+ * This allows customization of the feature flag system.
3324
+ *
3325
+ * @param options - Module configuration options
3326
+ * @returns Configured module
3327
+ *
3328
+ * @example
3329
+ * ```typescript
3330
+ * @Module({
3331
+ * imports: [
3332
+ * FeatureFlagModule.forRoot({
3333
+ * provider: 'redis',
3334
+ * cacheEnabled: true,
3335
+ * cacheTtl: 600,
3336
+ * })
3337
+ * ],
3338
+ * })
3339
+ * export class AppModule {}
3340
+ * ```
3341
+ */
3342
+ static forRoot(options) {
3343
+ return {
3344
+ module: exports.FeatureFlagModule,
3345
+ providers: [
3346
+ {
3347
+ provide: "FEATURE_FLAG_CONFIG",
3348
+ useValue: options ?? {}
3349
+ },
3350
+ exports.FeatureFlagService,
3351
+ exports.FeatureFlagRepository
3352
+ ],
3353
+ exports: [exports.FeatureFlagService, exports.FeatureFlagRepository]
3354
+ };
3355
+ }
3356
+ /**
3357
+ * Static method for importing the module asynchronously.
3358
+ * Useful when configuration depends on other services.
3359
+ *
3360
+ * @param options - Async module configuration options
3361
+ * @returns Configured async module
3362
+ *
3363
+ * @example
3364
+ * ```typescript
3365
+ * @Module({
3366
+ * imports: [
3367
+ * FeatureFlagModule.forRootAsync({
3368
+ * imports: [ConfigModule],
3369
+ * inject: [ConfigService],
3370
+ * useFactory: (configService: ConfigService) => ({
3371
+ * provider: configService.get('FEATURE_FLAG_PROVIDER'),
3372
+ * cacheEnabled: configService.get('FEATURE_FLAG_CACHE_ENABLED'),
3373
+ * }),
3374
+ * })
3375
+ * ],
3376
+ * })
3377
+ * export class AppModule {}
3378
+ * ```
3379
+ */
3380
+ static forRootAsync(options) {
3381
+ return {
3382
+ module: exports.FeatureFlagModule,
3383
+ imports: options.imports ?? [],
3384
+ providers: [
3385
+ {
3386
+ provide: "FEATURE_FLAG_CONFIG",
3387
+ inject: options.inject ?? [],
3388
+ useFactory: options.useFactory ?? (() => ({}))
3389
+ },
3390
+ exports.FeatureFlagService,
3391
+ exports.FeatureFlagRepository
3392
+ ],
3393
+ exports: [exports.FeatureFlagService, exports.FeatureFlagRepository]
3394
+ };
3395
+ }
3396
+ };
3397
+ __name(exports.FeatureFlagModule, "FeatureFlagModule");
3398
+ exports.FeatureFlagModule = __decorateClass([
3399
+ common.Global(),
3400
+ common.Module({
3401
+ controllers: [exports.FeatureFlagController],
3402
+ providers: [exports.FeatureFlagService, exports.FeatureFlagRepository],
3403
+ exports: [exports.FeatureFlagService, exports.FeatureFlagRepository]
3404
+ })
3405
+ ], exports.FeatureFlagModule);
3406
+
3407
+ // src/backend/featureFlags/index.ts
3408
+ function FeatureFlagGuard() {
3409
+ return function(_target, _propertyName, descriptor) {
3410
+ return descriptor;
3411
+ };
3412
+ }
3413
+ __name(FeatureFlagGuard, "FeatureFlagGuard");
3414
+ function FeatureFlag() {
3415
+ return function(_target, _propertyName, descriptor) {
3416
+ return descriptor;
3417
+ };
3418
+ }
3419
+ __name(FeatureFlag, "FeatureFlag");
3420
+ var FeatureFlagContext = React.createContext(
3421
+ null
3422
+ );
3423
+ function FeatureFlagAppProvider({
3424
+ config,
3425
+ // defaultContext: _defaultContext,
3426
+ children,
3427
+ features,
3428
+ onReady,
3429
+ onError,
3430
+ isShowLoading = false,
3431
+ loadingComponent,
3432
+ errorComponent
3433
+ }) {
3434
+ const [state, setState] = React.useState({
3435
+ provider: null,
3436
+ isInitialized: false,
3437
+ isLoading: true,
3438
+ error: null,
3439
+ lastUpdated: null
3440
+ });
3441
+ const isMountedRef = React.useRef(true);
3442
+ const initializeProvider = React.useCallback(async () => {
3443
+ try {
3444
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
3445
+ const provider = FeatureFlagProviderFactory.create(config, features);
3446
+ await provider.initialize();
3447
+ if (isMountedRef.current) {
3448
+ setState({
3449
+ provider,
3450
+ isInitialized: true,
3451
+ isLoading: false,
3452
+ error: null,
3453
+ lastUpdated: /* @__PURE__ */ new Date()
3454
+ });
3455
+ onReady?.(provider);
3456
+ }
3457
+ } catch (error) {
3458
+ const errorObj = error instanceof Error ? error : new Error("Failed to initialize feature flags");
3459
+ if (isMountedRef.current) {
3460
+ setState((prev) => ({
3461
+ ...prev,
3462
+ isLoading: false,
3463
+ error: errorObj
3464
+ }));
3465
+ onError?.(errorObj);
3466
+ }
3467
+ }
3468
+ }, [config, onReady, onError]);
3469
+ const refresh = React.useCallback(async () => {
3470
+ if (!state.provider) {
3471
+ await initializeProvider();
3472
+ return;
3473
+ }
3474
+ try {
3475
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
3476
+ await state.provider.refresh();
3477
+ if (isMountedRef.current) {
3478
+ setState((prev) => ({
3479
+ ...prev,
3480
+ isLoading: false,
3481
+ lastUpdated: /* @__PURE__ */ new Date()
3482
+ }));
3483
+ }
3484
+ } catch (error) {
3485
+ const errorObj = error instanceof Error ? error : new Error("Failed to refresh feature flags");
3486
+ if (isMountedRef.current) {
3487
+ setState((prev) => ({
3488
+ ...prev,
3489
+ isLoading: false,
3490
+ error: errorObj
3491
+ }));
3492
+ onError?.(errorObj);
3493
+ }
3494
+ }
3495
+ }, [state.provider, initializeProvider, onError]);
3496
+ React.useEffect(() => {
3497
+ void initializeProvider();
3498
+ }, [initializeProvider]);
3499
+ React.useEffect(() => {
3500
+ if (!state.provider) return;
3501
+ const unsubscribe = state.provider.subscribe(() => {
3502
+ if (isMountedRef.current) {
3503
+ setState((prev) => ({
3504
+ ...prev,
3505
+ lastUpdated: /* @__PURE__ */ new Date()
3506
+ }));
3507
+ }
3508
+ });
3509
+ return unsubscribe;
3510
+ }, [state.provider]);
3511
+ React.useEffect(() => {
3512
+ return () => {
3513
+ isMountedRef.current = false;
3514
+ if (state.provider) {
3515
+ state.provider.dispose();
3516
+ }
3517
+ };
3518
+ }, [state.provider]);
3519
+ if (state.isLoading && isShowLoading) {
3520
+ if (loadingComponent) {
3521
+ const LoadingComponent = loadingComponent;
3522
+ return /* @__PURE__ */ jsxRuntime.jsx(LoadingComponent, {});
3523
+ }
3524
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: "Loading feature flags..." });
3525
+ }
3526
+ if (state.error && errorComponent) {
3527
+ const ErrorComponent = errorComponent;
3528
+ return /* @__PURE__ */ jsxRuntime.jsx(ErrorComponent, { error: state.error, retry: initializeProvider });
3529
+ }
3530
+ if (!state.provider) {
3531
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: "Feature flag provider not available" });
3532
+ }
3533
+ const contextValue = {
3534
+ provider: state.provider,
3535
+ isInitialized: state.isInitialized,
3536
+ isLoading: state.isLoading,
3537
+ error: state.error,
3538
+ lastUpdated: state.lastUpdated,
3539
+ refresh
3540
+ };
3541
+ return /* @__PURE__ */ jsxRuntime.jsx(FeatureFlagContext.Provider, { value: contextValue, children });
3542
+ }
3543
+ __name(FeatureFlagAppProvider, "FeatureFlagAppProvider");
3544
+ FeatureFlagAppProvider.displayName = "FeatureFlagAppProvider";
3545
+ function createFeatureFlagProvider(config, options) {
3546
+ return /* @__PURE__ */ __name(function withFeatureFlags(component) {
3547
+ const WrappedComponent = /* @__PURE__ */ __name((props) => /* @__PURE__ */ jsxRuntime.jsx(
3548
+ FeatureFlagAppProvider,
3549
+ {
3550
+ config,
3551
+ features: options?.features ?? {},
3552
+ ...options,
3553
+ children: React__default.default.createElement(component, props)
3554
+ }
3555
+ ), "WrappedComponent");
3556
+ WrappedComponent.displayName = `withFeatureFlags(${component.displayName ?? component.name})`;
3557
+ return WrappedComponent;
3558
+ }, "withFeatureFlags");
3559
+ }
3560
+ __name(createFeatureFlagProvider, "createFeatureFlagProvider");
3561
+ function useFeatureFlagProvider() {
3562
+ const context = React.useContext(FeatureFlagContext);
3563
+ if (!context) {
3564
+ throw new Error(
3565
+ "useFeatureFlagProvider must be used within a FeatureFlagAppProvider. Make sure to wrap your component tree with <FeatureFlagAppProvider>."
3566
+ );
3567
+ }
3568
+ return context.provider;
3569
+ }
3570
+ __name(useFeatureFlagProvider, "useFeatureFlagProvider");
3571
+ function useFeatureFlagProviderStatus() {
3572
+ const context = React.useContext(FeatureFlagContext);
3573
+ if (!context) {
3574
+ throw new Error(
3575
+ "useFeatureFlagProviderStatus must be used within a FeatureFlagProvider. Make sure to wrap your component tree with <FeatureFlagAppProvider>."
3576
+ );
3577
+ }
3578
+ return {
3579
+ isInitialized: context.isInitialized,
3580
+ isLoading: context.isLoading,
3581
+ error: context.error,
3582
+ lastUpdated: context.lastUpdated
3583
+ };
3584
+ }
3585
+ __name(useFeatureFlagProviderStatus, "useFeatureFlagProviderStatus");
3586
+ function createEvaluationFunction(params, setState) {
3587
+ const { provider, key, context, defaultValue } = params;
3588
+ return async () => {
3589
+ if (!provider) return;
3590
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
3591
+ try {
3592
+ const evaluation = await provider.getFlag(key, context);
3593
+ setState((prev) => ({
3594
+ ...prev,
3595
+ value: evaluation?.value ?? defaultValue ?? false,
3596
+ isLoading: false,
3597
+ error: null,
3598
+ evaluation
3599
+ }));
3600
+ } catch (error) {
3601
+ setState((prev) => ({
3602
+ ...prev,
3603
+ value: defaultValue ?? false,
3604
+ isLoading: false,
3605
+ error: error instanceof Error ? error : new Error("Evaluation failed"),
3606
+ evaluation: null
3607
+ }));
3608
+ }
3609
+ };
3610
+ }
3611
+ __name(createEvaluationFunction, "createEvaluationFunction");
3612
+ function useFeatureFlagEvaluation(key, context, defaultValue, provider) {
3613
+ const [state, setState] = React.useState({
3614
+ value: defaultValue ?? false,
3615
+ isLoading: true,
3616
+ error: null,
3617
+ evaluation: null
3618
+ });
3619
+ const evaluateFlag = React.useCallback(
3620
+ createEvaluationFunction({ provider, key, context, defaultValue }, setState),
3621
+ [provider, key, context, defaultValue]
3622
+ );
3623
+ React.useEffect(() => {
3624
+ const timer = setTimeout(() => {
3625
+ void evaluateFlag();
3626
+ }, 0);
3627
+ return () => clearTimeout(timer);
3628
+ }, [evaluateFlag]);
3629
+ return {
3630
+ value: state.value,
3631
+ isLoading: state.isLoading,
3632
+ error: state.error,
3633
+ refresh: evaluateFlag
3634
+ };
3635
+ }
3636
+ __name(useFeatureFlagEvaluation, "useFeatureFlagEvaluation");
3637
+ function useMultipleFeatureFlagsEvaluation(keys, context, defaultValue, provider) {
3638
+ const [flagStates, setFlagStates] = React.useState(() => {
3639
+ const initialStates = {};
3640
+ keys.forEach((key) => {
3641
+ initialStates[key] = {
3642
+ value: defaultValue,
3643
+ isLoading: true,
3644
+ error: null,
3645
+ evaluation: null
3646
+ };
3647
+ });
3648
+ return initialStates;
3649
+ });
3650
+ const evaluateFlags = React.useCallback(async () => {
3651
+ if (!provider) return;
3652
+ setFlagStates((prevStates) => {
3653
+ const newStates = { ...prevStates };
3654
+ keys.forEach((key) => {
3655
+ newStates[key] = {
3656
+ ...newStates[key],
3657
+ isLoading: true,
3658
+ error: null
3659
+ };
3660
+ });
3661
+ return newStates;
3662
+ });
3663
+ const evaluations = await Promise.allSettled(
3664
+ keys.map(async (key) => {
3665
+ try {
3666
+ const evaluation = await provider.getFlag(key, context);
3667
+ return { key, evaluation, error: null };
3668
+ } catch (error) {
3669
+ return {
3670
+ key,
3671
+ evaluation: null,
3672
+ error: error instanceof Error ? error : new Error("Evaluation failed")
3673
+ };
3674
+ }
3675
+ })
3676
+ );
3677
+ setFlagStates((prevStates) => {
3678
+ const newStates = { ...prevStates };
3679
+ evaluations.forEach((result, index) => {
3680
+ const key = keys[index];
3681
+ if (result.status === "fulfilled") {
3682
+ const { evaluation, error } = result.value;
3683
+ newStates[key] = {
3684
+ value: evaluation?.value ?? defaultValue,
3685
+ isLoading: false,
3686
+ error,
3687
+ evaluation
3688
+ };
3689
+ }
3690
+ });
3691
+ return newStates;
3692
+ });
3693
+ }, [keys, provider, context, defaultValue]);
3694
+ React.useEffect(() => {
3695
+ const timer = setTimeout(() => {
3696
+ void evaluateFlags();
3697
+ }, 0);
3698
+ return () => clearTimeout(timer);
3699
+ }, [evaluateFlags]);
3700
+ const refreshFunctions = React.useMemo(() => {
3701
+ const refreshFns = {};
3702
+ keys.forEach((key) => {
3703
+ refreshFns[key] = async () => {
3704
+ if (!provider) return;
3705
+ setFlagStates((prev) => ({
3706
+ ...prev,
3707
+ [key]: { ...prev[key], isLoading: true, error: null }
3708
+ }));
3709
+ try {
3710
+ const evaluation = await provider.getFlag(key, context);
3711
+ setFlagStates((prev) => ({
3712
+ ...prev,
3713
+ [key]: {
3714
+ value: evaluation?.value ?? defaultValue,
3715
+ isLoading: false,
3716
+ error: null,
3717
+ evaluation
3718
+ }
3719
+ }));
3720
+ } catch (error) {
3721
+ setFlagStates((prev) => ({
3722
+ ...prev,
3723
+ [key]: {
3724
+ value: defaultValue,
3725
+ isLoading: false,
3726
+ error: error instanceof Error ? error : new Error("Evaluation failed"),
3727
+ evaluation: null
3728
+ }
3729
+ }));
3730
+ }
3731
+ };
3732
+ });
3733
+ return refreshFns;
3734
+ }, [keys, provider, context, defaultValue]);
3735
+ return React.useMemo(() => {
3736
+ const result = {};
3737
+ keys.forEach((key) => {
3738
+ const state = flagStates[key] ?? {
3739
+ value: defaultValue,
3740
+ isLoading: true,
3741
+ error: null};
3742
+ result[key] = {
3743
+ value: state.value,
3744
+ isLoading: state.isLoading,
3745
+ error: state.error,
3746
+ refresh: refreshFunctions[key] ?? (async () => {
3747
+ })
3748
+ };
3749
+ });
3750
+ return result;
3751
+ }, [keys, flagStates, refreshFunctions, defaultValue]);
3752
+ }
3753
+ __name(useMultipleFeatureFlagsEvaluation, "useMultipleFeatureFlagsEvaluation");
3754
+
3755
+ // src/frontend/featureFlags/hooks/useFeatureFlag.ts
3756
+ function useFeatureFlag(key, options = {}) {
3757
+ const { context, isAutoRefresh = true, defaultValue, isSuspense = false } = options;
3758
+ const provider = useFeatureFlagProvider();
3759
+ const {
3760
+ value,
3761
+ isLoading,
3762
+ error,
3763
+ refresh: evaluateFlag
3764
+ } = useFeatureFlagEvaluation(
3765
+ key,
3766
+ context,
3767
+ defaultValue,
3768
+ provider
3769
+ );
3770
+ const refresh = React.useCallback(async () => {
3771
+ await evaluateFlag();
3772
+ }, [evaluateFlag]);
3773
+ React.useEffect(() => {
3774
+ if (!provider || !isAutoRefresh) return;
3775
+ const unsubscribe = provider.subscribe(() => {
3776
+ void evaluateFlag();
3777
+ });
3778
+ return unsubscribe;
3779
+ }, [provider, isAutoRefresh, evaluateFlag]);
3780
+ if (isSuspense && isLoading) {
3781
+ throw evaluateFlag();
3782
+ }
3783
+ return {
3784
+ value,
3785
+ isLoading,
3786
+ error,
3787
+ refresh
3788
+ };
3789
+ }
3790
+ __name(useFeatureFlag, "useFeatureFlag");
3791
+ function useFeatureFlagEnabled(key, options = {}) {
3792
+ const { value } = useFeatureFlag(key, {
3793
+ ...options,
3794
+ defaultValue: options.defaultValue ?? false
3795
+ });
3796
+ return value;
3797
+ }
3798
+ __name(useFeatureFlagEnabled, "useFeatureFlagEnabled");
3799
+ function useFeatureFlagValue(key, options = {}) {
3800
+ const { value } = useFeatureFlag(key, options);
3801
+ return value;
3802
+ }
3803
+ __name(useFeatureFlagValue, "useFeatureFlagValue");
3804
+ function useMultipleFeatureFlags(keys, options = {}) {
3805
+ const provider = useFeatureFlagProvider();
3806
+ const { context, isAutoRefresh = true, defaultValue = false } = options;
3807
+ const flagStates = useMultipleFeatureFlagsEvaluation(keys, context, defaultValue, provider);
3808
+ React.useEffect(() => {
3809
+ if (!provider || !isAutoRefresh) return;
3810
+ const unsubscribe = provider.subscribe(() => {
3811
+ keys.forEach((key) => {
3812
+ if (typeof flagStates[key]?.refresh === "function") {
3813
+ void flagStates[key].refresh();
3814
+ }
3815
+ });
3816
+ });
3817
+ return unsubscribe;
3818
+ }, [provider, isAutoRefresh, flagStates, keys]);
3819
+ return flagStates;
3820
+ }
3821
+ __name(useMultipleFeatureFlags, "useMultipleFeatureFlags");
3822
+ function useFeatureFlagHelpers() {
3823
+ const provider = useFeatureFlagProvider();
3824
+ const setOverride = React.useCallback(
3825
+ (key, value) => {
3826
+ provider.setOverride(key, value);
3827
+ },
3828
+ [provider]
3829
+ );
3830
+ const removeOverride = React.useCallback(
3831
+ (key) => {
3832
+ provider.removeOverride(key);
3833
+ },
3834
+ [provider]
3835
+ );
3836
+ const clearOverrides = React.useCallback(() => {
3837
+ provider.clearOverrides();
3838
+ }, [provider]);
3839
+ const refresh = React.useCallback(async () => {
3840
+ await provider.refresh();
3841
+ }, [provider]);
3842
+ const getMultipleFlags = React.useCallback(
3843
+ async (keys, context) => {
3844
+ const results = {};
3845
+ await Promise.all(
3846
+ keys.map(async (key) => {
3847
+ const evaluation = await provider.getFlag(key, context);
3848
+ results[key] = evaluation.value;
3849
+ })
3850
+ );
3851
+ return results;
3852
+ },
3853
+ [provider]
3854
+ );
3855
+ const isAnyEnabled = React.useCallback(
3856
+ async (keys, context) => {
3857
+ const evaluations = await Promise.all(keys.map((key) => provider.isEnabled(key, context)));
3858
+ return evaluations.some((enabled) => enabled);
3859
+ },
3860
+ [provider]
3861
+ );
3862
+ const isAllEnabled = React.useCallback(
3863
+ async (keys, context) => {
3864
+ const evaluations = await Promise.all(keys.map((key) => provider.isEnabled(key, context)));
3865
+ return evaluations.every((enabled) => enabled);
3866
+ },
3867
+ [provider]
3868
+ );
3869
+ const whenEnabled = React.useCallback(
3870
+ async (key, callback, fallback, context) => {
3871
+ const isEnabled = await provider.isEnabled(key, context);
3872
+ if (isEnabled) {
3873
+ return callback();
3874
+ } else if (fallback) {
3875
+ return fallback();
3876
+ }
3877
+ return void 0;
3878
+ },
3879
+ [provider]
3880
+ );
3881
+ return React.useMemo(
3882
+ () => ({
3883
+ setOverride,
3884
+ removeOverride,
3885
+ clearOverrides,
3886
+ refresh,
3887
+ getMultipleFlags,
3888
+ isAnyEnabled,
3889
+ isAllEnabled,
3890
+ whenEnabled
3891
+ }),
3892
+ [
3893
+ setOverride,
3894
+ removeOverride,
3895
+ clearOverrides,
3896
+ refresh,
3897
+ getMultipleFlags,
3898
+ isAnyEnabled,
3899
+ isAllEnabled,
3900
+ whenEnabled
3901
+ ]
3902
+ );
3903
+ }
3904
+ __name(useFeatureFlagHelpers, "useFeatureFlagHelpers");
3905
+
3906
+ exports.ApiFeatureFlagProvider = ApiFeatureFlagProvider;
3907
+ exports.CacheManager = CacheManager;
3908
+ exports.ConditionUtils = ConditionUtils;
3909
+ exports.ContextUtils = ContextUtils;
3910
+ exports.DEFAULT_FEATURE_FLAG_CONFIG = DEFAULT_FEATURE_FLAG_CONFIG;
3911
+ exports.DatabaseFeatureFlagProvider = DatabaseFeatureFlagProvider;
3912
+ exports.FeatureFlag = FeatureFlag;
3913
+ exports.FeatureFlagAppProvider = FeatureFlagAppProvider;
3914
+ exports.FeatureFlagContext = FeatureFlagContext;
3915
+ exports.FeatureFlagContextBuilder = FeatureFlagContextBuilder;
3916
+ exports.FeatureFlagEngine = FeatureFlagEngine;
3917
+ exports.FeatureFlagGuard = FeatureFlagGuard;
3918
+ exports.FeatureFlagProvider = FeatureFlagProvider;
3919
+ exports.FeatureFlagProviderFactory = FeatureFlagProviderFactory;
3920
+ exports.FeatureFlagSystem = FeatureFlagSystem;
3921
+ exports.FileFeatureFlagProvider = FileFeatureFlagProvider;
3922
+ exports.HashUtils = HashUtils;
3923
+ exports.MemoryFeatureFlagProvider = MemoryFeatureFlagProvider;
3924
+ exports.RedisFeatureFlagProvider = RedisFeatureFlagProvider;
3925
+ exports.ValueUtils = ValueUtils;
3926
+ exports.createBackendContext = createBackendContext;
3927
+ exports.createFeatureFlagProvider = createFeatureFlagProvider;
3928
+ exports.createFrontendContext = createFrontendContext;
3929
+ exports.createRolloutIdentifier = createRolloutIdentifier;
3930
+ exports.evaluateArrayOperator = evaluateArrayOperator;
3931
+ exports.evaluateConditionOperator = evaluateConditionOperator;
3932
+ exports.evaluateEqualityOperator = evaluateEqualityOperator;
3933
+ exports.evaluateNumericOperator = evaluateNumericOperator;
3934
+ exports.evaluateStringOperator = evaluateStringOperator;
3935
+ exports.hashString = hashString;
3936
+ exports.isArrayOperator = isArrayOperator;
3937
+ exports.isEqualityOperator = isEqualityOperator;
3938
+ exports.isInRollout = isInRollout;
3939
+ exports.isNumericOperator = isNumericOperator;
3940
+ exports.isStringOperator = isStringOperator;
3941
+ exports.isTruthy = isTruthy;
3942
+ exports.toBoolean = toBoolean;
3943
+ exports.useFeatureFlag = useFeatureFlag;
3944
+ exports.useFeatureFlagEnabled = useFeatureFlagEnabled;
3945
+ exports.useFeatureFlagHelpers = useFeatureFlagHelpers;
3946
+ exports.useFeatureFlagProvider = useFeatureFlagProvider;
3947
+ exports.useFeatureFlagProviderStatus = useFeatureFlagProviderStatus;
3948
+ exports.useFeatureFlagValue = useFeatureFlagValue;
3949
+ exports.useMultipleFeatureFlags = useMultipleFeatureFlags;
3950
+ //# sourceMappingURL=index.cjs.map
3951
+ //# sourceMappingURL=index.cjs.map