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