@varia-bly/variably-sdk
Version:
Official JavaScript/TypeScript SDK for Variably feature flags, experimentation, and real-time dynamic configurations
330 lines (278 loc) ⢠9.36 kB
text/typescript
/**
* Dynamic Configuration Client Usage Examples
*
* This example demonstrates how to use the Variably SDK's dynamic configuration
* client with real-time updates and intelligent fallback to polling.
*/
import {
DynamicConfigClient,
DynamicConfigClientConfig,
UserContext,
DynamicConfigResult,
ConfigChangeCallback
} from '../src/index';
// Example 1: Basic Usage with Real-time Updates
async function basicRealtimeExample() {
console.log('=== Basic Real-time Dynamic Config Example ===');
// Configuration for the client
const config: DynamicConfigClientConfig = {
apiKey: 'your-api-key-here',
jwtToken: 'your-jwt-token-here', // Required for WebSocket authentication
projectId: 'your-project-id',
baseUrl: 'https://graphql.variably.tech',
enableRealtime: true,
debug: true
};
// Create client
const configClient = new DynamicConfigClient(config);
// User context for configuration evaluation
const userContext: UserContext = {
userId: 'user-123',
email: 'user@example.com',
country: 'US',
attributes: {
plan: 'premium',
beta_user: true
}
};
try {
// Get a boolean configuration
const featureEnabled = await configClient.getConfigBool(
'new-dashboard-feature',
false,
userContext
);
console.log('New dashboard feature enabled:', featureEnabled);
// Get a JSON configuration
const themeConfig = await configClient.getConfigJSON(
'ui-theme-config',
{ theme: 'light', primaryColor: '#007bff' },
userContext
);
console.log('Theme configuration:', themeConfig);
// Get a string configuration
const apiEndpoint = await configClient.getConfigString(
'api-endpoint',
'https://api.example.com',
userContext
);
console.log('API endpoint:', apiEndpoint);
// Subscribe to real-time changes
const unsubscribe = configClient.onConfigChange('new-dashboard-feature', (result) => {
console.log('š Configuration changed!', {
key: result.key,
newValue: result.value,
version: result.version,
realTime: result.realTimeUpdate
});
});
// Simulate keeping the application running
console.log('ā
Client setup complete. Listening for real-time updates...');
// Clean up after 30 seconds (in real apps, this would be on app shutdown)
setTimeout(() => {
unsubscribe();
configClient.disconnect();
console.log('š Client disconnected');
}, 30000);
} catch (error) {
console.error('ā Error:', error);
}
}
// Example 2: Advanced Usage with Multiple Configurations
async function advancedExample() {
console.log('\n=== Advanced Dynamic Config Example ===');
const config: DynamicConfigClientConfig = {
apiKey: process.env.VARIABLY_API_KEY || 'your-api-key',
jwtToken: process.env.VARIABLY_JWT_TOKEN || 'your-jwt-token',
projectId: process.env.VARIABLY_PROJECT_ID || 'your-project-id',
baseUrl: process.env.VARIABLY_BASE_URL || 'https://graphql.variably.tech',
enableRealtime: true,
pollingInterval: 60000, // 60 second fallback polling
cache: {
ttl: 300000, // 5 minute cache
maxSize: 500,
enabled: true
},
debug: true
};
const configClient = new DynamicConfigClient(config);
const userContext: UserContext = {
userId: 'advanced-user-456',
email: 'advanced@example.com',
country: 'CA',
platform: 'web',
version: '2.1.0',
attributes: {
subscription: 'enterprise',
feature_flags: ['beta', 'experimental']
}
};
// Configuration keys we're interested in
const configKeys = [
'feature-limits',
'pricing-model',
'ui-customization',
'integration-settings',
'notification-preferences'
];
try {
// Get multiple configurations
console.log('š Loading configurations...');
const configurations = await Promise.all(
configKeys.map(async (key) => {
const result = await configClient.evaluateConfig(key, {}, userContext);
return { key, result };
})
);
configurations.forEach(({ key, result }) => {
console.log(` ā ${key}:`, {
value: result.value,
reason: result.reason,
cached: result.cacheHit,
version: result.version
});
});
// Subscribe to all configuration changes
const unsubscribeAll = configClient.onAnyConfigChange((result) => {
console.log('š Any config changed:', {
key: result.key,
value: result.value,
updateType: result.realTimeUpdate ? 'real-time' : 'polling',
version: result.version,
timestamp: result.retrievedAt
});
// Handle specific configuration changes
switch (result.key) {
case 'feature-limits':
console.log(' š§ Updating feature limits in application...');
break;
case 'ui-customization':
console.log(' šØ Applying new UI customization...');
break;
case 'pricing-model':
console.log(' š° Updating pricing display...');
break;
}
});
// Monitor connection status
const connectionStatus = configClient.getConnectionStatus();
console.log('š Connection status:', connectionStatus);
// Refresh configurations manually if needed
setTimeout(async () => {
console.log('š Manually refreshing configurations...');
await configClient.refreshConfigs(userContext);
}, 10000);
// Clean up
setTimeout(() => {
unsubscribeAll();
configClient.disconnect();
console.log('š Advanced client disconnected');
}, 45000);
} catch (error) {
console.error('ā Advanced example error:', error);
}
}
// Example 3: Polling-Only Mode (for environments without WebSocket support)
async function pollingOnlyExample() {
console.log('\n=== Polling-Only Mode Example ===');
const config: DynamicConfigClientConfig = {
apiKey: 'your-api-key',
projectId: 'your-project-id',
baseUrl: 'https://graphql.variably.tech',
enableRealtime: false, // Disable real-time updates
pollingInterval: 30000, // 30 second polling
debug: true
};
const configClient = new DynamicConfigClient(config);
const userContext: UserContext = {
userId: 'polling-user-789',
attributes: { environment: 'restricted-network' }
};
try {
// Get configuration (will use polling for updates)
const maintenanceMode = await configClient.getConfigBool(
'maintenance-mode',
false,
userContext
);
console.log('Maintenance mode:', maintenanceMode);
// Subscribe to changes (will be detected via polling)
const unsubscribe = configClient.onConfigChange('maintenance-mode', (result) => {
console.log('š Polling detected change:', {
key: result.key,
value: result.value,
reason: result.reason
});
});
console.log('ā
Polling-only client active. Checking for updates every 30 seconds...');
// Clean up
setTimeout(() => {
unsubscribe();
configClient.disconnect();
console.log('š Polling client disconnected');
}, 120000);
} catch (error) {
console.error('ā Polling example error:', error);
}
}
// Example 4: Error Handling and Resilience
async function errorHandlingExample() {
console.log('\n=== Error Handling Example ===');
const config: DynamicConfigClientConfig = {
apiKey: 'invalid-api-key', // Intentionally invalid
jwtToken: 'invalid-jwt-token',
projectId: 'test-project',
baseUrl: 'https://graphql.variably.tech',
enableRealtime: true,
debug: true
};
const configClient = new DynamicConfigClient(config);
const userContext: UserContext = {
userId: 'error-test-user'
};
try {
// This will fail due to invalid API key, but should return default value
const result = await configClient.evaluateConfig(
'test-config',
'default-value',
userContext
);
console.log('Result with error handling:', {
value: result.value,
reason: result.reason,
hasError: !!result.error,
errorMessage: result.error?.message
});
// The client should fall back to polling mode if WebSocket fails
const status = configClient.getConnectionStatus();
console.log('Connection status after error:', status);
} catch (error) {
console.error('ā Unexpected error (should be handled gracefully):', error);
}
configClient.disconnect();
}
// Run examples
async function runExamples() {
console.log('š Starting Variably Dynamic Config SDK Examples\n');
// Run examples in sequence
await basicRealtimeExample();
await new Promise(resolve => setTimeout(resolve, 2000));
await advancedExample();
await new Promise(resolve => setTimeout(resolve, 2000));
await pollingOnlyExample();
await new Promise(resolve => setTimeout(resolve, 2000));
await errorHandlingExample();
console.log('\nā
All examples completed!');
}
// Export examples for use in documentation or testing
export {
basicRealtimeExample,
advancedExample,
pollingOnlyExample,
errorHandlingExample,
runExamples
};
// Run if this file is executed directly
if (require.main === module) {
runExamples().catch(console.error);
}