@plyaz/core 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/backend/featureFlags/config/feature-flag.config.d.ts +111 -0
  2. package/dist/backend/featureFlags/config/feature-flag.config.d.ts.map +1 -0
  3. package/dist/backend/featureFlags/config/validation.d.ts +181 -0
  4. package/dist/backend/featureFlags/config/validation.d.ts.map +1 -0
  5. package/dist/backend/featureFlags/database/connection.d.ts +321 -0
  6. package/dist/backend/featureFlags/database/connection.d.ts.map +1 -0
  7. package/dist/backend/featureFlags/database/repository.d.ts +518 -0
  8. package/dist/backend/featureFlags/database/repository.d.ts.map +1 -0
  9. package/dist/backend/featureFlags/decorators/feature-disabled.decorator.d.ts +6 -0
  10. package/dist/backend/featureFlags/decorators/feature-disabled.decorator.d.ts.map +1 -0
  11. package/dist/backend/featureFlags/decorators/feature-enabled.decorator.d.ts +8 -0
  12. package/dist/backend/featureFlags/decorators/feature-enabled.decorator.d.ts.map +1 -0
  13. package/dist/backend/featureFlags/decorators/feature-flag.decorator.d.ts +11 -0
  14. package/dist/backend/featureFlags/decorators/feature-flag.decorator.d.ts.map +1 -0
  15. package/dist/backend/featureFlags/feature-flag.controller.d.ts +1 -2
  16. package/dist/backend/featureFlags/feature-flag.controller.d.ts.map +1 -1
  17. package/dist/backend/featureFlags/feature-flag.module.d.ts +2 -3
  18. package/dist/backend/featureFlags/feature-flag.module.d.ts.map +1 -1
  19. package/dist/backend/featureFlags/feature-flag.service.d.ts +149 -8
  20. package/dist/backend/featureFlags/feature-flag.service.d.ts.map +1 -1
  21. package/dist/backend/featureFlags/guards/feature-flag.guard.d.ts +19 -0
  22. package/dist/backend/featureFlags/guards/feature-flag.guard.d.ts.map +1 -0
  23. package/dist/backend/featureFlags/index.d.ts +10 -36
  24. package/dist/backend/featureFlags/index.d.ts.map +1 -1
  25. package/dist/backend/featureFlags/interceptors/error-handling-interceptor.d.ts +16 -0
  26. package/dist/backend/featureFlags/interceptors/error-handling-interceptor.d.ts.map +1 -0
  27. package/dist/backend/featureFlags/interceptors/feature-flag-logging-interceptor.d.ts +18 -0
  28. package/dist/backend/featureFlags/interceptors/feature-flag-logging-interceptor.d.ts.map +1 -0
  29. package/dist/backend/featureFlags/middleware/feature-flag-middleware.d.ts +167 -0
  30. package/dist/backend/featureFlags/middleware/feature-flag-middleware.d.ts.map +1 -0
  31. package/dist/base/cache/feature/caching.d.ts +16 -0
  32. package/dist/base/cache/feature/caching.d.ts.map +1 -0
  33. package/dist/base/cache/index.d.ts +1 -0
  34. package/dist/base/cache/index.d.ts.map +1 -1
  35. package/dist/domain/featureFlags/providers/database.d.ts +17 -12
  36. package/dist/domain/featureFlags/providers/database.d.ts.map +1 -1
  37. package/dist/frontend/index.d.ts +1 -0
  38. package/dist/frontend/index.d.ts.map +1 -1
  39. package/dist/frontend/providers/ApiProvider.d.ts +41 -0
  40. package/dist/frontend/providers/ApiProvider.d.ts.map +1 -0
  41. package/dist/frontend/providers/index.d.ts +7 -0
  42. package/dist/frontend/providers/index.d.ts.map +1 -0
  43. package/dist/index.cjs +2325 -273
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.mjs +2315 -275
  48. package/dist/index.mjs.map +1 -1
  49. package/dist/services/ApiClientService.d.ts +90 -0
  50. package/dist/services/ApiClientService.d.ts.map +1 -0
  51. package/dist/services/index.d.ts +8 -0
  52. package/dist/services/index.d.ts.map +1 -0
  53. package/dist/utils/common/index.d.ts +1 -1
  54. package/dist/utils/common/index.d.ts.map +1 -1
  55. package/dist/utils/common/validation.d.ts +20 -0
  56. package/dist/utils/common/validation.d.ts.map +1 -0
  57. package/dist/utils/db/databaseService.d.ts +6 -0
  58. package/dist/utils/db/databaseService.d.ts.map +1 -0
  59. package/dist/utils/db/index.d.ts +2 -0
  60. package/dist/utils/db/index.d.ts.map +1 -0
  61. package/dist/web_app/auth/add_user.d.ts +3 -0
  62. package/dist/web_app/auth/add_user.d.ts.map +1 -0
  63. package/dist/web_app/auth/update_user.d.ts +2 -0
  64. package/dist/web_app/auth/update_user.d.ts.map +1 -0
  65. package/package.json +20 -7
package/dist/index.cjs CHANGED
@@ -1,14 +1,22 @@
1
1
  'use strict';
2
2
 
3
3
  var config = require('@plyaz/config');
4
+ var types = require('@plyaz/types');
5
+ var common = require('@nestjs/common');
6
+ var rxjs = require('rxjs');
4
7
  var fs = require('fs');
5
8
  var path = require('path');
6
9
  var util = require('util');
7
10
  var url = require('url');
8
11
  var yaml = require('yaml');
9
- var common = require('@nestjs/common');
12
+ var db = require('@plyaz/db');
13
+ var errors = require('@plyaz/errors');
14
+ var db$1 = require('@plyaz/types/db');
15
+ var crypto = require('crypto');
16
+ var operators = require('rxjs/operators');
10
17
  var React = require('react');
11
18
  var jsxRuntime = require('react/jsx-runtime');
19
+ var api = require('@plyaz/api');
12
20
 
13
21
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
22
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -221,6 +229,10 @@ var ValueUtils = {
221
229
  return current;
222
230
  }, "getNestedProperty")
223
231
  };
232
+ var isString = /* @__PURE__ */ __name((value) => typeof value === "string", "isString");
233
+ var isDefined = /* @__PURE__ */ __name((value) => value !== void 0, "isDefined");
234
+ var isNumber = /* @__PURE__ */ __name((value) => typeof value === "number", "isNumber");
235
+ var getValidProviders = /* @__PURE__ */ __name(() => Object.values(types.FEATURE_FLAG_PROVIDERS), "getValidProviders");
224
236
  var FeatureFlagContextBuilder = class _FeatureFlagContextBuilder {
225
237
  static {
226
238
  __name(this, "FeatureFlagContextBuilder");
@@ -1364,6 +1376,27 @@ var RedisCacheStrategy = class {
1364
1376
  return `${this.keyPrefix}${key}`;
1365
1377
  }
1366
1378
  };
1379
+ exports.Caching = class Caching {
1380
+ // Map to store cached responses keyed by URL + active feature flags
1381
+ cache = /* @__PURE__ */ new Map();
1382
+ intercept(context, next) {
1383
+ const request = context.switchToHttp().getRequest();
1384
+ const flagsKey = JSON.stringify(request.featureFlags ?? {});
1385
+ const cacheKey = `${request.url}|${flagsKey}`;
1386
+ if (this.cache.has(cacheKey)) {
1387
+ return rxjs.of(this.cache.get(cacheKey));
1388
+ }
1389
+ return next.handle().pipe(
1390
+ rxjs.tap((response) => {
1391
+ this.cache.set(cacheKey, response);
1392
+ })
1393
+ );
1394
+ }
1395
+ };
1396
+ __name(exports.Caching, "Caching");
1397
+ exports.Caching = __decorateClass([
1398
+ common.Injectable()
1399
+ ], exports.Caching);
1367
1400
 
1368
1401
  // src/base/cache/index.ts
1369
1402
  var CacheManager = class {
@@ -2692,210 +2725,1243 @@ Example: "https://api.plyaz.co.uk"`
2692
2725
  };
2693
2726
  }
2694
2727
  };
2695
-
2696
- // src/domain/featureFlags/providers/database.ts
2697
- var DatabaseFeatureFlagProvider = class extends FeatureFlagProvider {
2728
+ var DatabaseConnectionManager = class _DatabaseConnectionManager {
2698
2729
  static {
2699
- __name(this, "DatabaseFeatureFlagProvider");
2730
+ __name(this, "DatabaseConnectionManager");
2700
2731
  }
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");
2732
+ static instance;
2733
+ databaseService = null;
2734
+ constructor() {
2711
2735
  }
2712
2736
  /**
2713
- * Fetches flags and rules from the database.
2714
- * Currently throws an error as the database implementation is not ready.
2737
+ * Gets the singleton instance of DatabaseConnectionManager
2715
2738
  *
2716
- * @protected
2717
- * @returns Promise that rejects with implementation error
2718
- * @throws Error indicating missing database implementation
2739
+ * @description Returns the single instance of the connection manager, creating it if it doesn't exist.
2740
+ * This ensures only one database connection pool is maintained across the application.
2741
+ *
2742
+ * @returns {DatabaseConnectionManager} The singleton instance
2743
+ *
2744
+ * @example Getting the Instance
2745
+ * ```typescript
2746
+ * // Always returns the same instance
2747
+ * const manager1 = DatabaseConnectionManager.getInstance();
2748
+ * const manager2 = DatabaseConnectionManager.getInstance();
2749
+ * console.log(manager1 === manager2); // true
2750
+ *
2751
+ * // Use in services
2752
+ * class FeatureFlagRepository {
2753
+ * private connectionManager = DatabaseConnectionManager.getInstance();
2754
+ *
2755
+ * async getFlags() {
2756
+ * const db = this.connectionManager.getDatabase();
2757
+ * return db.query('feature_flags');
2758
+ * }
2759
+ * }
2760
+ * ```
2719
2761
  */
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
- );
2762
+ static getInstance() {
2763
+ if (!_DatabaseConnectionManager.instance) {
2764
+ _DatabaseConnectionManager.instance = new _DatabaseConnectionManager();
2765
+ }
2766
+ return _DatabaseConnectionManager.instance;
2724
2767
  }
2725
2768
  /**
2726
- * Validates the database provider configuration.
2769
+ * Initializes the database connection and registers feature flag tables
2727
2770
  *
2728
- * @private
2729
- * @throws Error if configuration is invalid or incomplete
2771
+ * @description Sets up the Supabase connection using environment variables and registers
2772
+ * all feature flag related tables. This method is idempotent - calling it multiple
2773
+ * times won't create additional connections.
2774
+ *
2775
+ * **What it does:**
2776
+ * 1. Validates required environment variables
2777
+ * 2. Creates Supabase database service
2778
+ * 3. Configures caching (5-minute TTL)
2779
+ * 4. Registers feature flag tables with proper ID columns
2780
+ * 5. Disables audit logging (until tables are created)
2781
+ *
2782
+ * @throws {DatabaseError} When environment variables are missing or connection fails
2783
+ *
2784
+ * @example Initialization Process
2785
+ * ```typescript
2786
+ * // Called automatically by FeatureFlagService.onModuleInit()
2787
+ * const manager = DatabaseConnectionManager.getInstance();
2788
+ *
2789
+ * try {
2790
+ * await manager.initialize();
2791
+ * console.log('Database connection established');
2792
+ * } catch (error) {
2793
+ * console.error('Failed to connect to database:', error.message);
2794
+ * // Error: Missing environment variables for Supabase connection
2795
+ * }
2796
+ * ```
2797
+ *
2798
+ * @example Environment Variable Validation
2799
+ * ```typescript
2800
+ * // If any of these are missing, initialization fails:
2801
+ * // SUPABASE_URL=undefined
2802
+ * // SUPABASE_SERVICE_ROLE_KEY=undefined
2803
+ * // SUPABASE_ANON_PUBLIC_KEY=undefined
2804
+ *
2805
+ * // Throws: DatabaseError with code CONFIG_REQUIRED
2806
+ * ```
2807
+ *
2808
+ * @example Registered Tables
2809
+ * ```typescript
2810
+ * // These tables are automatically registered:
2811
+ * // - feature_flags (ID: key)
2812
+ * // - feature_flag_rules (ID: id)
2813
+ * // - feature_flag_overrides (ID: id)
2814
+ * // - feature_flag_evaluations (ID: id)
2815
+ * ```
2730
2816
  */
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"');
2817
+ async initialize() {
2818
+ if (this.databaseService) {
2819
+ return;
2740
2820
  }
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" }'
2821
+ const supabaseUrl = process.env.SUPABASE_URL;
2822
+ const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
2823
+ const supabaseAnonKey = process.env.SUPABASE_ANON_PUBLIC_KEY;
2824
+ if (!supabaseUrl || !supabaseServiceKey || !supabaseAnonKey) {
2825
+ throw new errors.DatabaseError(
2826
+ "Missing environment variables for Supabase connection. Please check your .env file.",
2827
+ types.DATABASE_ERROR_CODES.CONFIG_REQUIRED
2746
2828
  );
2747
2829
  }
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"'
2830
+ this.databaseService = await db.createDatabaseService({
2831
+ adapter: db$1.ADAPTER_TYPES.SUPABASE,
2832
+ config: {
2833
+ supabaseUrl,
2834
+ supabaseServiceKey,
2835
+ supabaseAnonKey
2836
+ },
2837
+ // Enable working extensions only
2838
+ audit: {
2839
+ enabled: false,
2840
+ retentionDays: 90
2841
+ },
2842
+ cache: {
2843
+ enabled: true,
2844
+ provider: "memory",
2845
+ ttl: 300
2846
+ },
2847
+ softDelete: {
2848
+ enabled: false,
2849
+ field: "deleted_at"
2850
+ }
2851
+ });
2852
+ const serviceWithAdapter = this.databaseService;
2853
+ if (serviceWithAdapter.adapter?.registerTable) {
2854
+ serviceWithAdapter.adapter.registerTable("feature_flags", "feature_flags", "key");
2855
+ serviceWithAdapter.adapter.registerTable("feature_flag_rules", "feature_flag_rules", "id");
2856
+ serviceWithAdapter.adapter.registerTable(
2857
+ "feature_flag_overrides",
2858
+ "feature_flag_overrides",
2859
+ "id"
2754
2860
  );
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"`
2861
+ serviceWithAdapter.adapter.registerTable(
2862
+ "feature_flag_evaluations",
2863
+ "feature_flag_evaluations",
2864
+ "id"
2762
2865
  );
2763
2866
  }
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
2867
  }
2774
2868
  /**
2775
- * Validates PostgreSQL URL format.
2869
+ * Gets the initialized database service instance
2776
2870
  *
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.
2871
+ * @description Returns the database service for performing queries. The database must be
2872
+ * initialized first using initialize() method, otherwise this will throw an error.
2786
2873
  *
2787
- * @private
2788
- * @param url - URL to validate
2789
- * @returns True if valid MySQL URL
2874
+ * @returns {DatabaseServiceInterface} The database service instance
2875
+ * @throws {DatabaseError} When database is not initialized
2876
+ *
2877
+ * @example Basic Usage
2878
+ * ```typescript
2879
+ * const manager = DatabaseConnectionManager.getInstance();
2880
+ * await manager.initialize(); // Must initialize first
2881
+ *
2882
+ * const db = manager.getDatabase();
2883
+ *
2884
+ * // Query feature flags
2885
+ * const result = await db.query('feature_flags', {
2886
+ * filters: [{ field: 'is_enabled', operator: 'eq', value: true }],
2887
+ * sort: [{ field: 'key', direction: 'asc' }]
2888
+ * });
2889
+ *
2890
+ * if (result.success) {
2891
+ * console.log('Found flags:', result.value.data);
2892
+ * }
2893
+ * ```
2894
+ *
2895
+ * @example Error Handling
2896
+ * ```typescript
2897
+ * const manager = DatabaseConnectionManager.getInstance();
2898
+ * // Don't call initialize()
2899
+ *
2900
+ * try {
2901
+ * const db = manager.getDatabase();
2902
+ * } catch (error) {
2903
+ * console.error(error.message);
2904
+ * // "Database not initialized. Call initialize() first."
2905
+ * }
2906
+ * ```
2907
+ *
2908
+ * @example CRUD Operations
2909
+ * ```typescript
2910
+ * const db = manager.getDatabase();
2911
+ *
2912
+ * // Create a flag
2913
+ * const createResult = await db.create('feature_flags', {
2914
+ * key: 'NEW_FEATURE',
2915
+ * value: true,
2916
+ * is_enabled: true
2917
+ * });
2918
+ *
2919
+ * // Update a flag
2920
+ * const updateResult = await db.update('feature_flags', 'NEW_FEATURE', {
2921
+ * value: false
2922
+ * });
2923
+ *
2924
+ * // Delete a flag
2925
+ * const deleteResult = await db.delete('feature_flags', 'NEW_FEATURE');
2926
+ * ```
2790
2927
  */
2791
- isValidMysqlUrl(url) {
2792
- return url.startsWith("mysql://");
2928
+ getDatabase() {
2929
+ if (!this.databaseService) {
2930
+ throw new errors.DatabaseError(
2931
+ "Database not initialized. Call initialize() first.",
2932
+ types.DATABASE_ERROR_CODES.INIT_FAILED
2933
+ );
2934
+ }
2935
+ return this.databaseService;
2793
2936
  }
2794
2937
  /**
2795
- * Masks sensitive parts of connection string for logging.
2938
+ * Executes a database transaction with automatic rollback on failure
2796
2939
  *
2797
- * @private
2798
- * @param connectionString - Original connection string
2799
- * @returns Masked connection string
2940
+ * @description Provides atomic database operations by wrapping multiple queries in a transaction.
2941
+ * If any operation fails, all changes are rolled back automatically. This is essential for
2942
+ * maintaining data consistency when creating flags with rules or performing bulk operations.
2943
+ *
2944
+ * @template T The return type of the transaction callback
2945
+ * @param {Function} callback - Function that receives transaction object and performs operations
2946
+ * @returns {Promise<T>} The result of the transaction callback
2947
+ * @throws {DatabaseError} When transaction fails or returns no value
2948
+ *
2949
+ * @example Atomic Flag Creation with Rules
2950
+ * ```typescript
2951
+ * const manager = DatabaseConnectionManager.getInstance();
2952
+ *
2953
+ * const result = await manager.transaction(async (tx) => {
2954
+ * // Create the flag
2955
+ * const flag = await tx.create('feature_flags', {
2956
+ * key: 'PREMIUM_CHECKOUT',
2957
+ * value: true,
2958
+ * is_enabled: true,
2959
+ * description: 'Premium checkout flow'
2960
+ * });
2961
+ *
2962
+ * // Create targeting rule
2963
+ * const rule = await tx.create('feature_flag_rules', {
2964
+ * flag_key: 'PREMIUM_CHECKOUT',
2965
+ * name: 'Premium Users Only',
2966
+ * conditions: [
2967
+ * { field: 'userRole', operator: 'equals', value: 'premium' },
2968
+ * { field: 'subscriptionActive', operator: 'equals', value: true }
2969
+ * ],
2970
+ * value: true,
2971
+ * priority: 100
2972
+ * });
2973
+ *
2974
+ * return { flag, rule };
2975
+ * });
2976
+ *
2977
+ * console.log('Created flag and rule:', result);
2978
+ * ```
2979
+ *
2980
+ * @example Bulk Flag Updates
2981
+ * ```typescript
2982
+ * const flagsToUpdate = ['FEATURE_A', 'FEATURE_B', 'FEATURE_C'];
2983
+ *
2984
+ * await manager.transaction(async (tx) => {
2985
+ * for (const flagKey of flagsToUpdate) {
2986
+ * await tx.update('feature_flags', flagKey, {
2987
+ * is_enabled: false,
2988
+ * updated_at: new Date().toISOString()
2989
+ * });
2990
+ * }
2991
+ *
2992
+ * // Log the bulk update
2993
+ * await tx.create('feature_flag_evaluations', {
2994
+ * flag_key: 'BULK_UPDATE',
2995
+ * result: { updatedFlags: flagsToUpdate },
2996
+ * reason: 'bulk_disable',
2997
+ * evaluated_at: new Date().toISOString()
2998
+ * });
2999
+ * });
3000
+ * ```
3001
+ *
3002
+ * @example Error Handling and Rollback
3003
+ * ```typescript
3004
+ * try {
3005
+ * await manager.transaction(async (tx) => {
3006
+ * await tx.create('feature_flags', { key: 'TEST_FLAG', value: true });
3007
+ *
3008
+ * // This will cause the entire transaction to rollback
3009
+ * throw new Error('Something went wrong');
3010
+ *
3011
+ * // This won't execute, and the flag creation above will be rolled back
3012
+ * await tx.create('feature_flag_rules', { flag_key: 'TEST_FLAG' });
3013
+ * });
3014
+ * } catch (error) {
3015
+ * console.log('Transaction failed, all changes rolled back');
3016
+ * }
3017
+ * ```
2800
3018
  */
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]";
3019
+ async transaction(callback) {
3020
+ const db = this.getDatabase();
3021
+ const result = await db.transaction(callback);
3022
+ if (!result.success) {
3023
+ const errorMessage = result.error?.message ?? "Transaction failed";
3024
+ throw new errors.DatabaseError(errorMessage, types.DATABASE_ERROR_CODES.TRANSACTION_FAILED);
2808
3025
  }
3026
+ if (result.value === void 0 || result.value === null) {
3027
+ throw new errors.DatabaseError("Transaction returned no value", types.DATABASE_ERROR_CODES.INVALID_RESULT);
3028
+ }
3029
+ return result.value;
2809
3030
  }
2810
3031
  /**
2811
- * Gets database provider status and configuration info.
3032
+ * Closes the database connection and cleans up resources
2812
3033
  *
2813
- * @returns Database provider status information
3034
+ * @description Properly closes the database connection and resets the singleton instance.
3035
+ * This is typically called during application shutdown or in tests that need to reset
3036
+ * the connection state.
3037
+ *
3038
+ * @example Application Shutdown
3039
+ * ```typescript
3040
+ * // In your NestJS module's onModuleDestroy
3041
+ * export class FeatureFlagModule implements OnModuleDestroy {
3042
+ * async onModuleDestroy() {
3043
+ * const manager = DatabaseConnectionManager.getInstance();
3044
+ * await manager.close();
3045
+ * console.log('Database connection closed');
3046
+ * }
3047
+ * }
3048
+ * ```
3049
+ *
3050
+ * @example Test Cleanup
3051
+ * ```typescript
3052
+ * describe('Feature Flag Tests', () => {
3053
+ * afterEach(async () => {
3054
+ * // Clean up connection between tests
3055
+ * const manager = DatabaseConnectionManager.getInstance();
3056
+ * await manager.close();
3057
+ * });
3058
+ * });
3059
+ * ```
2814
3060
  */
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
- };
3061
+ async close() {
3062
+ this.databaseService = null;
2825
3063
  }
2826
3064
  };
