Composição
ObjectFields podem ser derivados uns dos outros. pick, omit, partial, required, extend e merge retornam cada um um novo ObjectField — o original permanece intocado. A mesma cadeia funciona no nível de tipos: Infer<typeof derived> reflete o que quer que a composição tenha feito. Use esses métodos para compartilhar um único schema base entre as fronteiras de leitura/escrita/patch em vez de se repetir.
Exemplo mínimo
const baseUser = a.object({
id: a.string(),
name: a.string(),
email: a.string().email(),
age: a.number().int(),
})
Todos os exemplos abaixo derivam deste baseUser.
pick(keys)
Restringe ao subconjunto listado. As chaves são verificadas em tempo de tipo contra keyof T. Passe o array como as const para inferência com tipos literais.
const publicUser = baseUser.pick(['id', 'name'] as const)
type PublicUser = Infer<typeof publicUser>
// PublicUser = { id: string; name: string }
O novo ObjectField tem uma configuração de nível de schema zerada — sem name, timestamps, indexes ou meta da origem. Apenas a flag required do próprio objeto é preservada.
omit(keys)
Descarta as chaves listadas. Mesma convenção de as const; mesma semântica de reset de configuração que o pick.
const safeUser = baseUser.omit(['email'] as const)
type SafeUser = Infer<typeof safeUser>
// SafeUser = { id: string; name: string; age: number }
partial()
Torna todo field filho opcional. Tanto _input quanto _output de cada propriedade passam a ser T | undefined, elevados a posições ?: no tipo do objeto.
const patch = baseUser.partial()
type Patch = Infer<typeof patch>
// Patch = { id?: string; name?: string; email?: string; age?: number }
partial() diz respeito aos filhos, não ao objeto em si — a própria flag required/optional do ObjectField não muda. Se você quer tanto partial() QUANTO o objeto opcional, encadeie .optional() depois.
required()
Inverso de partial() — chama .required() em todo field filho, removendo qualquer | undefined dos tipos deles.
const looseUser = a.object({
id: a.string(),
name: a.string().optional(),
})
const strictUser = looseUser.required()
type Strict = Infer<typeof strictUser>
// Strict = { id: string; name: string }
A configuração de nível de schema (name/timestamps/indexes/meta/description) é preservada — required() é um fortalecimento do mesmo schema, não um schema novo como pick/omit.
extend(shape)
Adiciona novas chaves (ou substitui as existentes) passando um objeto de shape parcial.
const audited = baseUser.extend({
createdAt: a.date(),
// overrides baseUser.age:
age: a.number().int().nonnegative(),
})
type Audited = Infer<typeof audited>
// Audited adds createdAt and keeps the (narrower) age
A resolução de conflitos é a direita vence: qualquer chave em shape substitui a original. A configuração do próprio objeto é preservada.
merge(other)
Mescla o shape de outro ObjectField. Semanticamente equivalente a this.extend(other.getObj()).
const audit = a.object({
createdAt: a.date(),
updatedAt: a.date(),
})
const userWithAudit = baseUser.merge(audit)
type UserWithAudit = Infer<typeof userWithAudit>
// UserWithAudit = baseUser fields + createdAt + updatedAt
A configuração de nível de schema de other (name, timestamps, indexes, description) não é copiada — apenas o shape de suas propriedades. Mesma regra de a direita vence em conflitos.
Armadilhas
[!WARNING]
extendemergeusam a direita vence em chaves conflitantes. A definição que chega substitui a original silenciosamente. Se você não pretendia a substituição, o sistema de tipos não pode ajudar — não há erro em sobreposição. Recorra aomitprimeiro se quiser ser explícito sobre o descarte.
[!WARNING] Cada composição retorna um ObjectField novinho em folha. O original permanece intocado. Variáveis do schema original ainda resolvem para o shape original; se você quer “atualizar” um schema no lugar, reatribua a variável.
Relacionados
- Fields e modificadores — a superfície completa do ObjectField.
- Inferindo tipos — como
Infer<>percorre a composição. - Receitas → Compartilhar tipos com o frontend.