UPDATE
Following @jcalz's suggestion, passing a union to a generic type can help validate if it meets the constraint. Find a demo on the Playground.
type Template = { x: string; y: { [key: string]: any } };
type B = { x: "B", y: { test2: number } }
type C = { x: "C", y: { test2: number } }
type D = { x: "C", y: { test2: number } }
type E = { x: 5 }
type Check<U, T extends U> = T
// @ts-expect-error
type AError1 = Check<Template, B | C | E>
// @ts-expect-error
type AError2 = Check<Template, B | C | D | E>
type A = Check<Template, B | C | D>
Integrating generics can be useful, although it may become lengthy when dealing with more than 10 possible types in a union. View a sample on the Playground.
type Template = { x: string; y: { [key: string]: any } };
type B = {x: "B", y: {test2: number}}
type C = {x: "C", y: {test2: number}}
type D = {x: "C", y: {test2: number}}
type E = {x: 5}
type MakeUnion<TBase, T1 extends TBase, T2 extends TBase, T3 extends TBase = never, T4 extends TBase = never, T5 extends TBase = never> = T1 | T2 | T3 | T4 | T5
// @ts-expect-error
type AError1 = MakeUnion<Template, B, C, E>
// @ts-expect-error
type AError2 = MakeUnion<Template, B, C, D, E>
type A = MakeUnion<Template, B, C, D>