FOUNDRY
C8 Platform
← Tasks

[LIB-01] lib/config.ts — typed env config from app.json extra

completedcode_genP0

Description

## LIB-01: lib/config.ts — typed env config from app.json extra Section: Client Libraries | Milestone: M1 | Owner: AG Lu ### Failing Tests - **config_file** (file_exists): File `c8-audio/lib/config.ts` must exist - **livekit_url_config** (grep): Pattern `LIVEKIT_URL` in `c8-audio/lib/config.ts` (need 1+ matches) - **supabase_url_config** (grep): Pattern `SUPABASE_URL` in `c8-audio/lib/config.ts` (need 1+ matches) ### Acceptance Criteria - File `c8-audio/lib/config.ts` must exist - File matching `c8-audio/lib/config.ts` must contain pattern `LIVEKIT_URL` - File matching `c8-audio/lib/config.ts` must contain pattern `SUPABASE_URL` ### Codebase Conventions - Trilingual comments: // EN: ... // VI: ... // ES: ... - SPEC section refs in comments - Zod at module level with .safeParse() - (supabase as any) with eslint-disable - NextResponse.json() responses - Error format: { error: 'message' } ### Dependencies - SC-01: FAIL
Assignee
-
Claimed By
Cloud Lu → Claude Sonnet
Created
22d ago
Completed
19d ago

Quality

Overall
79%
Pass
PASS
Graded By
-
Graded At
-

Cost

Input Tokens
-
Output Tokens
-
Est. USD
-

Result

