Add util type for merge multiple types
It would be super useful to have utility type for merge multiple types using @type as array.
For example:
type UnionToIntersection<T> = (
T extends any ? (arg: T) => any : never
) extends (arg: infer I) => any
? I
: never;
type MergeTypes<
T extends { "@type": string }[],
R extends unknown[] = [],
> = T extends [
infer I extends { "@type": string },
...infer J extends { "@type": string }[],
]
? UnionToIntersection<I["@type"]> extends never
? I
: MergeTypes<J, [...R, I["@type"]]>
: R;
type MergePropertiesExceptForTypes<T extends unknown[], R = {}> = T extends [
infer I,
...infer J,
]
? MergePropertiesExceptForTypes<J, Omit<I, "@type"> & R>
: R;
export type MergeSchema<T extends { "@type": string }[]> =
MergeTypes<T> extends unknown[]
? MergePropertiesExceptForTypes<T> & { "@type": MergeTypes<T> }
: "Error: Leaf types expected." & { got: MergeTypes<T> };
// === Example Below
import { Product, SheetMusic } from "schema-dts";
type Error = MergeSchema<[Product, SheetMusic]>; // "Error: Leaf types expected." & { got: Product }
type Good = MergeSchema<[Extract<Product, { "@type": "Product" }>, SheetMusic]>;
I think It may help #189, #179 as well
@mjy9088 pls be aware that your code returns error type for Product and SoftwareApplication merge:
type Error = MergeSchema<[Product, SoftwareApplication]>; // "Error: Leaf types expected." & { got: Product }
@mjy9088 pls be aware that your code returns error type for Product and SoftwareApplication merge:
type Error = MergeSchema<[Product, SoftwareApplication]>; // "Error: Leaf types expected." & { got: Product }
Hi, thanks for your interest in this!
Just a quick note — Product is a union of many types (e.g. Product | Vehicle | ProductModel | ...), so using it directly with MergeSchema will result in an error like "Error: Leaf types expected.".
To avoid this, you should use a narrowed type like Extract<Product, { "@type": "Product" }> instead. That ensures you're working with a concrete leaf schema.
Hope that helps!
I've been thinking about this a lot. I've been stuck on pushing anything into the main library in part because of the leaf type issue.
Perhaps we could expose something like this as "MergeLeafTypes<>"?
The other thing is that I had been hesitant to export Leaf types since they seemed like an implementation detail, but increasingly there are many examples where people need the leaf type, so perhaps we should start exporting them.
I’m strongly in favor of exporting the leaf types. In fact, I’m already using them in my own codebase — for example:
import { Thing } from "schema-dts";
export type Leaf<T extends Exclude<Thing, string>['@type']> =
Extract<Thing, Record<"@type", T>>;
// usage:
type ProductLeaf = Leaf<"Product">;
Without these exported leaf types, I’ve had to re-implement similar utility types just to get precise narrowing for @type.
This not only adds boilerplate, but also risks subtle mismatches if the upstream Thing definition changes — for example, if @type handling or the shape of the union changes, my local Leaf<> could silently become inaccurate.
An official export would stay in sync with the library’s own definitions, so users could rely on them without tracking internal changes.
To make it clear that leaf types are still an implementation detail, one option could be to expose them in a sub-path, e.g.:
import { ProductLeaf } from "schema-dts/leaf";
This keeps the main API surface clean while still giving advanced users access to these types in a safe, consistent way.