Follow-up to #8344, from Wadii's review comment there.
featureStateToValue reads the type key off two different API shapes, and reaches for an assertion to do it:
const type =
(value as { value_type?: FeatureStateValue['type'] }).value_type ?? value.type
value_type is invented at the call site, so it is tied to nothing and a rename is silent.
Naming the trait shape lets in narrow instead:
// responses.ts
export type TraitValue = {
boolean_value: boolean | null
float_value?: number | null
integer_value: number | null
string_value: string | null
value_type: 'int' | 'unicode' | 'bool' | 'float'
}
// featureStateToValue.ts
value: FlagsmithValue | FeatureStateValue | TraitValue | undefined
const type = 'value_type' in value ? value.value_type : value.type
float_value and 'float' are in there because traits carry both (api/environments/identities/traits/models.py:15,37).
The half that actually catches renames is the producer: Utils.valueToTrait (utils.tsx:887) builds this same object with no return annotation, so annotate it : TraitValue. Checked locally, renaming the key then fails in both places instead of neither.
Kept out of #8344 because it pulls utils.tsx back into a PR whose point was decoupling from it.
Follow-up to #8344, from Wadii's review comment there.
featureStateToValuereads the type key off two different API shapes, and reaches for an assertion to do it:value_typeis invented at the call site, so it is tied to nothing and a rename is silent.Naming the trait shape lets
innarrow instead:float_valueand'float'are in there because traits carry both (api/environments/identities/traits/models.py:15,37).The half that actually catches renames is the producer:
Utils.valueToTrait(utils.tsx:887) builds this same object with no return annotation, so annotate it: TraitValue. Checked locally, renaming the key then fails in both places instead of neither.Kept out of #8344 because it pulls
utils.tsxback into a PR whose point was decoupling from it.