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