Contratos

El módulo contract abre bindings de servicios tipados para API remotas, workflows y funciones. Los contratos admiten validación de esquema, llamadas asíncronas y propagación del contexto de llamada. Esta página es una referencia de API; los IDs y valores como current_user representan entradas y estado circundante de la aplicación.

Carga

local contract = require("contract")

Abrir un Binding

Abrir un binding directamente 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

Con contexto de alcance o parametros de consulta:

-- 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 Descripción
binding_id string ID de binding, soporta parametros de consulta
scope table Valores de contexto (opcional, sobrescribe parametros de consulta)
options table Opciones de llamada (opcional) — ej. retry.max_attempts, retry.initial_delay

Devuelve: Instance, error

Obtener un Contrato

Recuperar definicion de contrato para introspeccion:

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

Definicion de Método

Campo Tipo Descripción
name string Nombre del método
description string Descripción del método
input_schemas table[] Definiciones de esquema de entrada (ausente cuando el método no declara ninguno)
output_schemas table[] Definiciones de esquema de salida (ausente cuando el método no declara ninguno)

Encontrar Implementaciones

Listar todos los bindings que implementan un contrato:

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

O via objeto de contrato:

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

Verificar Implementacion

Verificar si una instancia implementa un contrato:

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

Llamar Metodos

Llamada sincrona - bloquea hasta 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

Llamadas Asincronas

Agregar sufijo _async para ejecución asincrona:

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

Consulta Futures para conocer sus métodos.

Abrir via Contrato

Abre un binding mediante un objeto de contrato. Las llamadas siguientes son alternativas; comprueba el error de contract.get() y de la llamada open() elegida antes de usar la instancia.

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})

Agregar Contexto

Crear envoltorio con contexto preconfigurado:

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()

Opciones de Llamada

Configura reintentos y otro comportamiento de llamada vía 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()

Las opciones se aplican a cada llamada de método de la instancia devuelta. Solo los errores reintentables disparan reintentos; los demás vuelven de inmediato. with_options puede encadenarse con with_context, with_actor y with_scope.

Opción Tipo Descripción
retry.max_attempts int Intentos máximos incluyendo el primero (1 desactiva reintentos)
retry.initial_delay int/duration Retardo antes del primer reintento (ms o cadena de duración), por defecto 100
retry.max_delay int/duration Límite superior del retardo de backoff (ms o cadena de duración), por defecto 10s
retry.backoff_factor number Multiplicador aplicado al retardo tras cada intento, por defecto 2.0
retry.jitter number Fracción de jitter aleatorio aplicada a cada retardo, por defecto 0.1
retry.retry_kinds string[] Reintentar solo los errores de estos tipos; por defecto se reintenta cualquier tipo excepto Invalid, PermissionDenied e Internal
retry.skip_kinds string[] Nunca reintentar los errores de estos tipos

Contexto de Seguridad

Establecer actor y alcance para autorizacion:

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

Sin with_actor/with_scope explícitos, un contrato abierto hereda el actor y el scope ambientales del llamador. Cuando se establecen, se propagan a las funciones de implementación enlazadas — cada llamada de método en la instancia se ejecuta bajo esa identidad.

Permisos

Permiso Recurso Funciones
contract.get id de contrato get()
contract.open id de binding open(), Contract:open()
contract.implementations id de contrato find_implementations(), Contract:implementations()
contract.call nombre de método llamadas de método sync y async
contract.context "context" Contract:with_context()
contract.security "security" Contract:with_actor(), Contract:with_scope()

Errores

Condición Tipo
Formato de ID de binding invalido errors.INVALID
Contrato no encontrado errors.NOT_FOUND
Binding no encontrado errors.NOT_FOUND
Método no encontrado errors.NOT_FOUND
Sin binding por defecto errors.NOT_FOUND
Permiso denegado errors.PERMISSION_DENIED
Llamada fallida tipo del error de la implementación (preservado); errors.INTERNAL para fallos de despacho