Resolvedores
Quem responde por cada campo: contexto, autorização, erros, middleware e a ordem das coisas.
Um resolvedor é a ação que responde por um campo. Ele recebe o objeto pai, os argumentos e o contexto — e devolve o valor.
action pedidos_de(usuario, args, ctx):
yield Banco.pedidos(usuario.id, limite := args["limite"])
Lavra.campo(esq, "Usuario", "pedidos", "[Pedido!]!",
args := {"limite": {"tipo": "Integer", "padrao": 10}},
resolve := pedidos_de)A ordem em que as coisas acontecem#
texto da consulta
↓ ler — vira árvore; erro aqui traz linha e coluna
↓ validar — TODOS os problemas de uma vez, sem resolver nada
↓ limites — profundidade e custo, antes de qualquer resolvedor
↓ resolver — campo a campo, do topo para as folhas
↓ lote — a fila vira uma consulta só
↓ coagir — o valor vira o tipo declarado
resposta: dados, erros, extensoesA validação vem antes de executar de propósito. Executar e descobrir no meio que o campo não existe já custou tudo o que veio antes — inclusive escritas, numa mudanca.
O contexto#
O contexto é o que atravessa a consulta inteira: quem pediu, a conexão do banco, o rastro. Ele é montado por pedido e morre com ele.
action contexto_de(req):
token := req["headers"]["authorization"] ?? ""
yield {
"usuario": autenticar(token),
"banco": conexao,
"ip": req["ip"],
}
Lavra.montar(api, esq, "/lavra", contexto_de := contexto_de)action meu_perfil(raiz, args, ctx):
given ctx["usuario"] is void:
Lavra.recusar("entre para ver o seu perfil")
yield ctx["usuario"]Erros#
Um erro num campo não derruba a resposta inteira. O campo vira void, o erro entra na lista com o caminho até ele, e o resto da consulta continua.
action risco_de(cliente, args, ctx):
resposta := Malha.de(ctx["malha"], "risco").obter($"/risco/{cliente.id}")
given resposta["status"] isnt 200:
Lavra.erro("o serviço de risco não respondeu", codigo := "dependencia")
yield resposta["corpo"]["nota"]{
"dados": {"cliente": {"nome": "Ana", "risco": void}},
"erros": [{"mensagem": "o serviço de risco não respondeu",
"caminho": ["cliente", "risco"],
"codigo": "dependencia"}]
}Quem pediu dez campos e teve um problema num recebe nove — não zero. É a diferença entre uma tela com um aviso e uma tela vazia.
A exceção é o campo !: ele prometeu nunca ser void, então um erro ali sobe para o pai, e daí para cima, até achar alguém que admita void.
Autorização#
Há três lugares, e eles resolvem coisas diferentes:
| Onde | Para quê |
|---|---|
| o campo não está no esquema | ninguém pode pedir, nunca |
Lavra.recusar no resolvedor | este usuário não pode ver este dado |
uma diretiva @admin | a regra vale para muitos campos, e aparece na consulta |
action cpf_de(usuario, args, ctx):
given ctx["usuario"]?.id isnt usuario.id and ctx["papel"] isnt "admin":
Lavra.recusar("o CPF é do próprio dono")
yield usuario.cpfMiddleware#
Para o que vale para toda consulta — registro, rastro, tempo —, use o middleware do próprio Kiln, que roda antes da rota:
action rastrear(req):
Malha.propagar(req, "api")
server api on 8080:
middleware rastrear
middleware Kiln.rate_limit(60, 60)
Lavra.montar(api, esq, "/lavra")Para o que vale por campo, o lugar é a diretiva própria — ela aparece na consulta, e é auditável lendo o texto.
Mudanças#
action criar_pedido(raiz, args, ctx):
dados := args["dados"]
given len(dados["itens"]) is 0:
Lavra.erro("um pedido precisa de ao menos um item", codigo := "validacao")
yield Banco.transacao(ctx["banco"], lambda => gravar(dados))Uma mudança que toca duas tabelas precisa de transação — e é Arcane.Database quem a dá. O Lavra não inventa uma: a venda gravada com o estoque não baixado é o mesmo problema em qualquer camada, e ele já está resolvido embaixo.