Computed Flags API
Create derived flag values that automatically update when dependencies change.
compute()
Creates a computed flag that recalculates when its dependencies change.
Signature:
compute(key: string, dependencies: string[], fn: ComputeFn): void
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
key |
string |
Yes | The key for the computed flag |
dependencies |
string[] |
Yes | Array of flag keys this value depends on |
fn |
ComputeFn |
Yes | Function that computes the value from dependency values |
Returns: void
Throws:
ValidationError— If the key is invalidError— If the key is already used by a regular (non-computed) flagError— If a circular dependency is detected
Example:
store.set('base_damage', 10)
store.set('strength', 5)
store.compute('total_damage', ['base_damage', 'strength'], (baseDamage, strength) => {
return (baseDamage as number) + (strength as number) * 2
})
store.get('total_damage') // 20
// Automatically updates when dependencies change
store.set('strength', 10)
store.get('total_damage') // 30
Types
ComputeFn
type ComputeFn = (...values: Array<FlagValue | undefined>) => FlagValue
Function that computes a derived value. Receives dependency values in order:
// For compute('result', ['a', 'b', 'c'], fn)
// fn receives: (valueOfA, valueOfB, valueOfC)
Must return a valid FlagValue (boolean, number, or string).
Behavior Details
Read-Only
Computed flags cannot be modified directly:
store.compute('doubled', ['x'], (x) => (x as number) * 2)
store.set('doubled', 100) // throws Error
store.delete('doubled') // throws Error
store.toggle('doubled') // throws Error
store.increment('doubled') // throws Error
Synchronous Only
Compute functions must be synchronous. Async functions return a Promise object, not the resolved value:
// DON'T do this
store.compute('bad', ['x'], async (x) => {
return await someAsyncOperation(x)
})
store.get('bad') // Returns a Promise, not the value
Missing Dependencies
Dependencies that don't exist are passed as undefined:
store.compute('safe', ['missing'], (value) => {
return value ?? 'default'
})
store.get('safe') // 'default'
Error Handling
If the compute function throws, the error is caught and the previous value is retained:
store.compute('risky', ['x'], (x) => {
if (x === 0) throw new Error('Division by zero')
return 100 / (x as number)
})
store.set('x', 10)
store.get('risky') // 10
store.set('x', 0)
store.get('risky') // Still 10 (error was caught)
Circular Dependencies
Circular dependencies are detected at definition time:
store.compute('a', ['b'], (b) => b)
store.compute('b', ['a'], (a) => a) // throws Error: circular dependency
store.compute('self', ['self'], (s) => s) // throws Error: circular dependency
In Conditions
Computed flags work in check() like regular flags:
store.set('level', 10)
store.compute('can_access', ['level'], (level) => (level as number) >= 5)
store.check('can_access') // true
store.check('can_access AND level > 8') // true
With Subscriptions
Changes to computed flags trigger subscriptions:
store.set('x', 1)
store.compute('doubled', ['x'], (x) => (x as number) * 2)
store.subscribeKey('doubled', (newVal, oldVal) => {
console.log(`doubled: ${oldVal} -> ${newVal}`)
})
store.set('x', 5) // Logs: "doubled: 2 -> 10"
In keys() and all()
Computed flags appear in key lists and snapshots:
store.set('x', 10)
store.compute('doubled', ['x'], (x) => (x as number) * 2)
store.keys() // ['x', 'doubled']
store.has('doubled') // true
store.all() // { x: 10, doubled: 20 }
Persistence
Computed flags are not persisted (they're recalculated on load):
const store = createFlagStore({
persist: { storage: localStorage }
})
store.set('x', 10)
store.compute('doubled', ['x'], (x) => (x as number) * 2)
// After reload, 'x' is restored from storage
// 'doubled' must be recomputed (not stored)
Clear Behavior
clear() removes regular flags but not computed definitions:
store.set('x', 10)
store.compute('doubled', ['x'], (x) => (x as number) * 2)
store.clear()
store.has('x') // false
store.has('doubled') // true (definition remains)
store.get('doubled') // 0 (computes with x=undefined, treated as 0)