Skip to content

Type.Partial/Pick/Omit silently discard outer Transform — should this throw instead? #12

@yiidtw

Description

@yiidtw

Hi Haydn — as per our discussion over email, I'm opening this issue here for further conversation. Thank you for taking the time to explain the design rationale, really appreciate it.

Behavior

Type.Partial() silently discards TransformKind when applied to a schema with outer Type.Transform(). Same for Type.Pick(), Type.Omit(), and Type.Composite().

I understand that interior Transform (applied to individual properties like Type.Transform(Type.String())) is retained through Partial — this is great. The issue is specifically about outer/structural Transform.

import { Type, TransformKind } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";

const Schema = Type.Transform(
  Type.Object({ bio: Type.String() })
)
  .Decode(v => ({ bio: v.bio.replace(/<[^>]*>/g, "") }))
  .Encode(v => v);

console.log(TransformKind in Schema);                // true
console.log(TransformKind in Type.Partial(Schema));   // false — silent discard

Value.Decode(Schema, { bio: "<b>hi</b>" });
// => { bio: "hi" }  — Transform active

Value.Decode(Type.Partial(Schema), { bio: "<b>hi</b>" });
// => { bio: "<b>hi</b>" }  — Transform silently gone

I also checked typebox 1.x — Partial() discards ~codec the same way.

Question

Type.Intersect() already throws when it encounters Transform (as of 0.34.48). Would it make sense for Partial/Pick/Omit to do the same? A throw would:

  • Make the behavior visible (no silent surprise)
  • Be consistent with Intersect's approach
  • Guide users toward the interior-Transform pattern you recommended

Best Regards,
YD

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions