Skip to content

Unions, Intersections, and Variants ​

Problem ​

A domain model may have multiple valid shapes. You need to express those branches while keeping accurate errors and typed output.

Solution ​

Use unions for alternative shapes, intersections for merged constraints, and variants for discriminated object unions.

ts
import { s } from '@vielzeug/spell';

const Timestamped = s.object({
  createdAt: s.date(),
});

const DraftArticle = s.object({
  kind: s.literal('draft'),
  title: s.string().min(3),
});

const PublishedArticle = s.object({
  kind: s.literal('published'),
  slug: s.string().slug(),
  title: s.string().min(3),
});

const Article = s.discriminatedUnion('kind', {
  draft: DraftArticle,
  published: PublishedArticle,
});

const AuditedArticle = s.intersect(Article, Timestamped);
const result = AuditedArticle.safeParse({ kind: 'published', title: 'Docs', createdAt: new Date() });

if (!result.success) {
  console.log(result.error.issues);
}

Pitfalls ​

  • Prefer s.discriminatedUnion() over s.union() when one field can discriminate the branches. Errors are smaller and branch selection is deterministic.
  • Intersections merge outputs deeply. Keep overlapping property names compatible across both sides.
  • Plain unions report a stable union-level issue. Use a discriminated union when errors must identify one object branch.