Contracts

O módulo contract abre bindings de serviços tipados para APIs remotas, workflows e funções, com validação de schema, chamadas assíncronas e propagação do contexto. Esta página é uma referência de API; IDs e valores como current_user pertencem à aplicação.

Carregamento

local contract = require("contract")

Abrindo um Binding

Abrir um binding diretamente por ID:

local greeter, err = contract.open("app.services:greeter")
if err then
    return nil, err
end

local result, err = greeter:say_hello("Alice")
if err then
    return nil, err
end

Com contexto de escopo ou parametros de query:

-- With scope table
local svc, err = contract.open("app.services:user", {
    tenant_id = "acme",
    region = "us-east"
})

-- With query parameters (auto-converted: "true"→bool, numbers→int/float)
local api, err = contract.open("app.services:api?debug=true&timeout=5000")

-- With call options (third argument)
local inst, err = contract.open("app.services:flaky", nil, {
    retry = { max_attempts = 5, initial_delay = 100 }
})
Parâmetro Tipo Descrição
binding_id string ID do binding, suporta parametros de query
scope table Valores de contexto (opcional, sobrescreve parametros de query)
options table Opções de chamada (opcional) — ex. retry.max_attempts, retry.initial_delay

Retorna: Instance, error

Obtendo um Contract

Recuperar definição de contract para introspecção:

local c, err = contract.get("app.services:greeter")
if err then
    return nil, err
end

print(c:id())  -- "app.services:greeter"

local methods = c:methods()
for _, m in ipairs(methods) do
    print(m.name, m.description)
end

local method, err = c:method("say_hello")
if err then
    return nil, err
end

Definição de Method

Cada elemento do schema contém uma string format e pode incluir um valor definition.

Campo Tipo Descrição
name string Nome do método
description string Descrição do método
input_schemas table[] Definições de schema de entrada (ausente quando o método não declara nenhum)
output_schemas table[] Definições de schema de saída (ausente quando o método não declara nenhum)

Encontrando Implementações

Listar todos os bindings que implementam um contract:

local bindings, err = contract.find_implementations("app.services:greeter")
if err then
    return nil, err
end

for _, binding_id in ipairs(bindings) do
    print(binding_id)
end

Ou via objeto contract:

local c, err = contract.get("app.services:greeter")
if err then
    return nil, err
end
local bindings, err = c:implementations()
if err then
    return nil, err
end

Verificando Implementação

Verificar se instância implementa um contract:

if contract.is(instance, "app.services:greeter") then
    instance:say_hello("World")
end

Chamando Métodos

Chamada síncrona - bloqueia até completar:

local calc, err = contract.open("app.services:calculator")
if err then
    return nil, err
end

local sum, err = calc:add(10, 20)
if err then
    return nil, err
end
local product, err = calc:multiply(5, 6)
if err then
    return nil, err
end

Chamadas Assíncronas

Adicione sufixo _async para execução assíncrona:

local processor, err = contract.open("app.services:processor")
if err then
    return nil, err
end

local future, err = processor:process_async(large_dataset)
if err then
    return nil, err
end

-- Do other work...

-- Wait for result
local ch = future:response()
local _, open = ch:receive()
if not open then
    return nil, errors.new("future response channel closed")
end

local payload, result_err = future:result()
if result_err then return nil, result_err end
local result, data_err = payload:data()
if data_err then return nil, data_err end

Veja Futures para os métodos de future.

Abrindo via Contract

Abra um binding por um objeto contract. As chamadas abaixo são alternativas; verifique o erro retornado por contract.get() e pela chamada open() escolhida antes de usar a instância.

Abrir binding através de objeto contract:

local c, err = contract.get("app.services:user")
if err then
    return nil, err
end

-- Default binding
local instance, err = c:open()

-- Specific binding
local instance, err = c:open("app.services:user_impl")

-- With scope
local instance, err = c:open(nil, {user_id = 123})
local instance, err = c:open("app.services:user_impl", {user_id = 123})

Adicionando Contexto

Criar wrapper com contexto pre-configurado:

local ctx = require("ctx")
local c, err = contract.get("app.services:user")
if err then return nil, err end

local request_id, ctx_err = ctx.get("request_id")
if ctx_err then return nil, ctx_err end

local wrapped, err = c:with_context({
    request_id = request_id,
    user_id = current_user.id
})
if err then return nil, err end

local instance, err = wrapped:open()

Opções de Chamada

Configure retry e outro comportamento de chamada via with_options:

local c, err = contract.get("app.services:flaky")
if err then return nil, err end

local configured = c:with_options({
    retry = { max_attempts = 5, initial_delay = 100 }
})
local inst, err = configured:open("app.services:flaky_impl")
if err then return nil, err end

local result, err = inst:call()

As opções aplicam-se a cada chamada de método na instância retornada. Apenas erros passíveis de retry disparam retries; erros não passíveis de retry aparecem imediatamente. Encadeável com with_context, with_actor, with_scope.

Opção Tipo Descrição
retry.max_attempts int Tentativas máximas incluindo a primeira (1 desativa retry)
retry.initial_delay int/duration Atraso antes do primeiro retry (ms ou string de duração), padrão 100
retry.max_delay int/duration Limite superior do atraso de backoff (ms ou string de duração), padrão 10s
retry.backoff_factor number Multiplicador aplicado ao atraso após cada tentativa, padrão 2.0
retry.jitter number Fração de jitter aleatório aplicada a cada atraso, padrão 0.1
retry.retry_kinds string[] Só repete erros destes kinds; por padrão todo kind exceto Invalid, PermissionDenied e Internal é repetido
retry.skip_kinds string[] Nunca repete erros destes kinds

Contexto de Segurança

Definir ator e escopo para autorização:

local security = require("security")
local c, err = contract.get("app.services:admin")
if err then return nil, err end

local secured, err = c:with_actor(security.actor())
if err then return nil, err end

secured, err = secured:with_scope(security.scope())
if err then return nil, err end

local admin, err = secured:open()
if err then return nil, err end

Sem with_actor/with_scope explícitos, um contract aberto herda o ator e o escopo ambientes do chamador. Quando definidos, eles se propagam às funções de implementação vinculadas — cada chamada de método na instância executa sob essa identidade.

Permissões

Permissão Recurso Funções
contract.get id do contract get()
contract.open id do binding open(), Contract:open()
contract.implementations id do contract find_implementations(), Contract:implementations()
contract.call nome do método chamadas de método sync e async
contract.context "context" Contract:with_context()
contract.security "security" Contract:with_actor(), Contract:with_scope()

Erros

Condição Tipo
Formato de ID de binding inválido errors.INVALID
Contract não encontrado errors.NOT_FOUND
Binding não encontrado errors.NOT_FOUND
Método não encontrado errors.NOT_FOUND
Sem binding padrão errors.NOT_FOUND
Permissão negada errors.PERMISSION_DENIED
Chamada falhou kind do erro da implementação (preservado); errors.INTERNAL para falhas de dispatch