type KeyValueStoreCallback = (value: T) => void; interface IKeyValueStoreParam { value: T; valueRange?: T[]; callbacks?: KeyValueStoreCallback[]; } type KeyValueStoreData = { [K in keyof T]: IKeyValueStoreParam }; class KeyValueStore { constructor(private data: KeyValueStoreData) { for (const id of Object.keys(data)) { const props = data[id as keyof typeof data]; if (props.valueRange && props.valueRange.indexOf(props.value) === -1) { throw new Error(`Invalid value "${props.value}" for ID "${id}"`); } } } public getValue(id: keyof T) { return this.data[id].value; } public setValue(id: K, value: T[K]) { const props = this.data[id]; if (props.valueRange) { const valueIndex = props.valueRange.indexOf(value); if (valueIndex === -1) { throw new Error(`Invalid value "${value}" for ID "${id}"`); } } props.value = value; if (props.callbacks) { props.callbacks.forEach(callback => { callback(value); }); } } public cycleValue(id: K, dir: -1 | 1 = 1) { const props = this.data[id]; if (!props) { throw new Error(`Invalid ID "${id}"`); } let value = props.value; if (props.valueRange) { let valueIndex = props.valueRange.indexOf(value) + dir; if (valueIndex >= props.valueRange.length) { valueIndex = 0; } else if (valueIndex < 0) { valueIndex = props.valueRange.length - 1; } value = props.value = props.valueRange[valueIndex]; } else if (typeof value === "number") { (value as number) += dir; props.value = value; } else { throw new Error(`Can't cycle "${id}"`); } if (props.callbacks) { props.callbacks.forEach(callback => { callback(value); }); } return value; } public addCallback(id: K, callback: KeyValueStoreCallback) { const props = this.data[id]; if (!props.callbacks) { props.callbacks = []; } props.callbacks.push(callback); } }