@canary-flags/core
v1.0.1
Published
A powerful feature flag and configuration management library for JavaScript/TypeScript applications. CanaryCore provides a flexible, event-driven system for managing feature flags, configuration values, and runtime overrides.
Readme
CanaryCore
A powerful feature flag and configuration management library for JavaScript/TypeScript applications. CanaryCore provides a flexible, event-driven system for managing feature flags, configuration values, and runtime overrides.
Features
- 🚀 Feature Flag Management - Enable/disable features dynamically
- ⚡ Runtime Overrides - Override values at runtime without restarts
- 📡 Event-Driven - Listen to configuration changes in real-time
- 🔄 Configuration Merging - Merge configurations from multiple sources
- 🎯 Type Safety - Full TypeScript support with type-safe values
- 🧪 Testing Support - Easy mocking and testing utilities
Installation
npm install @canary-flags/core
# or
yarn add @canary-flags/coreBasic Usage
Creating a CanaryCore Instance
import CanaryCore from '@canary-flags/core'
// Create an empty instance
const canary = new CanaryCore()
// Create with initial configuration
const canary = new CanaryCore([
{ name: 'new-feature', value: true },
{ name: 'beta-mode', value: false }
])
// Create with overrides
const canary = new CanaryCore(undefined, {
'new-feature': true,
'beta-mode': true
})Setting and Getting Values
// Assign a value to a feature
canary.assign('my-feature', true)
// Get a feature value
const isEnabled = canary.get('my-feature') // true
// Override a value (takes precedence over assigned value)
canary.assign('my-feature', false)
canary.override('my-feature', true)
const value = canary.get('my-feature') // true (override takes precedence)Configuration Management
// Set a complete feature configuration
canary.config('advanced-feature', {
name: 'advanced-feature',
value: 'enabled',
variants: ['disabled', 'enabled', 'premium'],
metadata: {
description: 'Advanced feature with multiple variants'
}
})
// Get the full feature configuration
const config = canary.getFeature('advanced-feature')
// Returns: { name: 'advanced-feature', value: 'enabled', variants: [...], metadata: {...} }
// Initialize multiple features at once
canary.initializeConfig([
{ name: 'feature-1', value: true },
{ name: 'feature-2', value: 'beta' },
{ name: 'feature-3', value: 42 }
])Advanced Features
Event System
CanaryCore is built on EventEmitter and provides real-time notifications for configuration changes:
// Listen to specific feature changes
canary.on('assign:my-feature', (value, instance) => {
console.log(`my-feature assigned to: ${value}`)
})
canary.on('override:my-feature', (value, instance) => {
console.log(`my-feature overridden to: ${value}`)
})
canary.on('config:my-feature', (config) => {
console.log('my-feature configuration updated:', config)
})
// Listen to any override
canary.on('override', (key, value, instance) => {
console.log(`Feature ${key} changed to: ${value}`)
})
// Listen to all changes
canary.on('change', (key, value, instance) => {
console.log(`Feature ${key} changed to: ${value}`)
})
// Listen to bulk changes
canary.on('change:bulk', (instance) => {
console.log('Multiple features changed')
})
// Listen to reset events
canary.on('reset', (instance) => {
console.log('All overrides have been reset')
})Runtime Overrides
// Override multiple values at once
canary.overrideValues({
'feature-1': true,
'feature-2': 'enabled',
'feature-3': 100
})
// Check if a feature has an override
const hasOverride = canary.hasOverride('my-feature') // boolean
// Reset all overrides
canary.reset()
// Check if a feature has an assignment
const hasAssignment = canary.hasAssignment('my-feature') // booleanMerging Configurations
// Create a child configuration
const childCore = new CanaryCore([
{ name: 'child-feature', value: true },
{ name: 'shared-feature', value: 'child-value' }
])
// Merge child into parent
canary.merge(childCore)
// Child values take precedence over existing valuesCustom Callbacks
// Set custom evaluation callback
canary.onEvaluateFlag = (key, config) => {
console.log(`Feature ${key} evaluated:`, config?.value)
// Custom logic here
}
// Set custom toggle callback
canary.toggle = () => {
console.log('Toggle called')
// Custom toggle logic
}
// Check if callbacks are set
const hasCallback = canary.hasOnEvaluateFlag // booleanData Types
CanaryCore supports various data types for feature values:
// Boolean flags
canary.assign('boolean-feature', true)
// String values
canary.assign('string-feature', 'enabled')
// Numeric values
canary.assign('numeric-feature', 42)
// Object values
canary.assign('object-feature', {
mode: 'advanced',
settings: { timeout: 5000 }
})
// Null and undefined
canary.assign('null-feature', null)
canary.assign('undefined-feature', undefined)Variants
Features can have predefined variants:
// Boolean variants (default)
canary.config('simple-flag', {
name: 'simple-flag',
value: true
// variants: [false, true] (default)
})
// String variants
canary.config('theme-feature', {
name: 'theme-feature',
value: 'dark',
variants: ['light', 'dark', 'auto']
})
// Numeric variants
canary.config('timeout-feature', {
name: 'timeout-feature',
value: 5000,
variants: [1000, 5000, 10000]
})Getters and State
// Get all current overrides
const overrides = canary.overrides
// Returns: { 'feature-1': true, 'feature-2': 'enabled' }
// Get all feature values (including overrides)
const selections = canary.selections
// Returns: { 'feature-1': true, 'feature-2': 'enabled', 'feature-3': false }
// Get all assignments (without overrides)
const assignments = canary._assignments
// Returns: [true, 'enabled', false]Error Handling
// Handle non-existent features gracefully
const value = canary.get('non-existent') // undefined
// Check if feature exists before using
if (canary.hasAssignment('my-feature')) {
const value = canary.get('my-feature')
// Use value safely
}Performance Considerations
- CanaryCore uses Map for O(1) feature lookups
- Event listeners are automatically cleaned up when calling
destroy() - Configuration merging is optimized to avoid unnecessary updates
- Memory usage is minimal for typical feature flag scenarios
API Reference
Constructor
new CanaryCore(configArray?: CanaryStoreEntry[], overrides?: Overrides)Core Methods
assign(key: string, value: CanaryValue): CanaryCoreoverride(key: string, value: CanaryValue): CanaryCoreget(key: string): CanaryValueconfig(key: string, entry: CanaryStoreEntry): CanaryCoregetFeature(key: string): CanaryStoreEntry | undefinedhasOverride(key: string): booleanhasAssignment(key: string): booleanreset(): CanaryCoremerge(childCore: CanaryCore): CanaryCoreclear(): voiddestroy(): void
Getters
overrides: Overridesselections: { [key: string]: CanaryValue }hasOnEvaluateFlag: boolean
Setters
onEvaluateFlag: (key: string, config?: CanaryStoreEntry) => voidtoggle: () => void
Events
assign:${key}- Emitted when a feature is assignedoverride:${key}- Emitted when a feature is overriddenconfig:${key}- Emitted when a feature configuration is updatedchange- Emitted for any feature changechange:bulk- Emitted for bulk changesreset- Emitted when all overrides are reset
TypeScript Types
interface CanaryStoreEntry {
name: string
value: CanaryValue
variants?: [true, false] | [false, true] | string[] | number[] | object[]
override?: CanaryValue
metadata?: {
description: string
}
}
type CanaryValue = boolean | string | number | object | null | undefined
interface Overrides {
[key: string]: CanaryValue
}License
MIT
