Changelog - 2026-10-09
Const-value types keep their literals when copied
Behavior Change Breaking ChangeIn one line. A value typed with TConstValue and its siblings now keeps its literal union when you copy it into an object literal or a let, instead of widening to string or number.
What changed
- The types.
TStatusFromClass,TStringConstValue,TNumberConstValueandTConstValuekeep the literal members of a class'sstatic readonlyvalues.rows.map(row => ({ value: row.value }))now gives{ value: '12h' | '24h' }, not{ value: string }. - What stays the same. The union itself is unchanged. A member declared
: stringstill givesstring. The result is still assignable tostringornumber. - Also affected.
TRequestMethod,TEnvironment,TRequestTypeandTRequestBodyTypekeep their literals too.
Who is affected
- Most code. No action needed.
- Code that relied on the widening. A variable inferred from one of these types and later assigned a string that is not a member now fails to compile. Annotate it as
stringif it must hold any string. - Tests that compare against a non-member.
expect(value).toBe('other')no longer compiles whenvaluehas one of these types. Writeexpect<string>(value).toBe('other').
Breaking changes
WARNING
Narrow: only code that leaned on the old widening is affected.
Before:
typescript
// TimeModes has static readonly HALF_DAY = '12h' and FULL_DAY = '24h'
declare const modes: TConstValue<typeof TimeModes>[];
// ...
let current = modes[0]; // string
current = 'any text'; // compiledAfter:
typescript
declare const modes: TConstValue<typeof TimeModes>[];
// ...
let current = modes[0]; // '12h' | '24h'
current = 'any text'; // error: not assignable to '12h' | '24h'
let anyText: string = modes[0]; // write the wider type when you mean itSee Types and constants.