{
  "cost": {
    "input_tokens": 28,
    "estimated_usd": 0.032154,
    "output_tokens": 2138
  },
  "model": "claude-sonnet-4-20250514",
  "status": "success",
  "transport": "anthropic_api",
  "latency_ms": 28391,
  "response_text": "I'll help you create a typed environment configuration system that reads from `app.json`'s `extra` field. This is a common pattern in React Native/Expo apps for managing environment-specific settings.\n\n```typescript\n// lib/config.ts\n\nimport { ExpoConfig } from 'expo/config';\n\n// Define the structure of your environment configuration\ninterface EnvConfig {\n  // API Configuration\n  apiUrl: string;\n  apiTimeout: number;\n  \n  // Authentication\n  authClientId: string;\n  authRedirectUri: string;\n  \n  // Feature Flags\n  enableAnalytics: boolean;\n  enablePushNotifications: boolean;\n  debugMode: boolean;\n  \n  // Third-party Services\n  sentryDsn?: string;\n  amplitudeApiKey?: string;\n  \n  // App Configuration\n  appVersion: string;\n  buildNumber: string;\n  environment: 'development' | 'staging' | 'production';\n}\n\n// Default configuration values\nconst defaultConfig: Partial<EnvConfig> = {\n  apiTimeout: 10000,\n  enableAnalytics: true,\n  enablePushNotifications: true,\n  debugMode: false,\n  environment: 'development',\n};\n\n/**\n * Validates that required configuration values are present\n */\nfunction validateConfig(config: Partial<EnvConfig>): asserts config is EnvConfig {\n  const requiredFields: (keyof EnvConfig)[] = [\n    'apiUrl',\n    'authClientId',\n    'authRedirectUri',\n    'appVersion',\n    'buildNumber',\n    'environment',\n  ];\n\n  for (const field of requiredFields) {\n    if (!config[field]) {\n      throw new Error(`Missing required configuration field: ${field}`);\n    }\n  }\n\n  // Validate environment value\n  const validEnvironments = ['development', 'staging', 'production'];\n  if (!validEnvironments.includes(config.environment as string)) {\n    throw new Error(`Invalid environment: ${config.environment}. Must be one of: ${validEnvironments.join(', ')}`);\n  }\n}\n\n/**\n * Loads configuration from app.json extra field or expo config\n */\nfunction loadConfigFromExtra(): Partial<EnvConfig> {\n  try {\n    // In Expo/React Native, we can access the config through Constants\n    const Constants = require('expo-constants').default;\n    \n    if (!Constants.expoConfig?.extra) {\n      console.warn('No extra configuration found in app.json');\n      return {};\n    }\n\n    return Constants.expoConfig.extra as Partial<EnvConfig>;\n  } catch (error) {\n    console.error('Failed to load configuration from app.json:', error);\n    return {};\n  }\n}\n\n/**\n * Loads configuration from environment variables as fallback\n */\nfunction loadConfigFromEnv(): Partial<EnvConfig> {\n  return {\n    apiUrl: process.env.EXPO_PUBLIC_API_URL,\n    apiTimeout: process.env.EXPO_PUBLIC_API_TIMEOUT ? \n      parseInt(process.env.EXPO_PUBLIC_API_TIMEOUT, 10) : undefined,\n    \n    authClientId: process.env.EXPO_PUBLIC_AUTH_CLIENT_ID,\n    authRedirectUri: process.env.EXPO_PUBLIC_AUTH_REDIRECT_URI,\n    \n    enableAnalytics: process.env.EXPO_PUBLIC_ENABLE_ANALYTICS === 'true',\n    enablePushNotifications: process.env.EXPO_PUBLIC_ENABLE_PUSH_NOTIFICATIONS !== 'false',\n    debugMode: process.env.EXPO_PUBLIC_DEBUG_MODE === 'true',\n    \n    sentryDsn: process.env.EXPO_PUBLIC_SENTRY_DSN,\n    amplitudeApiKey: process.env.EXPO_PUBLIC_AMPLITUDE_API_KEY,\n    \n    appVersion: process.env.EXPO_PUBLIC_APP_VERSION,\n    buildNumber: process.env.EXPO_PUBLIC_BUILD_NUMBER,\n    environment: process.env.EXPO_PUBLIC_ENVIRONMENT as EnvConfig['environment'],\n  };\n}\n\n/**\n * Creates the final configuration by merging defaults, app.json extra, and environment variables\n */\nfunction createConfig(): EnvConfig {\n  // Merge configurations with precedence: env vars > app.json extra > defaults\n  const configFromExtra = loadConfigFromExtra();\n  const configFromEnv = loadConfigFromEnv();\n  \n  const mergedConfig = {\n    ...defaultConfig,\n    ...configFromExtra,\n    ...configFromEnv,\n  };\n\n  // Remove undefined values\n  const cleanConfig = Object.fromEntries(\n    Object.entries(mergedConfig).filter(([_, value]) => value !== undefined)\n  ) as Partial<EnvConfig>;\n\n  validateConfig(cleanConfig);\n  return cleanConfig;\n}\n\n// Export the configuration instance\nexport const config = createConfig();\n\n// Export types for external use\nexport type { EnvConfig };\n\n// Utility functions for common configuration checks\nexport const configUtils = {\n  /**\n   * Check if the app is running in development mode\n   */\n  isDevelopment: () => config.environment === 'development',\n  \n  /**\n   * Check if the app is running in production mode\n   */\n  isProduction: () => config.environment === 'production',\n  \n  /**\n   * Check if debug features should be enabled\n   */\n  isDebugEnabled: () => config.debugMode || config.environment === 'development',\n  \n  /**\n   * Get API configuration\n   */\n  getApiConfig: () => ({\n    baseURL: config.apiUrl,\n    timeout: config.apiTimeout,\n  }),\n  \n  /**\n   * Get authentication configuration\n   */\n  getAuthConfig: () => ({\n    clientId: config.authClientId,\n    redirectUri: config.authRedirectUri,\n  }),\n  \n  /**\n   * Check if a feature is enabled\n   */\n  isFeatureEnabled: (feature: keyof Pick<EnvConfig, 'enableAnalytics' | 'enablePushNotifications'>) => {\n    return config[feature];\n  },\n};\n\n// Development helper to log configuration (exclude sensitive data)\nif (configUtils.isDevelopment()) {\n  const safeConfig = {\n    ...config,\n    authClientId: config.authClientId ? '[REDACTED]' : undefined,\n    sentryDsn: config.sentryDsn ? '[REDACTED]' : undefined,\n    amplitudeApiKey: config.amplitudeApiKey ? '[REDACTED]' : undefined,\n  };\n  console.log('📱 App Configuration:', safeConfig);\n}\n```\n\nAnd here's an example of how your `app.json` should be structured:\n\n```json\n{\n  \"expo\": {\n    \"name\": \"Your App\",\n    \"slug\": \"your-app\",\n    \"version\": \"1.0.0\",\n    \"extra\": {\n      \"apiUrl\": \"https://api.yourapp.com\",\n      \"apiTimeout\": 15000,\n      \"authClientId\": \"your-auth-client-id\",\n      \"authRedirectUri\": \"yourapp://auth\",\n      \"enableAnalytics\": true,\n      \"enablePushNotifications\": true,\n      \"debugMode\": false,\n      \"sentryDsn\": \"https://your-sentry-dsn\",\n      \"amplitudeApiKey\": \"your-amplitude-key\",\n      \"appVersion\": \"1.0.0\",\n      \"buildNumber\": \"1\",\n      \"environment\": \"production\"\n    }\n  }\n}\n```\n\nExample usage in your app:\n\n```typescript\n// In your components or services\nimport { config, configUtils } from '@/lib/config';\n\n// Direct access to configuration\nconsole.log('API URL:', config.apiUrl);\nconsole.log('Debug mode:', config.debugMode);\n\n// Using utility functions\nif (configUtils.isProduction()) {\n  // Production-specific logic\n}\n\nif (configUtils.isFeatureEnabled('enableAnalytics')) {\n  // Initialize analytics\n}\n\n// Getting specific configurations\nconst apiConfig = configUtils.getApiConfig();\nconst authConfig = configUtils.getAuthConfig();\n```\n\nKey features of this implementation:\n\n1. **Type Safety**: Full TypeScript support with proper typing\n2. **Validation**: Ensures required configuration values are present\n3. **Fallback Support**: Uses environment variables as fallback\n4. **Utility Functions**: Convenient helpers for common configuration checks\n5. **Security**: Safely logs configuration without exposing sensitive data\n6. **Flexible**: Easy to extend with new configuration options\n7. **Environment-Aware**: Different configurations for different environments\n\nThis approach gives you a robust, type-safe configuration system that works well with Expo/React Native apps."
}
Task ID: 21d1176f-756a-418b-86c2-2746da434655