2827
- var PROVIDER_REGISTRY = {
2828
- memory: MemoryFeatureFlagProvider,
2829
- file: FileFeatureFlagProvider,
2830
- redis: RedisFeatureFlagProvider,
2831
- api: ApiFeatureFlagProvider,
2832
- database: DatabaseFeatureFlagProvider
2833
- };
2834
- var FeatureFlagProviderFactory = class {
3065
+ var FeatureFlagDatabaseRepository = class {
2835
3066
  static {
2836
- __name(this, "FeatureFlagProviderFactory");
3067
+ __name(this, "FeatureFlagDatabaseRepository");
2837
3068
  }
3069
+ connectionManager = DatabaseConnectionManager.getInstance();
2838
3070
  /**
2839
- * Creates a new feature flag provider instance based on configuration.
3071
+ * Retrieves all feature flags from the database with optional environment filtering
3072
+ *
3073
+ * @description Fetches all feature flags from the database. This method is primarily called
3074
+ * during system initialization to load flags into cache, but can also be used for
3075
+ * administrative purposes or cache refresh operations.
3076
+ *
3077
+ * @param {string} [environment] - Optional environment filter (e.g., 'production', 'staging')
3078
+ * @returns {Promise<FeatureFlag<TKey>[]>} Array of feature flags matching the criteria
3079
+ *
3080
+ * @throws {Error} When database query fails critically
3081
+ *
3082
+ * @example
3083
+ * ```typescript
3084
+ * // Get all flags for system initialization
3085
+ * const repository = new FeatureFlagDatabaseRepository();
3086
+ * const allFlags = await repository.getAllFlags();
3087
+ * console.log(`Loaded ${allFlags.length} feature flags`);
3088
+ *
3089
+ * // Get only production flags
3090
+ * const prodFlags = await repository.getAllFlags('production');
3091
+ * console.log(`Production flags: ${prodFlags.length}`);
3092
+ *
3093
+ * // Handle empty results
3094
+ * const flags = await repository.getAllFlags('nonexistent');
3095
+ * if (flags.length === 0) {
3096
+ * console.log('No flags found for environment');
3097
+ * }
3098
+ * ```
2840
3099
  *
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
3100
  */
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
- );
3101
+ async getAllFlags(environment) {
3102
+ const db = this.connectionManager.getDatabase();
3103
+ const options = {
3104
+ sort: [
3105
+ {
3106
+ field: types.FEATURE_FLAG_FIELD.Key,
3107
+ direction: types.SORT_DIRECTION.Asc
3108
+ }
3109
+ ],
3110
+ pagination: { page: 1, limit: 1e3 }
3111
+ };
3112
+ const result = await db.query(types.DATABASE_TABLE.FeatureFlags, options);
3113
+ if (!result.success) {
3114
+ console.warn("Query failed, returning empty array:", result.error?.message);
3115
+ return [];
3116
+ }
3117
+ let flags = result.value?.data ?? [];
3118
+ if (environment) {
3119
+ flags = flags.filter((flag) => {
3120
+ return Boolean(flag.environments?.includes(environment));
3121
+ });
2860
3122
  }
3123
+ return flags.map((flag) => this.mapToFeatureFlag(flag));
2861
3124
  }
2862
3125
  /**
2863
- * Creates a provider with automatic initialization.
3126
+ * Retrieves a specific feature flag by its key
3127
+ *
3128
+ * @description Fetches a single feature flag from the database using its unique key.
3129
+ * This method is optimized for runtime flag evaluation and uses the primary key index
3130
+ * for O(1) lookup performance.
3131
+ *
3132
+ * @param {TKey} key - The unique identifier of the feature flag to retrieve
3133
+ * @returns {Promise<FeatureFlag<TKey> | null>} The feature flag object or null if not found
3134
+ *
3135
+ * @throws {Error} When database operation fails
3136
+ *
3137
+ * @example
3138
+ * ```typescript
3139
+ * const repository = new FeatureFlagDatabaseRepository();
3140
+ *
3141
+ * // Get a specific flag for evaluation
3142
+ * const premiumFlag = await repository.getFlag('PREMIUM_FEATURE');
3143
+ * if (premiumFlag) {
3144
+ * console.log(`Flag enabled: ${premiumFlag.isEnabled}`);
3145
+ * console.log(`Flag value:`, premiumFlag.value);
3146
+ * } else {
3147
+ * console.log('Flag not found');
3148
+ * }
3149
+ *
3150
+ * // Handle flag evaluation
3151
+ * const checkoutFlag = await repository.getFlag('NEW_CHECKOUT');
3152
+ * const isNewCheckoutEnabled = checkoutFlag?.isEnabled ?? false;
3153
+ * ```
2864
3154
  *
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
3155
  */
2869
- static async createAndInitialize(config, features) {
2870
- const provider = this.create(config, features);
2871
- await provider.initialize();
2872
- return provider;
3156
+ async getFlag(key) {
3157
+ const db = this.connectionManager.getDatabase();
3158
+ const result = await db.get(types.DATABASE_TABLE.FeatureFlags, key);
3159
+ if (!result.success) {
3160
+ throw new errors.DatabaseError(
3161
+ `Failed to get flag: ${result.error?.message}`,
3162
+ types.DATABASE_ERROR_CODES.NO_DATA
3163
+ );
3164
+ }
3165
+ return result.value ? this.mapToFeatureFlag(result.value) : null;
2873
3166
  }
2874
3167
  /**
2875
- * Gets a list of all supported provider types.
3168
+ * Creates a new feature flag in the database
3169
+ *
3170
+ * @description Creates a new feature flag record with the provided configuration.
3171
+ * This method is typically called through administrative interfaces or during
3172
+ * system setup and migration processes.
3173
+ *
3174
+ * @param {CreateFlagRequest<TKey>} data - The feature flag creation data
3175
+ * @returns {Promise<FeatureFlag<TKey>>} The created feature flag object
3176
+ *
3177
+ * @throws {Error} When flag key already exists (primary key constraint violation)
3178
+ * @throws {Error} When database operation fails
3179
+ *
3180
+ * @example
3181
+ * ```typescript
3182
+ * const repository = new FeatureFlagDatabaseRepository();
3183
+ *
3184
+ * // Create a simple boolean flag
3185
+ * const simpleFlag = await repository.createFlag({
3186
+ * key: 'ENABLE_DARK_MODE',
3187
+ * value: true,
3188
+ * isEnabled: true,
3189
+ * environment: 'production',
3190
+ * description: 'Enable dark mode theme'
3191
+ * });
3192
+ *
3193
+ * // Create a complex flag with object value
3194
+ * const complexFlag = await repository.createFlag({
3195
+ * key: 'CHECKOUT_CONFIG',
3196
+ * value: {
3197
+ * variant: 'blue',
3198
+ * showPromo: true,
3199
+ * maxItems: 10
3200
+ * },
3201
+ * isEnabled: false,
3202
+ * environment: 'staging',
3203
+ * description: 'Checkout page configuration'
3204
+ * });
3205
+ *
3206
+ * console.log(`Created flag: ${simpleFlag.key}`);
3207
+ * ```
2876
3208
  *
2877
- * @returns Array of supported provider names
2878
3209
  */
2879
- static getSupportedProviders() {
2880
- return Object.keys(PROVIDER_REGISTRY);
3210
+ async createFlag(data) {
3211
+ const db = this.connectionManager.getDatabase();
3212
+ const flagData = {
3213
+ [types.FEATURE_FLAG_FIELD.Key]: data.key,
3214
+ [types.FEATURE_FLAG_FIELD.Value]: data.value,
3215
+ [types.FEATURE_FLAG_FIELD.IsEnabled]: data.isEnabled ?? true,
3216
+ [types.FEATURE_FLAG_FIELD.Environments]: data.environment ? [data.environment] : void 0,
3217
+ [types.FEATURE_FLAG_FIELD.Description]: data.description,
3218
+ [types.FEATURE_FLAG_FIELD.UpdatedAt]: (/* @__PURE__ */ new Date()).toISOString()
3219
+ };
3220
+ const result = await db.create(types.DATABASE_TABLE.FeatureFlags, flagData);
3221
+ if (!result.success || !result.value) {
3222
+ throw new errors.DatabaseError(
3223
+ `Failed to create flag: ${result.error?.message}`,
3224
+ types.DATABASE_ERROR_CODES.CREATE_FAILED
3225
+ );
3226
+ }
3227
+ return this.mapToFeatureFlag(result.value);
2881
3228
  }
2882
3229
  /**
2883
- * Checks if a provider type is supported.
3230
+ * Updates an existing feature flag in the database
3231
+ *
3232
+ * @description Updates specific fields of an existing feature flag. Only provided
3233
+ * fields will be updated, leaving other fields unchanged. The updated_at timestamp
3234
+ * is automatically set to the current time.
3235
+ *
3236
+ * @param {TKey} key - The unique identifier of the feature flag to update
3237
+ * @param {Partial<CreateFlagRequest<TKey>>} data - Partial flag data to update
3238
+ * @returns {Promise<FeatureFlag<TKey>>} The updated feature flag object
3239
+ *
3240
+ * @throws {Error} When flag with the specified key is not found
3241
+ * @throws {Error} When database operation fails
3242
+ *
3243
+ * @example
3244
+ * ```typescript
3245
+ * const repository = new FeatureFlagDatabaseRepository();
3246
+ *
3247
+ * // Enable a flag
3248
+ * const enabledFlag = await repository.updateFlag('BETA_FEATURE', {
3249
+ * isEnabled: true
3250
+ * });
3251
+ *
3252
+ * // Update flag value and description
3253
+ * const updatedFlag = await repository.updateFlag('CHECKOUT_CONFIG', {
3254
+ * value: { variant: 'green', showPromo: false },
3255
+ * description: 'Updated checkout configuration'
3256
+ * });
3257
+ *
3258
+ * // Change environment
3259
+ * const prodFlag = await repository.updateFlag('NEW_FEATURE', {
3260
+ * environment: 'production'
3261
+ * });
3262
+ *
3263
+ * console.log(`Updated flag: ${updatedFlag.key}`);
3264
+ * ```
2884
3265
  *
2885
- * @param providerType - Provider type to check
2886
- * @returns True if provider type is supported
2887
3266
  */
2888
- static isProviderSupported(providerType) {
2889
- return providerType in PROVIDER_REGISTRY;
3267
+ async updateFlag(key, data) {
3268
+ const db = this.connectionManager.getDatabase();
3269
+ const updateData = {
3270
+ ...data,
3271
+ [types.FEATURE_FLAG_FIELD.UpdatedAt]: (/* @__PURE__ */ new Date()).toISOString()
3272
+ };
3273
+ const result = await db.update(types.DATABASE_TABLE.FeatureFlags, key, updateData);
3274
+ if (!result.success || !result.value) {
3275
+ throw new errors.DatabaseError(
3276
+ `Failed to update flag: ${result.error?.message}`,
3277
+ types.DATABASE_ERROR_CODES.UPDATE_FAILED
3278
+ );
3279
+ }
3280
+ return this.mapToFeatureFlag(result.value);
2890
3281
  }
2891
3282
  /**
2892
- * Gets provider information including implementation status.
3283
+ * Deletes a feature flag from the database
2893
3284
  *
2894
- * @returns Record of provider information
2895
- */
2896
- static getProvidersInfo() {
2897
- return {
2898
- memory: {
3285
+ * @description Permanently removes a feature flag from the database. This operation
3286
+ * cannot be undone. All associated rules, evaluations, and overrides should be
3287
+ * cleaned up separately if needed.
3288
+ *
3289
+ * @param {TKey} key - The unique identifier of the feature flag to delete
3290
+ * @returns {Promise<void>} Promise that resolves when deletion is complete
3291
+ *
3292
+ * @throws {Error} When flag with the specified key is not found
3293
+ * @throws {Error} When database operation fails
3294
+ *
3295
+ * @example
3296
+ * ```typescript
3297
+ * const repository = new FeatureFlagDatabaseRepository();
3298
+ *
3299
+ * // Delete a flag
3300
+ * try {
3301
+ * await repository.deleteFlag('OLD_FEATURE');
3302
+ * console.log('Flag deleted successfully');
3303
+ * } catch (error) {
3304
+ * console.error('Failed to delete flag:', error.message);
3305
+ * }
3306
+ *
3307
+ * // Verify deletion
3308
+ * const deletedFlag = await repository.getFlag('OLD_FEATURE');
3309
+ * console.log(deletedFlag === null); // true
3310
+ * ```
3311
+ *
3312
+ */
3313
+ async deleteFlag(key) {
3314
+ const db = this.connectionManager.getDatabase();
3315
+ const result = await db.delete(types.DATABASE_TABLE.FeatureFlags, key);
3316
+ if (!result.success) {
3317
+ throw new errors.DatabaseError(
3318
+ `Failed to delete flag: ${result.error?.message}`,
3319
+ types.DATABASE_ERROR_CODES.DELETE_FAILED
3320
+ );
3321
+ }
3322
+ }
3323
+ /**
3324
+ * Retrieves all rules associated with a specific feature flag
3325
+ *
3326
+ * @description Fetches all rules configured for a specific feature flag, ordered by
3327
+ * priority in descending order. Rules are used for advanced flag evaluation logic
3328
+ * based on user context and conditions.
3329
+ *
3330
+ * @param {TKey} key - The feature flag key to get rules for
3331
+ * @returns {Promise<FeatureFlagRule<TKey>[]>} Array of rules for the specified flag
3332
+ *
3333
+ * @example
3334
+ * ```typescript
3335
+ * const repository = new FeatureFlagDatabaseRepository();
3336
+ *
3337
+ * // Get rules for a flag
3338
+ * const rules = await repository.getFlagRules('PREMIUM_FEATURE');
3339
+ * console.log(`Found ${rules.length} rules`);
3340
+ *
3341
+ * // Process rules by priority
3342
+ * rules.forEach(rule => {
3343
+ * console.log(`Rule: ${rule.name}, Priority: ${rule.priority}`);
3344
+ * });
3345
+ * ```
3346
+ *
3347
+ */
3348
+ async getFlagRules(key) {
3349
+ const db = this.connectionManager.getDatabase();
3350
+ const options = {
3351
+ filter: {
3352
+ field: types.FEATURE_FLAG_RULE_FIELD.FlagKey,
3353
+ operator: "eq",
3354
+ value: key
3355
+ },
3356
+ sort: [
3357
+ {
3358
+ field: types.FEATURE_FLAG_RULE_FIELD.Priority,
3359
+ direction: types.SORT_DIRECTION.Desc
3360
+ }
3361
+ ]
3362
+ };
3363
+ const result = await db.list(
3364
+ types.DATABASE_TABLE.FeatureFlagRules,
3365
+ options
3366
+ );
3367
+ if (!result.success) {
3368
+ console.warn("getFlagRules failed, returning empty array:", result.error?.message);
3369
+ return [];
3370
+ }
3371
+ const rules = result.value?.data ?? [];
3372
+ return rules.map((rule) => this.mapToFeatureFlagRule(rule));
3373
+ }
3374
+ /**
3375
+ * Retrieves all enabled feature flag rules from the database
3376
+ *
3377
+ * @description Fetches all enabled rules across all feature flags, sorted by flag key
3378
+ * and then by priority. This method is used during system initialization to load
3379
+ * all active rules into the evaluation engine.
3380
+ *
3381
+ * @returns {Promise<FeatureFlagRule<TKey>[]>} Array of all enabled rules
3382
+ *
3383
+ * @example
3384
+ * ```typescript
3385
+ * const repository = new FeatureFlagDatabaseRepository();
3386
+ *
3387
+ * // Load all rules for evaluation engine
3388
+ * const allRules = await repository.getAllRules();
3389
+ * console.log(`Loaded ${allRules.length} active rules`);
3390
+ *
3391
+ * // Group rules by flag
3392
+ * const rulesByFlag = allRules.reduce((acc, rule) => {
3393
+ * if (!acc[rule.flagKey]) acc[rule.flagKey] = [];
3394
+ * acc[rule.flagKey].push(rule);
3395
+ * return acc;
3396
+ * }, {});
3397
+ * ```
3398
+ *
3399
+ */
3400
+ async getAllRules() {
3401
+ const db = this.connectionManager.getDatabase();
3402
+ const options = {
3403
+ filter: {
3404
+ field: types.FEATURE_FLAG_RULE_FIELD.IsEnabled,
3405
+ operator: "eq",
3406
+ value: true
3407
+ },
3408
+ sort: [
3409
+ { field: types.FEATURE_FLAG_RULE_FIELD.FlagKey, direction: types.SORT_DIRECTION.Asc },
3410
+ { field: types.FEATURE_FLAG_RULE_FIELD.Priority, direction: types.SORT_DIRECTION.Desc }
3411
+ ]
3412
+ };
3413
+ const result = await db.list(
3414
+ types.DATABASE_TABLE.FeatureFlagRules,
3415
+ options
3416
+ );
3417
+ if (!result.success) {
3418
+ console.warn("getAllRules failed, returning empty array:", result.error?.message);
3419
+ return [];
3420
+ }
3421
+ const rules = result.value?.data ?? [];
3422
+ return rules.map((rule) => this.mapToFeatureFlagRule(rule));
3423
+ }
3424
+ /**
3425
+ * Logs a feature flag evaluation for audit and analytics purposes
3426
+ *
3427
+ * @description Records feature flag evaluation events for compliance tracking,
3428
+ * debugging, and analytics. This method is called after each flag evaluation
3429
+ * when audit logging is enabled.
3430
+ *
3431
+ * @param {Object} params - The evaluation parameters
3432
+ * @param {TKey} params.flagKey - The feature flag key that was evaluated
3433
+ * @param {string} [params.userId] - Optional user ID who triggered the evaluation
3434
+ * @param {FeatureFlagContext} [params.context] - Optional evaluation context
3435
+ * @param {FeatureFlagValue} params.result - The evaluation result
3436
+ * @returns {Promise<void>} Promise that resolves when log entry is created
3437
+ *
3438
+ * @example
3439
+ * ```typescript
3440
+ * const repository = new FeatureFlagDatabaseRepository();
3441
+ *
3442
+ * // Log a user evaluation
3443
+ * await repository.logEvaluation({
3444
+ * flagKey: 'PREMIUM_FEATURE',
3445
+ * userId: 'user123',
3446
+ * context: {
3447
+ * userRole: 'premium',
3448
+ * environment: 'production',
3449
+ * requestId: 'req-456'
3450
+ * },
3451
+ * result: true
3452
+ * });
3453
+ *
3454
+ * // Log anonymous evaluation
3455
+ * await repository.logEvaluation({
3456
+ * flagKey: 'PUBLIC_FEATURE',
3457
+ * result: { variant: 'blue', enabled: true }
3458
+ * });
3459
+ * ```
3460
+ *
3461
+ */
3462
+ async logEvaluation(params) {
3463
+ try {
3464
+ const db = this.connectionManager.getDatabase();
3465
+ const evaluationData = {
3466
+ id: crypto.randomUUID(),
3467
+ flag_key: params.flagKey,
3468
+ user_id: params.userId,
3469
+ context: params.context ? params.context : void 0,
3470
+ result: params.result,
3471
+ reason: types.EVALUATION_REASONS.EVALUATION,
3472
+ evaluated_at: (/* @__PURE__ */ new Date()).toISOString()
3473
+ };
3474
+ const result = await db.create(types.DATABASE_TABLE.FeatureFlagEvaluations, evaluationData);
3475
+ if (!result.success) {
3476
+ console.warn("logEvaluation failed:", result.error?.message);
3477
+ }
3478
+ } catch (error) {
3479
+ console.warn("logEvaluation error:", error);
3480
+ }
3481
+ }
3482
+ /**
3483
+ * Sets a user-specific override for a feature flag
3484
+ *
3485
+ * @description Creates a user-specific override that takes precedence over the
3486
+ * default flag value and rules. Overrides can have optional expiration times
3487
+ * and are useful for testing, gradual rollouts, or user-specific configurations.
3488
+ *
3489
+ * @param {TKey} flagKey - The feature flag key to override
3490
+ * @param {string} userId - The user ID for whom to set the override
3491
+ * @param {FeatureFlagValue} value - The override value
3492
+ * @param {Date} [expiresAt] - Optional expiration date for the override
3493
+ * @returns {Promise<void>} Promise that resolves when override is created
3494
+ *
3495
+ * @example
3496
+ * ```typescript
3497
+ * const repository = new FeatureFlagDatabaseRepository();
3498
+ *
3499
+ * // Set permanent override
3500
+ * await repository.setOverride(
3501
+ * 'BETA_FEATURE',
3502
+ * 'user123',
3503
+ * true
3504
+ * );
3505
+ *
3506
+ * // Set temporary override (expires in 1 hour)
3507
+ * const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
3508
+ * await repository.setOverride(
3509
+ * 'PREMIUM_FEATURE',
3510
+ * 'testuser456',
3511
+ * { enabled: true, variant: 'premium' },
3512
+ * expiresAt
3513
+ * );
3514
+ * ```
3515
+ *
3516
+ */
3517
+ async setOverride(flagKey, userId, value, expiresAt) {
3518
+ const db = this.connectionManager.getDatabase();
3519
+ const overrideData = {
3520
+ id: crypto.randomUUID(),
3521
+ flag_key: flagKey,
3522
+ user_id: userId,
3523
+ value,
3524
+ expires_at: expiresAt?.toISOString(),
3525
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
3526
+ };
3527
+ const result = await db.create(types.DATABASE_TABLE.FeatureFlagOverrides, overrideData);
3528
+ if (!result.success) {
3529
+ console.warn("setOverride failed:", result.error?.message);
3530
+ }
3531
+ }
3532
+ /**
3533
+ * Retrieves a user-specific override for a feature flag
3534
+ *
3535
+ * @description Fetches the override value for a specific user and flag combination.
3536
+ * Automatically filters out expired overrides. Returns null if no valid override
3537
+ * exists for the user.
3538
+ *
3539
+ * @param {TKey} flagKey - The feature flag key to check for overrides
3540
+ * @param {string} userId - The user ID to get the override for
3541
+ * @returns {Promise<FeatureFlagValue | null>} The override value or null if none exists
3542
+ *
3543
+ * @example
3544
+ * ```typescript
3545
+ * const repository = new FeatureFlagDatabaseRepository();
3546
+ *
3547
+ * // Check for user override
3548
+ * const override = await repository.getOverride('BETA_FEATURE', 'user123');
3549
+ * if (override !== null) {
3550
+ * console.log('User has override:', override);
3551
+ * } else {
3552
+ * console.log('No override found, using default flag value');
3553
+ * }
3554
+ *
3555
+ * // Use in flag evaluation
3556
+ * const userOverride = await repository.getOverride('PREMIUM_FEATURE', userId);
3557
+ * const flagValue = userOverride ?? defaultFlagValue;
3558
+ * ```
3559
+ *
3560
+ */
3561
+ async getOverride(flagKey, userId) {
3562
+ const db = this.connectionManager.getDatabase();
3563
+ const options = {
3564
+ filter: {
3565
+ field: types.DATABASE_FIELDS.FlagKey,
3566
+ operator: "eq",
3567
+ value: flagKey
3568
+ }
3569
+ };
3570
+ const result = await db.list(
3571
+ types.DATABASE_TABLE.FeatureFlagOverrides,
3572
+ options
3573
+ );
3574
+ if (!result.success || !result.value?.data) {
3575
+ return null;
3576
+ }
3577
+ const typedData = result.value.data;
3578
+ const validOverrides = typedData.filter((override) => {
3579
+ if (override.user_id !== userId) return false;
3580
+ if (override.expires_at && new Date(override.expires_at) < /* @__PURE__ */ new Date()) return false;
3581
+ return true;
3582
+ });
3583
+ return validOverrides.length > 0 ? validOverrides[0].value : null;
3584
+ }
3585
+ /**
3586
+ * Removes user-specific overrides for a feature flag
3587
+ *
3588
+ * @description Deletes all override records for a specific user and flag combination.
3589
+ * This operation cannot be undone and will cause the user to receive the default
3590
+ * flag value or rule-based evaluation on subsequent requests.
3591
+ *
3592
+ * @param {TKey} flagKey - The feature flag key to remove overrides for
3593
+ * @param {string} userId - The user ID to remove overrides for
3594
+ * @returns {Promise<void>} Promise that resolves when overrides are removed
3595
+ *
3596
+ * @example
3597
+ * ```typescript
3598
+ * const repository = new FeatureFlagDatabaseRepository();
3599
+ *
3600
+ * // Remove user override
3601
+ * await repository.removeOverride('BETA_FEATURE', 'user123');
3602
+ * console.log('Override removed, user will get default flag value');
3603
+ *
3604
+ * // Verify removal
3605
+ * const override = await repository.getOverride('BETA_FEATURE', 'user123');
3606
+ * console.log(override === null); // true
3607
+ *
3608
+ * // Bulk cleanup - remove overrides for multiple users
3609
+ * const userIds = ['user1', 'user2', 'user3'];
3610
+ * for (const userId of userIds) {
3611
+ * await repository.removeOverride('OLD_FEATURE', userId);
3612
+ * }
3613
+ * ```
3614
+ *
3615
+ */
3616
+ async removeOverride(flagKey, userId) {
3617
+ const db = this.connectionManager.getDatabase();
3618
+ const options = {
3619
+ filter: {
3620
+ field: types.DATABASE_FIELDS.FlagKey,
3621
+ operator: "eq",
3622
+ value: flagKey
3623
+ }
3624
+ };
3625
+ const listResult = await db.list(
3626
+ types.DATABASE_TABLE.FeatureFlagOverrides,
3627
+ options
3628
+ );
3629
+ if (!listResult.success || !listResult.value?.data) {
3630
+ return;
3631
+ }
3632
+ const typedOverrides = listResult.value.data;
3633
+ const overridesToDelete = typedOverrides.filter((override) => override.user_id === userId);
3634
+ for (const override of overridesToDelete) {
3635
+ await db.delete(types.DATABASE_TABLE.FeatureFlagOverrides, override.id);
3636
+ }
3637
+ }
3638
+ /**
3639
+ * Maps a database row to a FeatureFlag object
3640
+ *
3641
+ * @description Converts raw database row data to a properly typed FeatureFlag object.
3642
+ * Handles field name variations between database schema and application types,
3643
+ * provides default values for missing fields, and ensures type safety.
3644
+ *
3645
+ * @private
3646
+ * @param {DatabaseFeatureFlagRow} row - Raw database row data
3647
+ * @returns {FeatureFlag<TKey>} Typed FeatureFlag object
3648
+ *
3649
+ * @example
3650
+ * ```typescript
3651
+ * // Database row input:
3652
+ * const dbRow = {
3653
+ * key: 'PREMIUM_FEATURE',
3654
+ * value: { enabled: true, variant: 'blue' },
3655
+ * is_enabled: true,
3656
+ * environments: ['production'],
3657
+ * description: 'Premium feature toggle',
3658
+ * created_at: '2024-01-15T10:30:00Z'
3659
+ * };
3660
+ *
3661
+ * // Mapped output:
3662
+ * const flag = this.mapToFeatureFlag(dbRow);
3663
+ * // {
3664
+ * // key: 'PREMIUM_FEATURE',
3665
+ * // type: 'boolean',
3666
+ * // name: 'PREMIUM_FEATURE',
3667
+ * // value: { enabled: true, variant: 'blue' },
3668
+ * // isEnabled: true,
3669
+ * // environment: 'production',
3670
+ * // description: 'Premium feature toggle',
3671
+ * // createdAt: Date('2024-01-15T10:30:00Z'),
3672
+ * // updatedAt: Date('2024-01-15T10:30:00Z'),
3673
+ * // createdBy: 'system',
3674
+ * // updatedBy: 'system'
3675
+ * // }
3676
+ * ```
3677
+ *
3678
+ */
3679
+ mapToFeatureFlag(row) {
3680
+ return {
3681
+ key: row.key,
3682
+ type: types.FEATURE_FLAG_TYPES.BOOLEAN,
3683
+ name: row.key,
3684
+ value: row.value,
3685
+ isEnabled: this.getIsEnabled(row),
3686
+ environment: row.environments?.[0] ?? "",
3687
+ description: row.description ?? "",
3688
+ createdAt: this.getCreatedAt(row),
3689
+ updatedAt: this.getUpdatedAt(row),
3690
+ createdBy: types.SYSTEM_USERS.SYSTEM,
3691
+ updatedBy: types.SYSTEM_USERS.SYSTEM
3692
+ };
3693
+ }
3694
+ /**
3695
+ * Extracts the enabled status from a database row
3696
+ *
3697
+ * @private
3698
+ * @param {DatabaseFeatureFlagRow} row - Database row with enabled field variations
3699
+ * @returns {boolean} The enabled status, defaulting to true if not specified
3700
+ */
3701
+ getIsEnabled(row) {
3702
+ return row.isEnabled ?? row.is_enabled ?? true;
3703
+ }
3704
+ /**
3705
+ * Extracts the creation date from a database row
3706
+ *
3707
+ * @private
3708
+ * @param {DatabaseFeatureFlagRow} row - Database row with creation date field variations
3709
+ * @returns {Date} The creation date, defaulting to current date if not specified
3710
+ */
3711
+ getCreatedAt(row) {
3712
+ return row.createdAt ?? (row.created_at ? new Date(row.created_at) : /* @__PURE__ */ new Date());
3713
+ }
3714
+ /**
3715
+ * Extracts the update date from a database row
3716
+ *
3717
+ * @private
3718
+ * @param {DatabaseFeatureFlagRow} row - Database row with update date field variations
3719
+ * @returns {Date} The update date, defaulting to current date if not specified
3720
+ */
3721
+ getUpdatedAt(row) {
3722
+ return row.updatedAt ?? (row.updated_at ? new Date(row.updated_at) : /* @__PURE__ */ new Date());
3723
+ }
3724
+ /**
3725
+ * Maps a database row to a FeatureFlagRule object
3726
+ *
3727
+ * @description Converts raw database rule row data to a properly typed FeatureFlagRule object.
3728
+ * Handles field name variations and ensures type safety for rule evaluation.
3729
+ *
3730
+ * @private
3731
+ * @param {DatabaseFeatureFlagRuleRow} row - Raw database rule row data
3732
+ * @returns {FeatureFlagRule<TKey>} Typed FeatureFlagRule object
3733
+ *
3734
+ */
3735
+ mapToFeatureFlagRule(row) {
3736
+ return {
3737
+ id: row.id,
3738
+ flagKey: row.flagKey || row.flag_key,
3739
+ name: row.name,
3740
+ conditions: row.conditions,
3741
+ value: row.value,
3742
+ priority: row.priority,
3743
+ isEnabled: row.isEnabled ?? row.is_enabled ?? true
3744
+ };
3745
+ }
3746
+ };
3747
+
3748
+ // src/domain/featureFlags/providers/database.ts
3749
+ var DatabaseFeatureFlagProvider = class extends FeatureFlagProvider {
3750
+ static {
3751
+ __name(this, "DatabaseFeatureFlagProvider");
3752
+ }
3753
+ connectionManager = DatabaseConnectionManager.getInstance();
3754
+ repository = new FeatureFlagDatabaseRepository();
3755
+ /**
3756
+ * Creates a new database feature flag provider.
3757
+ *
3758
+ * @param config - Provider configuration with database settings
3759
+ */
3760
+ constructor(config, features) {
3761
+ super(config, features);
3762
+ throw new Error("Database provider requires @plyaz/db package implementation");
3763
+ }
3764
+ /**
3765
+ * Initialize database connection
3766
+ */
3767
+ async initialize() {
3768
+ await this.connectionManager.initialize();
3769
+ await super.initialize();
3770
+ }
3771
+ /**
3772
+ * Fetches flags and rules from the database.
3773
+ *
3774
+ * @protected
3775
+ * @returns Promise with flags and rules from database
3776
+ */
3777
+ async fetchData() {
3778
+ throw new Error(
3779
+ "Database Provider is not yet implemented.\n\nRequired Database Setup:\n- Install @plyaz/db package\n- Configure database connection\n- Run database migrations\n\nRequired Tables:\n- feature_flags\n- feature_flag_rules\n\nDatabase Schema:\nSee /src/backend/featureFlags/database/schema.ts for table definitions"
3780
+ );
3781
+ }
3782
+ /**
3783
+ * Dispose resources
3784
+ */
3785
+ dispose() {
3786
+ super.dispose();
3787
+ this.connectionManager.close().catch((error) => {
3788
+ this.log("Error closing database connection:", error);
3789
+ });
3790
+ }
3791
+ /**
3792
+ * Validates the database provider configuration.
3793
+ *
3794
+ * @private
3795
+ * @throws Error if configuration is invalid or incomplete
3796
+ */
3797
+ validateConfig() {
3798
+ this.validateProviderType();
3799
+ this.validateDatabaseConfig();
3800
+ this.validateConnectionString();
3801
+ this.logConfigurationStatus();
3802
+ }
3803
+ validateProviderType() {
3804
+ if (this.config.provider !== "database") {
3805
+ throw new Error('Database provider requires provider to be set to "database"');
3806
+ }
3807
+ }
3808
+ validateDatabaseConfig() {
3809
+ if (!this.config.databaseConfig) {
3810
+ throw new Error(
3811
+ 'Database configuration is required for database provider. Set databaseConfig in your configuration.\nExample: databaseConfig: { connectionString: "postgresql://...", tableName: "feature_flags" }'
3812
+ );
3813
+ }
3814
+ }
3815
+ validateConnectionString() {
3816
+ const { connectionString, tableName } = this.config.databaseConfig;
3817
+ if (!connectionString) {
3818
+ throw new Error(
3819
+ 'Database connection string is required. Set connectionString in your databaseConfig.\nExample: connectionString: "postgresql://user:pass@localhost:5432/plyaz"'
3820
+ );
3821
+ }
3822
+ if (!this.isValidPostgresUrl(connectionString) && !this.isValidMysqlUrl(connectionString)) {
3823
+ throw new Error(
3824
+ `Database connection string must be a valid PostgreSQL or MySQL URL. Received: ${connectionString}
3825
+ Examples:
3826
+ - PostgreSQL: "postgresql://user:pass@localhost:5432/plyaz"
3827
+ - MySQL: "mysql://user:pass@localhost:3306/plyaz"`
3828
+ );
3829
+ }
3830
+ if (!tableName || typeof tableName !== "string") {
3831
+ throw new Error("Database provider requires databaseConfig.tableName");
3832
+ }
3833
+ }
3834
+ logConfigurationStatus() {
3835
+ const { connectionString, tableName } = this.config.databaseConfig;
3836
+ this.log("Database provider configuration is valid, but implementation is not ready");
3837
+ this.log("Connection String:", this.maskConnectionString(connectionString));
3838
+ this.log("Table Name:", tableName ?? "feature_flags (default)");
3839
+ }
3840
+ /**
3841
+ * Validates PostgreSQL URL format.
3842
+ *
3843
+ * @private
3844
+ * @param url - URL to validate
3845
+ * @returns True if valid PostgreSQL URL
3846
+ */
3847
+ isValidPostgresUrl(url) {
3848
+ return url.startsWith("postgresql://") || url.startsWith("postgres://");
3849
+ }
3850
+ /**
3851
+ * Validates MySQL URL format.
3852
+ *
3853
+ * @private
3854
+ * @param url - URL to validate
3855
+ * @returns True if valid MySQL URL
3856
+ */
3857
+ isValidMysqlUrl(url) {
3858
+ return url.startsWith("mysql://");
3859
+ }
3860
+ /**
3861
+ * Masks sensitive parts of connection string for logging.
3862
+ *
3863
+ * @private
3864
+ * @param connectionString - Original connection string
3865
+ * @returns Masked connection string
3866
+ */
3867
+ maskConnectionString(connectionString) {
3868
+ try {
3869
+ const url = new URL(connectionString);
3870
+ const masked = `${url.protocol}//${url.username}:****@${url.host}${url.pathname}`;
3871
+ return masked;
3872
+ } catch {
3873
+ return "[INVALID_URL]";
3874
+ }
3875
+ }
3876
+ /**
3877
+ * Gets database provider status and configuration info.
3878
+ *
3879
+ * @returns Database provider status information
3880
+ */
3881
+ getDatabaseInfo() {
3882
+ return {
3883
+ connectionString: this.config.databaseConfig?.connectionString ? this.maskConnectionString(this.config.databaseConfig.connectionString) : void 0,
3884
+ tableName: this.config.databaseConfig?.tableName ?? "feature_flags",
3885
+ isImplemented: false,
3886
+ requiredPackages: ["@plyaz/db"],
3887
+ recommendedORM: ["drizzle-orm", "prisma"],
3888
+ documentationPath: "/docs/feature-flag-to-implement/database-requirements.md",
3889
+ schemaPath: "/docs/feature-flag-to-implement/database-requirements.md#database-schema"
3890
+ };
3891
+ }
3892
+ };
3893
+ var PROVIDER_REGISTRY = {
3894
+ memory: MemoryFeatureFlagProvider,
3895
+ file: FileFeatureFlagProvider,
3896
+ redis: RedisFeatureFlagProvider,
3897
+ api: ApiFeatureFlagProvider,
3898
+ database: DatabaseFeatureFlagProvider
3899
+ };
3900
+ var FeatureFlagProviderFactory = class {
3901
+ static {
3902
+ __name(this, "FeatureFlagProviderFactory");
3903
+ }
3904
+ /**
3905
+ * Creates a new feature flag provider instance based on configuration.
3906
+ *
3907
+ * @param config - Provider configuration
3908
+ * @param features - Record of feature flag keys to their default values
3909
+ * @returns Configured provider instance
3910
+ * @throws Error if provider type is unsupported or configuration is invalid
3911
+ */
3912
+ static create(config, features) {
3913
+ this.validateConfig(config);
3914
+ const ProviderClass = PROVIDER_REGISTRY[config.provider];
3915
+ if (!ProviderClass) {
3916
+ throw new Error(
3917
+ `Unsupported provider type: ${config.provider}. Supported types: ${this.getSupportedProviders().join(", ")}`
3918
+ );
3919
+ }
3920
+ try {
3921
+ return new ProviderClass(config, features);
3922
+ } catch (error) {
3923
+ throw new Error(
3924
+ `Failed to create ${config.provider} provider: ${error instanceof Error ? error.message : "Unknown error"}`
3925
+ );
3926
+ }
3927
+ }
3928
+ /**
3929
+ * Creates a provider with automatic initialization.
3930
+ *
3931
+ * @param config - Provider configuration
3932
+ * @param features - Record of feature flag keys to their default values
3933
+ * @returns Promise resolving to initialized provider instance
3934
+ */
3935
+ static async createAndInitialize(config, features) {
3936
+ const provider = this.create(config, features);
3937
+ await provider.initialize();
3938
+ return provider;
3939
+ }
3940
+ /**
3941
+ * Gets a list of all supported provider types.
3942
+ *
3943
+ * @returns Array of supported provider names
3944
+ */
3945
+ static getSupportedProviders() {
3946
+ return Object.keys(PROVIDER_REGISTRY);
3947
+ }
3948
+ /**
3949
+ * Checks if a provider type is supported.
3950
+ *
3951
+ * @param providerType - Provider type to check
3952
+ * @returns True if provider type is supported
3953
+ */
3954
+ static isProviderSupported(providerType) {
3955
+ return providerType in PROVIDER_REGISTRY;
3956
+ }
3957
+ /**
3958
+ * Gets provider information including implementation status.
3959
+ *
3960
+ * @returns Record of provider information
3961
+ */
3962
+ static getProvidersInfo() {
3963
+ return {
3964
+ memory: {
2899
3965
  name: "Memory Provider",
2900
3966
  isImplemented: true,
2901
3967
  description: "In-memory provider using FEATURES constant"
@@ -3123,9 +4189,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3123
4189
  try {
3124
4190
  return await this.featureFlagService.evaluateFlag(key, body.context);
3125
4191
  } catch (error) {
3126
- throw new common.HttpException(
3127
- `Failed to evaluate flag: ${error instanceof Error ? error.message : "Unknown error"}`,
3128
- common.HttpStatus.BAD_REQUEST
4192
+ throw new errors.BaseError(
4193
+ types.ERROR_CODES.API_INVALID_INPUT,
4194
+ types.HTTP_STATUS.BAD_REQUEST,
4195
+ `Failed to evaluate flag: ${error instanceof Error ? error.message : "Unknown error"}`
3129
4196
  );
3130
4197
  }
3131
4198
  }
@@ -3134,9 +4201,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3134
4201
  const isEnabled = await this.featureFlagService.isEnabled(key, body.context);
3135
4202
  return { isEnabled };
3136
4203
  } 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
4204
+ throw new errors.BaseError(
4205
+ types.ERROR_CODES.API_INVALID_INPUT,
4206
+ types.HTTP_STATUS.BAD_REQUEST,
4207
+ `Failed to check flag status: ${error instanceof Error ? error.message : "Unknown error"}`
3140
4208
  );
3141
4209
  }
3142
4210
  }
@@ -3144,9 +4212,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3144
4212
  try {
3145
4213
  return await this.featureFlagService.getAllFlags(body.context);
3146
4214
  } 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
4215
+ throw new errors.BaseError(
4216
+ types.ERROR_CODES.SERVER_ERROR,
4217
+ types.HTTP_STATUS.INTERNAL_SERVER_ERROR,
4218
+ `Failed to evaluate all flags: ${error instanceof Error ? error.message : "Unknown error"}`
3150
4219
  );
3151
4220
  }
3152
4221
  }
@@ -3154,9 +4223,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3154
4223
  try {
3155
4224
  return await this.featureFlagService.createFlag(createData);
3156
4225
  } catch (error) {
3157
- throw new common.HttpException(
3158
- `Failed to create flag: ${error instanceof Error ? error.message : "Unknown error"}`,
3159
- common.HttpStatus.BAD_REQUEST
4226
+ throw new errors.BaseError(
4227
+ types.ERROR_CODES.API_INVALID_INPUT,
4228
+ types.HTTP_STATUS.BAD_REQUEST,
4229
+ `Failed to create flag: ${error instanceof Error ? error.message : "Unknown error"}`
3160
4230
  );
3161
4231
  }
3162
4232
  }
@@ -3164,9 +4234,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3164
4234
  try {
3165
4235
  return await this.featureFlagService.updateFlag(key, updateData);
3166
4236
  } catch (error) {
3167
- throw new common.HttpException(
3168
- `Failed to update flag: ${error instanceof Error ? error.message : "Unknown error"}`,
3169
- common.HttpStatus.BAD_REQUEST
4237
+ throw new errors.BaseError(
4238
+ types.ERROR_CODES.API_INVALID_INPUT,
4239
+ types.HTTP_STATUS.BAD_REQUEST,
4240
+ `Failed to update flag: ${error instanceof Error ? error.message : "Unknown error"}`
3170
4241
  );
3171
4242
  }
3172
4243
  }
@@ -3175,9 +4246,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3175
4246
  await this.featureFlagService.deleteFlag(key);
3176
4247
  return { isSuccessful: true };
3177
4248
  } catch (error) {
3178
- throw new common.HttpException(
3179
- `Failed to delete flag: ${error instanceof Error ? error.message : "Unknown error"}`,
3180
- common.HttpStatus.BAD_REQUEST
4249
+ throw new errors.BaseError(
4250
+ types.ERROR_CODES.API_INVALID_INPUT,
4251
+ types.HTTP_STATUS.BAD_REQUEST,
4252
+ `Failed to delete flag: ${error instanceof Error ? error.message : "Unknown error"}`
3181
4253
  );
3182
4254
  }
3183
4255
  }
@@ -3186,9 +4258,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3186
4258
  await this.featureFlagService.setOverride(key, value);
3187
4259
  return { isSuccessful: true };
3188
4260
  } catch (error) {
3189
- throw new common.HttpException(
3190
- `Failed to set override: ${error instanceof Error ? error.message : "Unknown error"}`,
3191
- common.HttpStatus.BAD_REQUEST
4261
+ throw new errors.BaseError(
4262
+ types.ERROR_CODES.API_INVALID_INPUT,
4263
+ types.HTTP_STATUS.BAD_REQUEST,
4264
+ `Failed to set override: ${error instanceof Error ? error.message : "Unknown error"}`
3192
4265
  );
3193
4266
  }
3194
4267
  }
@@ -3197,9 +4270,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3197
4270
  await this.featureFlagService.removeOverride(key);
3198
4271
  return { isSuccessful: true };
3199
4272
  } catch (error) {
3200
- throw new common.HttpException(
3201
- `Failed to remove override: ${error instanceof Error ? error.message : "Unknown error"}`,
3202
- common.HttpStatus.BAD_REQUEST
4273
+ throw new errors.BaseError(
4274
+ types.ERROR_CODES.API_INVALID_INPUT,
4275
+ types.HTTP_STATUS.BAD_REQUEST,
4276
+ `Failed to remove override: ${error instanceof Error ? error.message : "Unknown error"}`
3203
4277
  );
3204
4278
  }
3205
4279
  }
@@ -3207,9 +4281,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3207
4281
  try {
3208
4282
  return await this.featureFlagService.getAllFeatureFlags(environment);
3209
4283
  } 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
4284
+ throw new errors.BaseError(
4285
+ types.ERROR_CODES.SERVER_ERROR,
4286
+ types.HTTP_STATUS.INTERNAL_SERVER_ERROR,
4287
+ `Failed to get flags: ${error instanceof Error ? error.message : "Unknown error"}`
3213
4288
  );
3214
4289
  }
3215
4290
  }
