Skip to content

feat(zod)!: make emToZodSchema a native branded z.enum - #39

Merged
CatOfJupit3r merged 3 commits into
mainfrom
branded-zod-schema
Oct 15, 2026
Merged

CatOfJupit3r merged 3 commits into
mainfrom
branded-zod-schema

Conversation

@CatOfJupit3r

@CatOfJupit3r CatOfJupit3r commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Problem

emToZodSchema returns a z.custom schema. Parsing works, but tools that introspect Zod schemas cannot describe it. z.toJSONSchema cannot represent it, so it cannot sit in MCP or model tool input schemas. Consumers who need JSON Schema have to write z.enum(declaration.rawValues), which drops the brand, and then cast back with as unknown as z.ZodType<...>.

Behavior

emToZodSchema(declaration) now returns a native Zod 4 z.enum built from the declaration's .enum member object, typed as the branded member union, with no cast. z.toJSONSchema describes it as { type: "string", enum: [...] }.

Differences from the previous z.custom schema

What the z.custom version did, and what the new one does:

Aspect z.custom (before) z.enum (now)
Membership check declaration.is (string in the member set) Zod's enum check on the same member set. Members are always strings, so nothing is lost.
Error custom issue, message Expected one of: A, B invalid_value issue with values. The message is kept as Expected one of: A, B.
Return type z.ZodType<Branded> z.ZodEnum<{ [key]: Branded }>, still assignable to z.ZodType<Branded>. Adds .enum, .options, .extract(), .exclude().
Object declarations (em({ ORDER_PAID: "order-paid" })) Validated values only Validates values. The keys stay on schema.enum.
Composed declarations (pick, omit, extend, em.combine, derive-with casing) Worked through is Work, because each composed declaration has its own member object (tested for pick and extend).
Transforms or casing at parse time None None.
JSON Schema / z.toJSONSchema Not representable { type: "string", enum: [...] }
Zod 3 Supported (^3.25.0 || ^4.0.0) Not supported. z.enum(object) is Zod 4 API, so the peer range is now ^4.0.0.

Nothing that the z.custom version did needed keeping beyond the error message, which is kept.

Release level

This is a major change for enumwaii, because of the narrower Zod peer range, the different issue code, and the different return type. eslint-plugin-enumwaii gets a patch for the report wording. See the changeset.

Changes

  • packages/enumwaii/src/adapters/zod.ts: emToZodSchema returns z.enum(declaration.enum, { error }), with updated JSDoc.
  • packages/enumwaii/package.json: zod peer range ^4.0.0.
  • Tests: runtime tests (parse, the invalid_value issue and message, options, JSON Schema output, object-declaration keys, pick and extend) and type tests (branded z.output, ZodEnum, composition in z.object, raw literals not assignable).
  • Docs: the adapters guide (behavior and a JSON Schema example), the compatibility page, the eslint-plugin docs, and the enumwaii skill.
  • prefer-native-schema-adapters: the report says the adapter keeps the branded output type.
  • Changeset: enumwaii major, eslint-plugin-enumwaii patch. No version bump or publish in this PR.

Validation

pnpm check passes (exit 0): format, pins, lint, tests (enumwaii 24 passed), type tests, skills, build, API JSDoc, publint/attw, and every example (including the orpc and tanstack-start-solid examples that compose emToZodSchema into z.object).

emToZodSchema is a z.custom schema, which z.toJSONSchema and MCP or model
tool definitions cannot describe. emToZodEnum builds a native z.enum from the
declaration's member object, so it converts to a JSON Schema string enum while
its output stays the branded member union.
@CatOfJupit3r
CatOfJupit3r temporarily deployed to extended-validation October 15, 2026 19:10 — with GitHub Actions Inactive
Fold the JSON Schema friendly behavior into emToZodSchema instead of adding a
second adapter. emToZodSchema now returns z.enum built from the declaration's
member object, typed as the branded member union, so z.toJSONSchema can
describe it. Invalid values report an invalid_value issue with the same
message. The zod peer range is now ^4.0.0.

BREAKING CHANGE: enumwaii/zod requires Zod 4, emToZodSchema returns
z.ZodEnum instead of z.ZodType, and invalid values report invalid_value
instead of a custom issue.
@CatOfJupit3r
CatOfJupit3r temporarily deployed to extended-validation October 15, 2026 19:27 — with GitHub Actions Inactive
@CatOfJupit3r CatOfJupit3r changed the title feat(zod): add emToZodEnum native branded Zod enum adapter feat(zod)!: make emToZodSchema a native branded z.enum Oct 15, 2026
@CatOfJupit3r
CatOfJupit3r merged commit c07f51b into main Oct 15, 2026
5 checks passed
@CatOfJupit3r
CatOfJupit3r deleted the branded-zod-schema branch October 15, 2026 20:21
@github-actions github-actions Bot mentioned this pull request Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant