As you note, your Root value is too wide to enforce the constraint you care about, but it does have the advantage of being straightforward to use:
const root: Root = {
values: [
{ one: true, all: [5] }, // uh oh no error
{ one: 'a', all: ['a', 'b', 'c'] }, // okay
{ one: true, all: [false, true, true] } // okay
]
}
const indexes = root.values.map(v => v.all.indexOf(v.one));
console.log(indexes) // [-1, 0, 1]
I'm showing the above for comparison purposes. The following approach will enforce the constraint, but you will end up paying for that with some loss of convenience.
When you find yourself wishing you could write an infinite union type like the invalid
type SomeValue = Value<string> | Value<number> | Value<boolean>
| Value<null> | Value<Date> | Value<{a: string, b: number}> | ...
it's a sign that you are looking for existentially quantified generic types. Most languages with generics, including TypeScript, only directly support universally quantified generic types (which correspond to infinite intersection types). There is a feature request at microsoft/TypeScript#14466 to support existentially quantified generics, but it's not part of the language yet.
Still, it is possible to emulate these generics. The difference between existential and universal generics has to do with who gets to specify the type parameter. If you switch the role of data supplier and data consumer, then universals become existentials. So we can encode SomeValue like this:
type SomeValue = <R>(cb: <T>(value: Value<T>) => R) => R;
Let's say you have a value someValue of type SomeValue. If you want to access the underlying Value<T> data, you need to call someValue() with some callback cb that receives the Value<T> and does something with it. It's like an immediately resolved Promise. The cb callback must be prepared for any possible Value<T> value; whoever supplies value gets to choose what T is. All you can say is that it's some T.
You can write a helper function to turn any Value<T> into a SomeValue:
const toSomeValue = <T,>(value: Value<T>): SomeValue => cb => cb(value);
And then your Root would be
type Root = { values: SomeValue[] }
Which means you can now create root as follows:
const root: Root = {
values: [
toSomeValue({ one: true, all: [5] }), // error!
toSomeValue({ one: 'a', all: ['a', 'b', 'c'] }), // okay
toSomeValue({ one: true, all: [false, true, true] }) // okay
]
}
Here you've got the type checking you wanted (with the penalty that you needed to write toSomeValue() a bunch of times). And now you can make indexes from before by pushing your old v => v.all.indexOf(v.one) callback down into someV:
const indexes = root.values.map(someV => someV(v => v.all.indexOf(v.one)));
console.log(indexes) // [-1, 0, 1]
So it produces the same result, but again, with more complexity.
Now you might want to try to make Root generic itself, where you map an array type to an array of Value<T> for individual T types. And then you could either manually annotate that root is, say, Root<[5, "a" | "b" | "c", boolean]>, or try to get the compiler to infer [5, "a" | "b" | "c", boolean] from the initializer to the values property. These approaches are technically possible, but they add even more complexity than the existential type encoding above, and they are not as type safe. So I won't go into detail here; although the code is included in the link at the bottom of the answer.
Playground link to code
<T>is global to your array, so basically all T types can be anything.