@@ -3217,9 +4292,10 @@ exports.FeatureFlagController = class FeatureFlagController {
3217
4292
  try {
3218
4293
  return await this.featureFlagService.getFlagRules(key);
3219
4294
  } 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
4295
+ throw new errors.BaseError(
4296
+ types.ERROR_CODES.API_INVALID_INPUT,
4297
+ types.HTTP_STATUS.BAD_REQUEST,
4298
+ `Failed to get flag rules: ${error instanceof Error ? error.message : "Unknown error"}`
3223
4299
  );
3224
4300
  }
3225
4301
  }
@@ -3228,64 +4304,443 @@ exports.FeatureFlagController = class FeatureFlagController {
3228
4304
  await this.featureFlagService.refreshCache();
3229
4305
  return { isSuccessful: true };
3230
4306
  } 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
4307
+ throw new errors.BaseError(
4308
+ types.ERROR_CODES.SERVER_ERROR,
4309
+ types.HTTP_STATUS.INTERNAL_SERVER_ERROR,
4310
+ `Failed to refresh cache: ${error instanceof Error ? error.message : "Unknown error"}`
4311
+ );
4312
+ }
4313
+ }
4314
+ };
4315
+ __name(exports.FeatureFlagController, "FeatureFlagController");
4316
+ __decorateClass([
4317
+ common.Post(":key/evaluate"),
4318
+ __decorateParam(0, common.Param("key")),
4319
+ __decorateParam(1, common.Body())
4320
+ ], exports.FeatureFlagController.prototype, "evaluateFlag", 1);
4321
+ __decorateClass([
4322
+ common.Post(":key/enabled"),
4323
+ __decorateParam(0, common.Param("key")),
4324
+ __decorateParam(1, common.Body())
4325
+ ], exports.FeatureFlagController.prototype, "isEnabled", 1);
4326
+ __decorateClass([
4327
+ common.Post("evaluate-all"),
4328
+ __decorateParam(0, common.Body())
4329
+ ], exports.FeatureFlagController.prototype, "evaluateAllFlags", 1);
4330
+ __decorateClass([
4331
+ common.Post(),
4332
+ __decorateParam(0, common.Body())
4333
+ ], exports.FeatureFlagController.prototype, "createFlag", 1);
4334
+ __decorateClass([
4335
+ common.Put(":key"),
4336
+ __decorateParam(0, common.Param("key")),
4337
+ __decorateParam(1, common.Body())
4338
+ ], exports.FeatureFlagController.prototype, "updateFlag", 1);
4339
+ __decorateClass([
4340
+ common.Delete(":key"),
4341
+ __decorateParam(0, common.Param("key"))
4342
+ ], exports.FeatureFlagController.prototype, "deleteFlag", 1);
4343
+ __decorateClass([
4344
+ common.Post(":key/override"),
4345
+ __decorateParam(0, common.Param("key")),
4346
+ __decorateParam(1, common.Body("value"))
4347
+ ], exports.FeatureFlagController.prototype, "setOverride", 1);
4348
+ __decorateClass([
4349
+ common.Delete(":key/override"),
4350
+ __decorateParam(0, common.Param("key"))
4351
+ ], exports.FeatureFlagController.prototype, "removeOverride", 1);
4352
+ __decorateClass([
4353
+ common.Get(),
4354
+ __decorateParam(0, common.Query("environment"))
4355
+ ], exports.FeatureFlagController.prototype, "getAllFeatureFlags", 1);
4356
+ __decorateClass([
4357
+ common.Get(":key/rules"),
4358
+ __decorateParam(0, common.Param("key"))
4359
+ ], exports.FeatureFlagController.prototype, "getFlagRules", 1);
4360
+ __decorateClass([
4361
+ common.Post("refresh")
4362
+ ], exports.FeatureFlagController.prototype, "refreshCache", 1);
4363
+ exports.FeatureFlagController = __decorateClass([
4364
+ common.Controller("feature-flags")
4365
+ ], exports.FeatureFlagController);
4366
+ var FeatureFlagConfigValidator = class {
4367
+ static {
4368
+ __name(this, "FeatureFlagConfigValidator");
4369
+ }
4370
+ /**
4371
+ * Validates feature flag configuration and returns detailed results
4372
+ *
4373
+ * @description Performs comprehensive validation of feature flag configuration,
4374
+ * checking all aspects including provider settings, cache configuration,
4375
+ * database connections, and environment-specific settings.
4376
+ *
4377
+ * @param {FeatureFlagEnvironmentConfig} config - Configuration object to validate
4378
+ * @returns {ValidationResult} Validation result with errors and warnings
4379
+ *
4380
+ * @example Successful Validation
4381
+ * ```typescript
4382
+ * const validConfig = {
4383
+ * provider: 'memory',
4384
+ * isCacheEnabled: true,
4385
+ * cacheTtl: 300,
4386
+ * refreshInterval: 60
4387
+ * };
4388
+ *
4389
+ * const result = FeatureFlagConfigValidator.validate(validConfig);
4390
+ * // result.isValid = true
4391
+ * // result.errors = []
4392
+ * // result.warnings = []
4393
+ * ```
4394
+ *
4395
+ * @example Validation with Errors
4396
+ * ```typescript
4397
+ * const invalidConfig = {
4398
+ * provider: 'invalid_provider', // Error: invalid provider
4399
+ * cacheTtl: -100, // Error: negative TTL
4400
+ * databaseConfig: { // Error: missing connection string
4401
+ * tableName: 'flags'
4402
+ * }
4403
+ * };
4404
+ *
4405
+ * const result = FeatureFlagConfigValidator.validate(invalidConfig);
4406
+ * // result.isValid = false
4407
+ * // result.errors = [
4408
+ * // { field: 'provider', message: 'Invalid provider...', code: 'INVALID_PROVIDER' },
4409
+ * // { field: 'cacheTtl', message: 'Cache TTL must be non-negative', code: 'INVALID_CACHE_TTL' }
4410
+ * // ]
4411
+ * ```
4412
+ *
4413
+ * @example Validation with Warnings
4414
+ * ```typescript
4415
+ * const configWithWarnings = {
4416
+ * provider: 'database',
4417
+ * cacheTtl: 7200, // Warning: very high TTL
4418
+ * isLoggingEnabled: true, // Warning: logging in production
4419
+ * databaseConfig: {
4420
+ * connectionString: 'https://project.supabase.co',
4421
+ * tableName: 'feature_flags'
4422
+ * }
4423
+ * };
4424
+ *
4425
+ * process.env.NODE_ENV = 'production';
4426
+ * const result = FeatureFlagConfigValidator.validate(configWithWarnings);
4427
+ * // result.isValid = true (warnings don't fail validation)
4428
+ * // result.warnings = [
4429
+ * // { field: 'cacheTtl', message: 'Cache TTL is very high...', code: 'HIGH_CACHE_TTL' },
4430
+ * // { field: 'isLoggingEnabled', message: 'Logging is enabled in production...', code: 'PRODUCTION_LOGGING_ENABLED' }
4431
+ * // ]
4432
+ * ```
4433
+ */
4434
+ static validate(config$1) {
4435
+ try {
4436
+ this.validateProvider(config$1);
4437
+ this.validateCacheSettings(config$1);
4438
+ if (config$1.provider === config.FEATURE_FLAG_PROVIDERS.DATABASE) {
4439
+ this.validateDatabaseConfig(config$1);
4440
+ }
4441
+ return {
4442
+ isValid: true,
4443
+ errors: [],
4444
+ warnings: this.getWarnings(config$1)
4445
+ };
4446
+ } catch (error) {
4447
+ return {
4448
+ isValid: false,
4449
+ errors: [
4450
+ {
4451
+ field: "config",
4452
+ message: error instanceof Error ? error.message : "Validation failed",
4453
+ code: error instanceof errors.BaseError ? error.code : "VALIDATION_ERROR"
4454
+ }
4455
+ ],
4456
+ warnings: []
4457
+ };
4458
+ }
4459
+ }
4460
+ static validateProvider(config) {
4461
+ const validProviders = getValidProviders();
4462
+ if (!validProviders.includes(config.provider)) {
4463
+ throw new errors.ValidationError(
4464
+ types.ERROR_CODES.CLIENT_INVALID_CONFIG,
4465
+ types.HTTP_STATUS.BAD_REQUEST,
4466
+ `Invalid provider: ${config.provider}. Must be one of: ${validProviders.join(", ")}`
4467
+ );
4468
+ }
4469
+ }
4470
+ static validateCacheSettings(config) {
4471
+ if (config.cacheTtl < 0) {
4472
+ throw new errors.ValidationError(
4473
+ types.ERROR_CODES.CLIENT_INVALID_CONFIG,
4474
+ types.HTTP_STATUS.BAD_REQUEST,
4475
+ "Cache TTL must be non-negative"
4476
+ );
4477
+ }
4478
+ if (config.refreshInterval < 0) {
4479
+ throw new errors.ValidationError(
4480
+ types.ERROR_CODES.CLIENT_INVALID_CONFIG,
4481
+ types.HTTP_STATUS.BAD_REQUEST,
4482
+ "Refresh interval must be non-negative"
4483
+ );
4484
+ }
4485
+ }
4486
+ static validateDatabaseConfig(config) {
4487
+ if (!config.databaseConfig) {
4488
+ throw new errors.ValidationError(
4489
+ types.ERROR_CODES.DB_CONFIG_REQUIRED,
4490
+ types.HTTP_STATUS.BAD_REQUEST,
4491
+ "Database configuration is required for database provider"
4492
+ );
4493
+ }
4494
+ const { connectionString, tableName, poolSize, timeout } = config.databaseConfig;
4495
+ this.validateConnectionString(connectionString);
4496
+ this.validateTableName(tableName);
4497
+ this.validatePoolSize(poolSize);
4498
+ this.validateTimeout(timeout);
4499
+ }
4500
+ static validateConnectionString(connectionString) {
4501
+ if (!connectionString) {
4502
+ throw new errors.ValidationError(
4503
+ types.ERROR_CODES.DB_CONFIG_REQUIRED,
4504
+ types.HTTP_STATUS.BAD_REQUEST,
4505
+ "Database connection string is required"
4506
+ );
4507
+ }
4508
+ try {
4509
+ new URL(connectionString);
4510
+ } catch {
4511
+ throw new errors.ValidationError(
4512
+ types.ERROR_CODES.CLIENT_INVALID_CONFIG,
4513
+ types.HTTP_STATUS.BAD_REQUEST,
4514
+ "Invalid database connection string format"
4515
+ );
4516
+ }
4517
+ }
4518
+ static validateTableName(tableName) {
4519
+ if (tableName && !isString(tableName)) {
4520
+ throw new errors.ValidationError(
4521
+ types.ERROR_CODES.CLIENT_INVALID_CONFIG,
4522
+ types.HTTP_STATUS.BAD_REQUEST,
4523
+ "Table name must be a string"
4524
+ );
4525
+ }
4526
+ }
4527
+ static validatePoolSize(poolSize) {
4528
+ if (isDefined(poolSize) && (poolSize < config.NUMERIC_CONSTANTS.MIN_POOL_SIZE || poolSize > config.NUMERIC_CONSTANTS.MAX_POOL_SIZE)) {
4529
+ throw new errors.ValidationError(
4530
+ types.ERROR_CODES.CLIENT_INVALID_CONFIG,
4531
+ types.HTTP_STATUS.BAD_REQUEST,
4532
+ "Pool size must be between 1 and 100"
4533
+ );
4534
+ }
4535
+ }
4536
+ static validateTimeout(timeout) {
4537
+ if (isDefined(timeout) && timeout < config.NUMERIC_CONSTANTS.MIN_TIMEOUT_MS) {
4538
+ throw new errors.ValidationError(
4539
+ types.ERROR_CODES.CLIENT_INVALID_CONFIG,
4540
+ types.HTTP_STATUS.BAD_REQUEST,
4541
+ "Timeout must be at least 1000ms"
4542
+ );
4543
+ }
4544
+ }
4545
+ static getWarnings(config$1) {
4546
+ const env = process.env.NODE_ENV;
4547
+ if (config$1.cacheTtl > config.NUMERIC_CONSTANTS.ONE_HOUR_SECONDS) {
4548
+ common.Logger.warn(
4549
+ "Cache TTL is very high (>1 hour). Consider reducing for better responsiveness.",
4550
+ {
4551
+ field: "cacheTtl",
4552
+ value: config$1.cacheTtl,
4553
+ code: "HIGH_CACHE_TTL"
4554
+ }
4555
+ );
4556
+ }
4557
+ if (env === types.NODE_ENVIRONMENTS.PRODUCTION && config$1.isLoggingEnabled) {
4558
+ common.Logger.warn("Logging is enabled in production. Consider disabling for performance.", {
4559
+ field: "isLoggingEnabled",
4560
+ code: "PRODUCTION_LOGGING_ENABLED"
4561
+ });
4562
+ }
4563
+ if (env === types.NODE_ENVIRONMENTS.DEVELOPMENT && !config$1.isCacheEnabled) {
4564
+ common.Logger.warn("Cache is disabled in development. This may impact performance testing.", {
4565
+ field: "isCacheEnabled",
4566
+ code: "DEVELOPMENT_CACHE_DISABLED"
4567
+ });
4568
+ }
4569
+ return [];
4570
+ }
4571
+ /**
4572
+ * Validates configuration and throws error if validation fails
4573
+ *
4574
+ * @description Convenience method that validates configuration and throws a detailed
4575
+ * error if validation fails. This is typically used during application startup
4576
+ * to ensure the system doesn't start with invalid configuration.
4577
+ *
4578
+ * @param {FeatureFlagEnvironmentConfig} config - Configuration to validate
4579
+ * @throws {DatabaseError} When validation fails with detailed error messages
4580
+ *
4581
+ * @example Startup Validation
4582
+ * ```typescript
4583
+ * // In FeatureFlagService.onModuleInit()
4584
+ * try {
4585
+ * const config = FeatureFlagConfigFactory.fromEnvironment();
4586
+ * FeatureFlagConfigValidator.validateOrThrow(config);
4587
+ *
4588
+ * // Configuration is valid, proceed with initialization
4589
+ * this.provider = FeatureFlagProviderFactory.create(config, FEATURES);
4590
+ * await this.provider.initialize();
4591
+ * } catch (error) {
4592
+ * this.logger.error('Configuration validation failed:', error.message);
4593
+ * throw error; // Prevent service from starting
4594
+ * }
4595
+ * ```
4596
+ *
4597
+ * @example Error Output
4598
+ * ```typescript
4599
+ * // When validation fails, throws DatabaseError with message:
4600
+ * // "Configuration validation failed:
4601
+ * // provider: Invalid provider: invalid_type. Must be one of: memory, file, redis, api, database
4602
+ * // cacheTtl: Cache TTL must be non-negative
4603
+ * // databaseConfig.connectionString: Database connection string is required"
4604
+ * ```
4605
+ *
4606
+ * @example Environment Variable Validation
4607
+ * ```bash
4608
+ * # Missing required environment variables:
4609
+ * # SUPABASE_URL= # Empty/missing
4610
+ * # FEATURE_FLAG_PROVIDER=database
4611
+ *
4612
+ * # Results in error:
4613
+ * # "Configuration validation failed:
4614
+ * # databaseConfig.connectionString: Database connection string is required"
4615
+ * ```
4616
+ */
4617
+ static validateOrThrow(config$1) {
4618
+ this.validateProvider(config$1);
4619
+ this.validateCacheSettings(config$1);
4620
+ if (config$1.provider === config.FEATURE_FLAG_PROVIDERS.DATABASE) {
4621
+ this.validateDatabaseConfig(config$1);
4622
+ }
4623
+ }
4624
+ };
4625
+ var FeatureFlagConfigFactory = class {
4626
+ static {
4627
+ __name(this, "FeatureFlagConfigFactory");
4628
+ }
4629
+ /**
4630
+ * Creates configuration from environment variables
4631
+ *
4632
+ * **EXECUTION ORDER: This is called FIRST in the initialization chain**
4633
+ *
4634
+ * Flow:
4635
+ * 1. NestJS starts → FeatureFlagModule loads
4636
+ * 2. FeatureFlagService.onModuleInit() called
4637
+ * 3. → initializeProvider() called
4638
+ * 4. → **THIS METHOD CALLED** ← YOU ARE HERE
4639
+ * 5. → Configuration validated
4640
+ * 6. → Provider created and initialized
4641
+ * 7. → Database connection established
4642
+ *
4643
+ * @returns Validated configuration object ready for provider creation
4644
+ * @throws {FeatureFlagConfigError} If environment variables are missing or invalid
4645
+ *
4646
+ * @example
4647
+ * ```typescript
4648
+ * // This method reads from process.env and creates:
4649
+ * {
4650
+ * provider: 'database',
4651
+ * isCacheEnabled: true,
4652
+ * cacheTtl: 300,
4653
+ * databaseConfig: {
4654
+ * connectionString: 'https://your-project.supabase.co',
4655
+ * tableName: 'feature_flags',
4656
+ * poolSize: 10,
4657
+ * timeout: 30000
4658
+ * }
4659
+ * }
4660
+ * ```
4661
+ */
4662
+ static fromEnvironment() {
4663
+ const config = {
4664
+ provider: this.getProvider(),
4665
+ isCacheEnabled: this.getCacheEnabled(),
4666
+ cacheTtl: this.getCacheTtl(),
4667
+ refreshInterval: this.getRefreshInterval(),
4668
+ shouldFallbackToDefaults: true,
4669
+ isLoggingEnabled: this.getLoggingEnabled()
4670
+ };
4671
+ if (config.provider === types.FEATURE_FLAG_PROVIDERS.DATABASE) {
4672
+ config.databaseConfig = this.getDatabaseConfig();
4673
+ }
4674
+ FeatureFlagConfigValidator.validateOrThrow(config);
4675
+ return config;
4676
+ }
4677
+ /**
4678
+ * Determines which provider to use - now uses constants instead of environment variables
4679
+ *
4680
+ * @private
4681
+ * @returns Provider type from constants
4682
+ */
4683
+ static getProvider() {
4684
+ return types.FEATURE_FLAG_PROVIDERS.MEMORY;
4685
+ }
4686
+ static getCacheEnabled() {
4687
+ return true;
4688
+ }
4689
+ static getCacheTtl() {
4690
+ return types.FEATURE_FLAG_DEFAULTS.CACHE_TTL;
4691
+ }
4692
+ static getRefreshInterval() {
4693
+ return types.FEATURE_FLAG_DEFAULTS.REFRESH_INTERVAL;
4694
+ }
4695
+ static getLoggingEnabled() {
4696
+ return process.env.NODE_ENV === types.NODE_ENVIRONMENTS.DEVELOPMENT || process.env.FEATURE_FLAG_LOGGING === "true";
4697
+ }
4698
+ /**
4699
+ * Creates database configuration from environment variables
4700
+ *
4701
+ * **CRITICAL**: This method requires SUPABASE_URL to be set in .env.local
4702
+ *
4703
+ * @private
4704
+ * @returns Database configuration object
4705
+ * @throws {FeatureFlagConfigError} If SUPABASE_URL is missing
4706
+ *
4707
+ * @example Environment Variables Required:
4708
+ * ```bash
4709
+ * SUPABASE_URL=https://your-project.supabase.co # REQUIRED
4710
+ * SUPABASE_ANON_PUBLIC_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... # Used in connection
4711
+ * FEATURE_FLAG_TABLE_NAME=feature_flags # Optional, defaults to 'feature_flags'
4712
+ * DB_POOL_SIZE=10 # Optional, defaults to 10
4713
+ * DB_TIMEOUT=30000 # Optional, defaults to 30000ms
4714
+ * ```
4715
+ *
4716
+ * @example Generated Configuration:
4717
+ * ```typescript
4718
+ * {
4719
+ * connectionString: 'https://your-project.supabase.co',
4720
+ * tableName: 'feature_flags',
4721
+ * poolSize: 10,
4722
+ * timeout: 30000
4723
+ * }
4724
+ * ```
4725
+ */
4726
+ static getDatabaseConfig() {
4727
+ const connectionString = process.env.SUPABASE_URL;
4728
+ if (!connectionString) {
4729
+ throw new errors.DatabaseError(
4730
+ "SUPABASE_URL is required for database provider",
4731
+ types.DATABASE_ERROR_CODES.CONFIG_REQUIRED
3234
4732
  );
3235
4733
  }
4734
+ return {
4735
+ connectionString,
4736
+ tableName: types.FEATURE_FLAG_DEFAULTS.TABLE_NAME,
4737
+ poolSize: types.FEATURE_FLAG_DEFAULTS.POOL_SIZE,
4738
+ timeout: types.FEATURE_FLAG_DEFAULTS.TIMEOUT
4739
+ };
3236
4740
  }
3237
4741
  };
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);
4742
+
4743
+ // src/backend/featureFlags/feature-flag.service.ts
3289
4744
  exports.FeatureFlagService = class FeatureFlagService {
3290
4745
  constructor(featureFlagRepository) {
3291
4746
  this.featureFlagRepository = featureFlagRepository;
@@ -3293,7 +4748,35 @@ exports.FeatureFlagService = class FeatureFlagService {
3293
4748
  logger = new common.Logger(exports.FeatureFlagService.name);
3294
4749
  provider;
3295
4750
  /**
3296
- * Initializes the service on module startup.
4751
+ * **FIRST METHOD CALLED** - NestJS lifecycle hook for module initialization
4752
+ *
4753
+ * **EXECUTION ORDER:**
4754
+ * 1. NestJS creates FeatureFlagModule
4755
+ * 2. NestJS instantiates FeatureFlagService
4756
+ * 3. **THIS METHOD CALLED AUTOMATICALLY** ← YOU ARE HERE
4757
+ * 4. → initializeProvider() called
4758
+ * 5. → Configuration loaded from .env.local
4759
+ * 6. → Database provider created and initialized
4760
+ * 7. → Database connection established
4761
+ * 8. → Initial data loaded from database
4762
+ * 9. System ready to serve requests
4763
+ *
4764
+ * @throws {Error} If provider initialization fails
4765
+ *
4766
+ * @example What happens during initialization:
4767
+ * ```typescript
4768
+ * // 1. Load config from environment
4769
+ * const config = FeatureFlagConfigFactory.fromEnvironment();
4770
+ *
4771
+ * // 2. Create database provider
4772
+ * this.provider = FeatureFlagProviderFactory.create(config, FEATURES);
4773
+ *
4774
+ * // 3. Initialize database connection
4775
+ * await this.provider.initialize();
4776
+ *
4777
+ * // 4. Load flags and rules from database
4778
+ * const { flags, rules } = await this.provider.fetchData();
4779
+ * ```
3297
4780
  */
3298
4781
  async onModuleInit() {
3299
4782
  try {
@@ -3312,36 +4795,115 @@ exports.FeatureFlagService = class FeatureFlagService {
3312
4795
  this.logger.log("Feature flag service disposed");
3313
4796
  }
3314
4797
  /**
3315
- * Initializes the feature flag provider.
4798
+ * **SECOND METHOD CALLED** - Initializes the feature flag provider
4799
+ *
4800
+ * **EXECUTION FLOW:**
4801
+ * 1. onModuleInit() called by NestJS
4802
+ * 2. **THIS METHOD CALLED** ← YOU ARE HERE
4803
+ * 3. → FeatureFlagConfigFactory.fromEnvironment() - Loads .env.local
4804
+ * 4. → FeatureFlagProviderFactory.create() - Creates database provider
4805
+ * 5. → provider.initialize() - Establishes database connection
4806
+ *
4807
+ * @private
4808
+ * @throws {Error} If configuration is invalid or database connection fails
4809
+ *
4810
+ * @example Configuration Loading Process:
4811
+ * ```typescript
4812
+ * // Reads from .env.local:
4813
+ * // SUPABASE_URL=https://your-project.supabase.co
4814
+ * // FEATURE_FLAG_PROVIDER=database
4815
+ * // FEATURE_FLAG_CACHE_ENABLED=true
4816
+ *
4817
+ * const config = {
4818
+ * provider: 'database',
4819
+ * isCacheEnabled: true,
4820
+ * databaseConfig: {
4821
+ * connectionString: 'https://your-project.supabase.co',
4822
+ * tableName: 'feature_flags'
4823
+ * }
4824
+ * };
4825
+ * ```
3316
4826
  */
3317
4827
  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();
4828
+ try {
4829
+ const config$1 = FeatureFlagConfigFactory.fromEnvironment();
4830
+ this.provider = FeatureFlagProviderFactory.create(config$1, config.FEATURES);
4831
+ await this.provider.initialize();
4832
+ this.logger.log(`Feature flag provider initialized: ${config$1.provider}`);
4833
+ } catch (error) {
4834
+ this.logger.error("Failed to initialize feature flag provider", error);
4835
+ throw new errors.BaseError(
4836
+ types.ERROR_CODES.CLIENT_INITIALIZATION_FAILED,
4837
+ types.HTTP_STATUS.INTERNAL_SERVER_ERROR,
4838
+ "Failed to initialize feature flag provider"
4839
+ );
4840
+ }
3329
4841
  }
3330
4842
  /**
3331
4843
  * Gets the current provider instance.
3332
4844
  */
3333
4845
  getProvider() {
3334
4846
  if (!this.provider) {
3335
- throw new Error("Feature flag provider not initialized");
4847
+ throw new errors.BaseError(
4848
+ types.ERROR_CODES.ERROR_SYSTEM_NOT_INITIALIZED,
4849
+ types.HTTP_STATUS.INTERNAL_SERVER_ERROR,
4850
+ "Feature flag provider not initialized"
4851
+ );
3336
4852
  }
3337
4853
  return this.provider;
3338
4854
  }
3339
4855
  /**
3340
- * Evaluates a feature flag for the given context.
4856
+ * **MAIN RUNTIME METHOD** - Evaluates a feature flag for the given context
3341
4857
  *
3342
- * @param key - Feature flag key
3343
- * @param context - Evaluation context
3344
- * @returns Feature flag evaluation result
4858
+ * **RUNTIME EXECUTION FLOW:**
4859
+ * 1. Client makes HTTP request to controller
4860
+ * 2. Controller calls **THIS METHOD** ← RUNTIME ENTRY POINT
4861
+ * 3. → getProvider() - Gets initialized provider
4862
+ * 4. → provider.getFlag() - Evaluates flag with context
4863
+ * 5. → Provider checks cache, rules, overrides
4864
+ * 6. → Database query if needed
4865
+ * 7. ← Returns evaluation result
4866
+ *
4867
+ * @param key - Feature flag key (e.g., 'PREMIUM_FEATURE', 'NEW_UI')
4868
+ * @param context - Evaluation context for targeting rules
4869
+ * @returns Feature flag evaluation result with value, reason, and metadata
4870
+ *
4871
+ * @example Simple Usage:
4872
+ * ```typescript
4873
+ * const evaluation = await this.featureFlagService.evaluateFlag('NEW_CHECKOUT');
4874
+ * console.log(evaluation.isEnabled); // true/false
4875
+ * console.log(evaluation.reason); // 'default_value' | 'rule_match' | 'override'
4876
+ * ```
4877
+ *
4878
+ * @example With User Context (for targeting rules):
4879
+ * ```typescript
4880
+ * const evaluation = await this.featureFlagService.evaluateFlag('BETA_FEATURE', {
4881
+ * userId: 'user123',
4882
+ * userRole: 'premium',
4883
+ * environment: 'production',
4884
+ * customAttributes: {
4885
+ * subscriptionTier: 'pro',
4886
+ * region: 'us-east'
4887
+ * }
4888
+ * });
4889
+ *
4890
+ * // Provider will:
4891
+ * // 1. Check for user-specific overrides
4892
+ * // 2. Evaluate targeting rules against context
4893
+ * // 3. Return appropriate value with reason
4894
+ * ```
4895
+ *
4896
+ * @example Evaluation Result:
4897
+ * ```typescript
4898
+ * {
4899
+ * key: 'BETA_FEATURE',
4900
+ * isEnabled: true,
4901
+ * value: { enabled: true, variant: 'blue' },
4902
+ * reason: 'rule_match',
4903
+ * ruleId: 'premium-users-rule',
4904
+ * evaluatedAt: '2024-01-15T10:30:00Z'
4905
+ * }
4906
+ * ```
3345
4907
  */
3346
4908
  async evaluateFlag(key, context) {
3347
4909
  try {
@@ -3520,10 +5082,8 @@ exports.FeatureFlagService = class FeatureFlagService {
3520
5082
  async getHealthStatus() {
3521
5083
  return {
3522
5084
  isInitialized: !!this.provider,
3523
- provider: "database",
3524
- // or get from config
5085
+ provider: types.FEATURE_FLAG_PROVIDERS.DATABASE,
3525
5086
  isCacheEnabled: true
3526
- // or get from config
3527
5087
  };
3528
5088
  }
3529
5089
  };
@@ -3762,8 +5322,8 @@ exports.FeatureFlagModule = class FeatureFlagModule {
3762
5322
  * @Module({
3763
5323
  * imports: [
3764
5324
  * FeatureFlagModule.forRoot({
3765
- * provider: 'redis',
3766
- * cacheEnabled: true,
5325
+ * provider: 'database',
5326
+ * isCacheEnabled: true,
3767
5327
  * cacheTtl: 600,
3768
5328
  * })
3769
5329
  * ],
@@ -3772,12 +5332,14 @@ exports.FeatureFlagModule = class FeatureFlagModule {
3772
5332
  * ```
3773
5333
  */
3774
5334
  static forRoot(options) {
5335
+ const config = FeatureFlagConfigFactory.fromEnvironment();
5336
+ const mergedConfig = { ...config, ...options };
3775
5337
  return {
3776
5338
  module: exports.FeatureFlagModule,
3777
5339
  providers: [
3778
5340
  {
3779
5341
  provide: "FEATURE_FLAG_CONFIG",
3780
- useValue: options ?? {}
5342
+ useValue: mergedConfig
3781
5343
  },
3782
5344
  exports.FeatureFlagService,
3783
5345
  exports.FeatureFlagRepository
@@ -3835,20 +5397,193 @@ exports.FeatureFlagModule = __decorateClass([
3835
5397
  exports: [exports.FeatureFlagService, exports.FeatureFlagRepository]
3836
5398
  })
3837
5399
  ], exports.FeatureFlagModule);
5400
+ function FeatureFlag(key, expected = true) {
5401
+ return common.SetMetadata(types.FEATURE_FLAG_METADATA.FLAG_CHECK, { key, expected });
5402
+ }
5403
+ __name(FeatureFlag, "FeatureFlag");
3838
5404
 
3839
- // src/backend/featureFlags/index.ts
3840
- function FeatureFlagGuard() {
3841
- return function(_target, _propertyName, descriptor) {
3842
- return descriptor;
3843
- };
5405
+ // src/backend/featureFlags/decorators/feature-disabled.decorator.ts
5406
+ function FeatureDisabled(key) {
5407
+ return FeatureFlag(key, false);
3844
5408
  }
3845
- __name(FeatureFlagGuard, "FeatureFlagGuard");
3846
- function FeatureFlag() {
3847
- return function(_target, _propertyName, descriptor) {
3848
- return descriptor;
3849
- };
5409
+ __name(FeatureDisabled, "FeatureDisabled");
5410
+
5411
+ // src/backend/featureFlags/decorators/feature-enabled.decorator.ts
5412
+ function FeatureEnabled(key) {
5413
+ return FeatureFlag(key, true);
3850
5414
  }
3851
- __name(FeatureFlag, "FeatureFlag");
5415
+ __name(FeatureEnabled, "FeatureEnabled");
5416
+ exports.FeatureFlagGuard = class FeatureFlagGuard {
5417
+ constructor(reflector, featureFlagService) {
5418
+ this.reflector = reflector;
5419
+ this.featureFlagService = featureFlagService;
5420
+ }
5421
+ logger = new common.Logger(exports.FeatureFlagGuard.name);
5422
+ async canActivate(context) {
5423
+ const meta = this.reflector.get(
5424
+ types.FEATURE_FLAG_METADATA.FLAG_CHECK,
5425
+ context.getHandler()
5426
+ // to get keys
5427
+ );
5428
+ if (!meta) return true;
5429
+ const { key, expected } = meta;
5430
+ const evaluation = await this.featureFlagService.evaluateFlag(key);
5431
+ const actual = evaluation.value ?? evaluation.isEnabled;
5432
+ if (!this.matches(expected, actual)) {
5433
+ this.logger.log(
5434
+ `FeatureFlagGuard denied: key=${key}, expected=${expected}, actual=${actual}`
5435
+ );
5436
+ throw new errors.BaseError(
5437
+ types.ERROR_CODES.AUTH_FORBIDDEN,
5438
+ types.HTTP_STATUS.FORBIDDEN,
5439
+ `Feature ${key} not in required state`
5440
+ );
5441
+ }
5442
+ return true;
5443
+ }
5444
+ /**
5445
+ * Compare expected vs actual values.
5446
+ */
5447
+ matches(expected, actual) {
5448
+ if (expected === void 0 || expected === null) return Boolean(actual);
5449
+ if (typeof actual !== "object" || actual === null) {
5450
+ return String(actual) === String(expected);
5451
+ }
5452
+ return JSON.stringify(actual) === JSON.stringify(expected);
5453
+ }
5454
+ };
5455
+ __name(exports.FeatureFlagGuard, "FeatureFlagGuard");
5456
+ exports.FeatureFlagGuard = __decorateClass([
5457
+ common.Injectable()
5458
+ ], exports.FeatureFlagGuard);
5459
+ function isFeatureFlagKey(value) {
5460
+ return Object.keys(config.FEATURES).includes(value);
5461
+ }
5462
+ __name(isFeatureFlagKey, "isFeatureFlagKey");
5463
+ exports.FeatureFlagMiddleware = class FeatureFlagMiddleware {
5464
+ constructor(featureFlagService) {
5465
+ this.featureFlagService = featureFlagService;
5466
+ }
5467
+ logger = new common.Logger(exports.FeatureFlagMiddleware.name);
5468
+ /**
5469
+ * Main middleware execution method
5470
+ *
5471
+ * @description Processes incoming requests to evaluate and attach feature flags.
5472
+ * Handles flag key extraction from multiple sources and graceful error handling.
5473
+ *
5474
+ * @param {FeatureFlagRequest} req - Express request object with feature flag extensions
5475
+ * @param {unknown} res - Express response object (not used in this middleware)
5476
+ * @param {Function} next - Next middleware function in the chain
5477
+ *
5478
+ * @throws {BaseError} When flag key is invalid or evaluation fails
5479
+ *
5480
+ * @example Request Processing Flow
5481
+ * ```typescript
5482
+ * // 1. Extract flag key from request
5483
+ * const flagKey = req.query.flag || req.headers['x-feature-flag'] || 'AUTH_GOOGLE';
5484
+ *
5485
+ * // 2. Validate flag key exists in system
5486
+ * if (!isFeatureFlagKey(flagKey)) {
5487
+ * throw new Error('Invalid flag key');
5488
+ * }
5489
+ *
5490
+ * // 3. Evaluate flag using service
5491
+ * const evaluation = await this.featureFlagService.evaluateFlag(flagKey);
5492
+ *
5493
+ * // 4. Attach to request object
5494
+ * req.featureFlags = { [flagKey]: evaluation.isEnabled };
5495
+ *
5496
+ * // 5. Continue to next middleware
5497
+ * next();
5498
+ * ```
5499
+ */
5500
+ // Use a simple next type to avoid conflicts with differing framework types across environments
5501
+ async use(req, res, next) {
5502
+ try {
5503
+ const flagKey = (
5504
+ // Prefer query param -> header -> default
5505
+ (req.query instanceof URLSearchParams ? req.query.get("flag") : req.query?.["flag"]) ?? req.headers?.["x-feature-flag"] ?? "AUTH_GOOGLE"
5506
+ );
5507
+ if (!isFeatureFlagKey(flagKey)) {
5508
+ throw new errors.BaseError(
5509
+ types.ERROR_CODES.RETRY_FAILED,
5510
+ config.HTTP_STATUS.NOT_FOUND,
5511
+ `Invalid feature flag key: ${flagKey}`
5512
+ );
5513
+ }
5514
+ const flagEvaluation = await this.featureFlagService.evaluateFlag(flagKey);
5515
+ req.featureFlags = {
5516
+ ...req.featureFlags,
5517
+ [flagKey]: flagEvaluation.isEnabled
5518
+ };
5519
+ this.logger.debug(`Feature flags attached to request: ${JSON.stringify(req.featureFlags)}`);
5520
+ next();
5521
+ } catch (error) {
5522
+ this.logger.error("Error evaluating feature flags", error);
5523
+ throw new errors.BaseError(
5524
+ types.ERROR_CODES.RETRY_FAILED,
5525
+ config.HTTP_STATUS.SERVICE_UNAVAILABLE,
5526
+ "Failed to evaluate feature flag"
5527
+ );
5528
+ }
5529
+ }
5530
+ };
5531
+ __name(exports.FeatureFlagMiddleware, "FeatureFlagMiddleware");
5532
+ exports.FeatureFlagMiddleware = __decorateClass([
5533
+ common.Injectable()
5534
+ ], exports.FeatureFlagMiddleware);
5535
+ exports.FeatureFlagLoggingInterceptor = class FeatureFlagLoggingInterceptor {
5536
+ logger = new common.Logger(exports.FeatureFlagLoggingInterceptor.name);
5537
+ intercept(context, next) {
5538
+ const request = context.switchToHttp().getRequest();
5539
+ const path2 = request.url;
5540
+ const flags = request.featureFlags ?? {};
5541
+ const featureToTrack = request.headers?.["x-feature-flag"];
5542
+ if (featureToTrack && flags[featureToTrack]) {
5543
+ this.trackFeatureUsage(featureToTrack, path2);
5544
+ } else {
5545
+ for (const [flag, enabled] of Object.entries(flags)) {
5546
+ if (enabled) this.trackFeatureUsage(flag, path2);
5547
+ }
5548
+ }
5549
+ return next.handle().pipe(
5550
+ rxjs.tap(() => {
5551
+ this.logger.log(`Completed request: ${path2}`);
5552
+ })
5553
+ );
5554
+ }
5555
+ trackFeatureUsage(flag, path2) {
5556
+ this.logger.log(`Feature "${flag}" used on endpoint "${path2}"`);
5557
+ }
5558
+ };
5559
+ __name(exports.FeatureFlagLoggingInterceptor, "FeatureFlagLoggingInterceptor");
5560
+ exports.FeatureFlagLoggingInterceptor = __decorateClass([
5561
+ common.Injectable()
5562
+ ], exports.FeatureFlagLoggingInterceptor);
5563
+ exports.ErrorHandlingInterceptor = class ErrorHandlingInterceptor {
5564
+ logger = new common.Logger(exports.ErrorHandlingInterceptor.name);
5565
+ intercept(context, next) {
5566
+ const req = context.switchToHttp().getRequest();
5567
+ const path2 = req.url;
5568
+ const flags = req.featureFlags ?? {};
5569
+ const featureToCheck = req.headers?.["x-feature-flag"] ?? "GLOBAL_FEATURE";
5570
+ if (!flags[featureToCheck]) {
5571
+ this.logger.warn(`Request to ${path2} blocked: ${featureToCheck} is disabled`);
5572
+ throw new errors.BaseError("RETRY_FAILED", types.HTTP_STATUS.FORBIDDEN);
5573
+ }
5574
+ this.logger.debug(`Request to ${path2} allowed: ${featureToCheck} is enabled`);
5575
+ return next.handle().pipe(
5576
+ operators.catchError((error) => {
5577
+ this.logger.error(`Error processing request to ${path2}`, error);
5578
+ throw new errors.BaseError("INTERNAL_SERVER_ERROR", types.HTTP_STATUS.INTERNAL_SERVER_ERROR, error);
5579
+ })
5580
+ );
5581
+ }
5582
+ };
5583
+ __name(exports.ErrorHandlingInterceptor, "ErrorHandlingInterceptor");
5584
+ exports.ErrorHandlingInterceptor = __decorateClass([
5585
+ common.Injectable()
5586
+ ], exports.ErrorHandlingInterceptor);
3852
5587
  var FeatureFlagContext = React.createContext(
3853
5588
  null
3854
5589
  );
@@ -4334,19 +6069,330 @@ function useFeatureFlagHelpers() {
4334
6069
  );
4335
6070
  }
4336
6071
  __name(useFeatureFlagHelpers, "useFeatureFlagHelpers");
6072
+ var MIN_RETRY_ATTEMPTS_PRODUCTION = 3;
6073
+ function getConfigForEnvironment(env) {
6074
+ switch (env) {
6075
+ case "production":
6076
+ return config.PRODUCTION_CONFIG;
6077
+ case "staging":
6078
+ return config.STAGING_CONFIG;
6079
+ case "development":
6080
+ case "test":
6081
+ default:
6082
+ return config.DEVELOPMENT_CONFIG;
6083
+ }
6084
+ }
6085
+ __name(getConfigForEnvironment, "getConfigForEnvironment");
6086
+ function validateBaseURL(mergedConfig, errors) {
6087
+ if (!mergedConfig.baseURL) {
6088
+ errors.push("baseURL is required in API configuration (apiConfig parameter)");
6089
+ }
6090
+ }
6091
+ __name(validateBaseURL, "validateBaseURL");
6092
+ function validateProductionEncryption(mergedConfig, errors, warnings) {
6093
+ if (mergedConfig.encryption?.enabled) {
6094
+ if (!mergedConfig.encryption?.key) {
6095
+ errors.push(
6096
+ 'encryption.key is REQUIRED when encryption is enabled in production. Pass it in apiConfig: { encryption: { key: { id: "prod-key-v1", key: process.env.ENCRYPTION_KEY!, algorithm: "AES-GCM", format: "raw" } } }'
6097
+ );
6098
+ }
6099
+ } else {
6100
+ warnings.push(
6101
+ "[SECURITY WARNING] Encryption is disabled in production. This is not recommended for handling sensitive data (PII, payment info, etc.)."
6102
+ );
6103
+ }
6104
+ }
6105
+ __name(validateProductionEncryption, "validateProductionEncryption");
6106
+ function validateProductionPerformance(mergedConfig, warnings) {
6107
+ if (!mergedConfig.networkAware?.enabled) {
6108
+ warnings.push(
6109
+ "[PERFORMANCE WARNING] networkAware is disabled in production. Enable it for better user experience on varying network conditions."
6110
+ );
6111
+ }
6112
+ if (!mergedConfig.tracking?.telemetry) {
6113
+ warnings.push(
6114
+ "[MONITORING WARNING] telemetry is disabled in production. Enable it for production monitoring and alerting."
6115
+ );
6116
+ }
6117
+ if (mergedConfig.retry && mergedConfig.retry.attempts !== void 0 && mergedConfig.retry.attempts < MIN_RETRY_ATTEMPTS_PRODUCTION) {
6118
+ warnings.push(
6119
+ `[RELIABILITY WARNING] Only ${mergedConfig.retry.attempts} retry attempts configured. Consider increasing to 3-5 for better reliability in production.`
6120
+ );
6121
+ }
6122
+ }
6123
+ __name(validateProductionPerformance, "validateProductionPerformance");
6124
+ function validateStagingEncryption(mergedConfig, errors, warnings) {
6125
+ if (mergedConfig.encryption?.enabled) {
6126
+ if (!mergedConfig.encryption?.key) {
6127
+ errors.push(
6128
+ 'encryption.key is REQUIRED in staging (aligned with production). Pass it in apiConfig: { encryption: { key: { id: "staging-key-v1", key: process.env.ENCRYPTION_KEY!, algorithm: "AES-GCM", format: "raw" } } }'
6129
+ );
6130
+ }
6131
+ } else {
6132
+ warnings.push(
6133
+ "[SECURITY WARNING] Encryption is disabled in staging. Staging should mirror production for accurate testing."
6134
+ );
6135
+ }
6136
+ if (!mergedConfig.tracking?.telemetry) {
6137
+ warnings.push(
6138
+ "[MONITORING WARNING] telemetry is disabled in staging. Enable it to test monitoring before production deployment."
6139
+ );
6140
+ }
6141
+ }
6142
+ __name(validateStagingEncryption, "validateStagingEncryption");
6143
+ function validateDevelopmentEncryption(mergedConfig, warnings) {
6144
+ if (mergedConfig.encryption?.enabled && !mergedConfig.encryption?.key) {
6145
+ warnings.push(
6146
+ "[DEV INFO] Encryption is enabled but no key provided in apiConfig. Encryption will be skipped in development (this is normal)."
6147
+ );
6148
+ }
6149
+ }
6150
+ __name(validateDevelopmentEncryption, "validateDevelopmentEncryption");
6151
+ function mapEnvironmentMetadata(envConfig, envDefaults) {
6152
+ const mapped = {};
6153
+ if (envConfig.apiKey) {
6154
+ const existingStatic = envDefaults.headers && typeof envDefaults.headers === "object" && "static" in envDefaults.headers && typeof envDefaults.headers.static === "object" ? envDefaults.headers.static : {};
6155
+ mapped.headers = {
6156
+ static: { ...existingStatic ?? {}, "X-API-Key": envConfig.apiKey }
6157
+ };
6158
+ }
6159
+ return mapped;
6160
+ }
6161
+ __name(mapEnvironmentMetadata, "mapEnvironmentMetadata");
6162
+ function applyDefaultClientSetting(client, envConfig) {
6163
+ const shouldSetAsDefault = envConfig.setAsDefault !== false;
6164
+ if (shouldSetAsDefault) {
6165
+ api.setDefaultApiClient(client);
6166
+ }
6167
+ }
6168
+ __name(applyDefaultClientSetting, "applyDefaultClientSetting");
6169
+ function validateEnvironmentConfig(envConfig, mergedConfig) {
6170
+ const errors = [];
6171
+ const warnings = [];
6172
+ validateBaseURL(mergedConfig, errors);
6173
+ if (envConfig.env === "production") {
6174
+ validateProductionEncryption(mergedConfig, errors, warnings);
6175
+ validateProductionPerformance(mergedConfig, warnings);
6176
+ } else if (envConfig.env === "staging") {
6177
+ validateStagingEncryption(mergedConfig, errors, warnings);
6178
+ } else if (envConfig.env === "development") {
6179
+ validateDevelopmentEncryption(mergedConfig, warnings);
6180
+ }
6181
+ if (warnings.length > 0) {
6182
+ globalThis.console.warn("[ApiClientService] Configuration warnings:", warnings);
6183
+ }
6184
+ if (errors.length > 0) {
6185
+ throw new api.ApiPackageError(
6186
+ "service.validation.failed",
6187
+ types.PACKAGE_STATUS_CODES.INVALID_CONFIGURATION,
6188
+ types.API_ERROR_CODES.CONFIG_VALIDATION_FAILED,
6189
+ {
6190
+ context: {
6191
+ operation: types.OPERATIONS.VALIDATION,
6192
+ // Ensure error details conform to ErrorDetail shape (include errorCode)
6193
+ errors: errors.map((err) => ({
6194
+ field: "config",
6195
+ message: err,
6196
+ errorCode: String(types.API_ERROR_CODES.CONFIG_VALIDATION_FAILED)
6197
+ })),
6198
+ i18n: {
6199
+ errors: errors.join("; "),
6200
+ warnings: warnings.join("; ")
6201
+ }
6202
+ }
6203
+ }
6204
+ );
6205
+ }
6206
+ }
6207
+ __name(validateEnvironmentConfig, "validateEnvironmentConfig");
6208
+ var ApiClientService = class {
6209
+ static {
6210
+ __name(this, "ApiClientService");
6211
+ }
6212
+ static instance = null;
6213
+ static isInitializing = false;
6214
+ static initPromise = null;
6215
+ /**
6216
+ * Initialize the API client with environment config and API options
6217
+ *
6218
+ * @param envConfig - Environment metadata (env, apiKey)
6219
+ * @param apiConfig - API configuration (baseURL, encryption, timeout, event handlers, etc.)
6220
+ * @returns Promise that resolves to the initialized client
6221
+ */
6222
+ static async init(envConfig, apiConfig) {
6223
+ if (this.instance) {
6224
+ globalThis.console.warn(
6225
+ "[ApiClientService] Client already initialized. Returning existing instance."
6226
+ );
6227
+ return this.instance;
6228
+ }
6229
+ if (this.isInitializing && this.initPromise) {
6230
+ await this.initPromise;
6231
+ return this.instance;
6232
+ }
6233
+ this.isInitializing = true;
6234
+ this.initPromise = this.createClient(envConfig, apiConfig);
6235
+ try {
6236
+ await this.initPromise;
6237
+ return this.instance;
6238
+ } finally {
6239
+ this.isInitializing = false;
6240
+ this.initPromise = null;
6241
+ }
6242
+ }
6243
+ /**
6244
+ * Internal initialization logic
6245
+ * Merges environment-specific defaults with API configuration
6246
+ *
6247
+ * Merge Priority (lowest to highest):
6248
+ * 1. Environment defaults (PRODUCTION_CONFIG / STAGING_CONFIG / DEVELOPMENT_CONFIG)
6249
+ * 2. Environment metadata (envConfig - apiKey)
6250
+ * 3. API configuration (apiConfig - baseURL, encryption, timeout, etc.)
6251
+ */
6252
+ static async createClient(envConfig, apiConfig) {
6253
+ try {
6254
+ const envDefaults = getConfigForEnvironment(envConfig.env);
6255
+ const envMetadataMapped = mapEnvironmentMetadata(envConfig, envDefaults);
6256
+ const mergedOptions = api.mergeConfigs(
6257
+ envDefaults,
6258
+ // Environment defaults (lowest priority)
6259
+ envMetadataMapped,
6260
+ // Environment metadata (medium priority)
6261
+ apiConfig ?? {}
6262
+ // API configuration (highest priority - includes baseURL, encryption, etc.)
6263
+ );
6264
+ validateEnvironmentConfig(envConfig, mergedOptions);
6265
+ this.instance = await api.createApiClient(mergedOptions);
6266
+ applyDefaultClientSetting(this.instance, envConfig);
6267
+ } catch (error) {
6268
+ throw new api.ApiPackageError(
6269
+ "service.initialization.failed",
6270
+ types.PACKAGE_STATUS_CODES.INITIALIZATION_FAILED,
6271
+ types.API_ERROR_CODES.CLIENT_INITIALIZATION_FAILED,
6272
+ {
6273
+ cause: error instanceof Error ? error : void 0,
6274
+ context: {
6275
+ operation: types.OPERATIONS.INITIALIZATION,
6276
+ originalError: error instanceof Error ? error.message : String(error),
6277
+ i18n: {
6278
+ error: error instanceof Error ? error.message : String(error)
6279
+ }
6280
+ }
6281
+ }
6282
+ );
6283
+ }
6284
+ }
6285
+ /**
6286
+ * Get the initialized client instance
6287
+ *
6288
+ * @throws {ApiPackageError} If client not initialized
6289
+ */
6290
+ static getClient() {
6291
+ if (!this.instance) {
6292
+ throw new api.ApiPackageError(
6293
+ "service.not_initialized",
6294
+ types.PACKAGE_STATUS_CODES.INITIALIZATION_FAILED,
6295
+ types.API_ERROR_CODES.CLIENT_INITIALIZATION_FAILED,
6296
+ {
6297
+ context: {
6298
+ operation: types.OPERATIONS.INITIALIZATION,
6299
+ i18n: {
6300
+ hint: "Call ApiClientService.init(envConfig, apiConfig) before accessing the client"
6301
+ }
6302
+ }
6303
+ }
6304
+ );
6305
+ }
6306
+ return this.instance;
6307
+ }
6308
+ /**
6309
+ * Check if client is initialized
6310
+ */
6311
+ static isInitialized() {
6312
+ return this.instance !== null;
6313
+ }
6314
+ /**
6315
+ * Reinitialize with new config and options
6316
+ */
6317
+ static async reinitialize(envConfig, apiConfig) {
6318
+ this.dispose();
6319
+ return this.init(envConfig, apiConfig);
6320
+ }
6321
+ /**
6322
+ * Dispose of the client instance
6323
+ */
6324
+ static dispose() {
6325
+ if (this.instance && "dispose" in this.instance) {
6326
+ this.instance.dispose?.();
6327
+ }
6328
+ this.instance = null;
6329
+ this.isInitializing = false;
6330
+ this.initPromise = null;
6331
+ }
6332
+ };
6333
+ var getApiClient = /* @__PURE__ */ __name(() => ApiClientService.getClient(), "getApiClient");
6334
+ var initApiClient = /* @__PURE__ */ __name((envConfig, apiConfig) => ApiClientService.init(envConfig, apiConfig), "initApiClient");
6335
+ function ApiProvider({
6336
+ children,
6337
+ envConfig,
6338
+ apiConfig,
6339
+ loadingComponent,
6340
+ errorComponent,
6341
+ onInitialized,
6342
+ onError
6343
+ }) {
6344
+ const [isReady, setIsReady] = React.useState(false);
6345
+ const [error, setError] = React.useState(null);
6346
+ React.useEffect(() => {
6347
+ ApiClientService.init(envConfig, apiConfig).then(() => {
6348
+ setIsReady(true);
6349
+ onInitialized?.();
6350
+ }).catch((err) => {
6351
+ const error2 = err instanceof Error ? err : new Error(String(err));
6352
+ setError(error2);
6353
+ onError?.(error2);
6354
+ globalThis.console.error("[ApiProvider] Failed to initialize API client:", error2);
6355
+ });
6356
+ return () => {
6357
+ };
6358
+ }, []);
6359
+ if (error) {
6360
+ if (errorComponent) {
6361
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: errorComponent(error) });
6362
+ }
6363
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "20px", color: "red" }, children: [
6364
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "API Client Initialization Failed" }),
6365
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: error.message })
6366
+ ] });
6367
+ }
6368
+ if (!isReady) {
6369
+ if (loadingComponent) {
6370
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent });
6371
+ }
6372
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "20px" }, children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Initializing API client..." }) });
6373
+ }
6374
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
6375
+ }
6376
+ __name(ApiProvider, "ApiProvider");
4337
6377
 
6378
+ exports.ApiClientService = ApiClientService;
4338
6379
  exports.ApiFeatureFlagProvider = ApiFeatureFlagProvider;
6380
+ exports.ApiProvider = ApiProvider;
4339
6381
  exports.CacheManager = CacheManager;
4340
6382
  exports.ConditionUtils = ConditionUtils;
4341
6383
  exports.ContextUtils = ContextUtils;
4342
6384
  exports.DEFAULT_FEATURE_FLAG_CONFIG = DEFAULT_FEATURE_FLAG_CONFIG;
6385
+ exports.DatabaseConnectionManager = DatabaseConnectionManager;
4343
6386
  exports.DatabaseFeatureFlagProvider = DatabaseFeatureFlagProvider;
4344
- exports.FeatureFlag = FeatureFlag;
6387
+ exports.FeatureDisabled = FeatureDisabled;
6388
+ exports.FeatureEnabled = FeatureEnabled;
4345
6389
  exports.FeatureFlagAppProvider = FeatureFlagAppProvider;
6390
+ exports.FeatureFlagConfigFactory = FeatureFlagConfigFactory;
6391
+ exports.FeatureFlagConfigValidator = FeatureFlagConfigValidator;
4346
6392
  exports.FeatureFlagContext = FeatureFlagContext;
4347
6393
  exports.FeatureFlagContextBuilder = FeatureFlagContextBuilder;
6394
+ exports.FeatureFlagDatabaseRepository = FeatureFlagDatabaseRepository;
4348
6395
  exports.FeatureFlagEngine = FeatureFlagEngine;
4349
- exports.FeatureFlagGuard = FeatureFlagGuard;
4350
6396
  exports.FeatureFlagProvider = FeatureFlagProvider;
4351
6397
  exports.FeatureFlagProviderFactory = FeatureFlagProviderFactory;
4352
6398
  exports.FeatureFlagSystem = FeatureFlagSystem;
@@ -4364,11 +6410,17 @@ exports.evaluateConditionOperator = evaluateConditionOperator;
4364
6410
  exports.evaluateEqualityOperator = evaluateEqualityOperator;
4365
6411
  exports.evaluateNumericOperator = evaluateNumericOperator;
4366
6412
  exports.evaluateStringOperator = evaluateStringOperator;
6413
+ exports.getApiClient = getApiClient;
6414
+ exports.getValidProviders = getValidProviders;
4367
6415
  exports.hashString = hashString;
6416
+ exports.initApiClient = initApiClient;
4368
6417
  exports.isArrayOperator = isArrayOperator;
6418
+ exports.isDefined = isDefined;
4369
6419
  exports.isEqualityOperator = isEqualityOperator;
4370
6420
  exports.isInRollout = isInRollout;
6421
+ exports.isNumber = isNumber;
4371
6422
  exports.isNumericOperator = isNumericOperator;
6423
+ exports.isString = isString;
4372
6424
  exports.isStringOperator = isStringOperator;
4373
6425
  exports.isTruthy = isTruthy;
4374
6426
  exports.toBoolean = toBoolean;