Unions, literals, and enums

The three “choice” kinds in Sapphire — union, literal, enum — let you express “one of these shapes” or “one of these values”. They all live under the a.type() namespace because they’re builders, not stand-alone primitives. This page covers their construction, their inferred types, and how each adapter materializes them.

Union

a.type().union([f1, f2, ...]) accepts any value that successfully parses against at least one of the inner fields. The output type is the union of inner output types.

const id = a.type().union([a.string(), a.number()])
type Id = Infer<typeof id>
// Id = string | number

Adapter mapping:

AdapterOutput
MongoSchema.Types.Mixed (no runtime narrowing — caveat is documented).
JSON SchemaoneOf: [...].
Drizzlejsonb column (Postgres) / fallback per dialect.

Literal

a.type().literal(value) matches exactly one string, number, or boolean value. The output type is narrowed to that literal.

const kind = a.type().literal('admin')
type Kind = Infer<typeof kind>
// Kind = 'admin'

Enum

a.type().enum(values) accepts either a readonly array of literals (use as const) or a TypeScript enum object.

const role = a.type().enum(['admin', 'editor', 'viewer'] as const)
type Role = Infer<typeof role>
// Role = 'admin' | 'editor' | 'viewer'

With a TS enum:

enum Status {
  Active = 1,
  Inactive = 2,
}
const status = a.type().enum(Status)
// _output is Status (a numeric union); reverse mappings ('1','2' → name)
// generated by TS are filtered before being baked into the IR.

TS numeric enums compile to { Active: 1, Inactive: 2, '1': 'Active', '2': 'Inactive' }. Sapphire’s extractEnumValues drops keys that match /^\d+$/ so only the forward direction makes it into the IR — string values are NOT accepted as members of a numeric enum.

Pitfalls

[!WARNING] literal('admin') and enum(['admin'] as const) have the same _output. For a single value the distinction is cosmetic — pick by intent. Use literal when the value is a tag (discriminant in a discriminated union, MCP tool’s type field); use enum when membership is the point and you expect to grow the list.

[!WARNING] Always pass as const to enum(...). Without it, ['admin','editor'] widens to string[] and you’ll get Role = string instead of the narrowed union. as const preserves the literal types end-to-end.