Skip to main content
O Node Registry é um endpoint da API pública que fornece um schema completo e estruturado de todos os nodes disponíveis (built-in e customizados) para um tenant. Ele é fundamental para a construção de editores de workflows no frontend, fornecendo todas as informações necessárias para renderizar, validar e configurar nodes dinamicamente.

Propósito

O Node Registry serve como uma fonte única de verdade sobre os nodes disponíveis, fornecendo:
  • Catálogo completo: Lista todos os nodes built-in e customizados disponíveis para o tenant
  • Schemas detalhados: Define estruturas de entrada, saída e configuração de cada node
  • Metadados: Informações sobre versão, descrição, capacidades e tipo de cada node
  • Validação: Especificações que permitem validação em tempo real no editor

Endpoint

GET /v1/gateway/custom-nodes/registry

Autenticação

Requer autenticação via chave de API (pública ou secreta):
X-API-Key: pk_sua_chave_aqui

Resposta

{
  "nodes": [
    {
      "type": "input",
      "name": "User Input",
      "description": "Waits for user input and pauses workflow execution",
      "version": "1.0.0",
      "isBuiltIn": true,
      "inputSchema": {
        "data": {
          "type": "any",
          "description": "Input data to process",
          "required": true
        }
      },
      "outputSchema": {
        "result": {
          "type": "object",
          "description": "Processing result",
          "properties": {
            "success": { "type": "boolean" },
            "data": { "type": "any" }
          }
        }
      },
      "configSchema": {
        "timeout": {
          "type": "number",
          "description": "Timeout in milliseconds",
          "min": 1000,
          "max": 300000,
          "default": 30000
        }
      },
      "canPause": true
    },
    {
      "type": "custom-api-call",
      "name": "Custom API Call",
      "description": "Custom node example",
      "version": "1.0.0",
      "isBuiltIn": false,
      "inputSchema": {
        "data": {
          "type": "any",
          "description": "Input data to process",
          "required": true
        }
      },
      "outputSchema": {
        "result": {
          "type": "object",
          "description": "API call result"
        }
      },
      "configSchema": {
        "endpoint": {
          "type": "string",
          "description": "API endpoint URL",
          "required": true
        },
        "method": {
          "type": "enum",
          "enum": ["GET", "POST", "PUT", "DELETE"],
          "default": "POST"
        }
      },
      "canPause": false
    }
  ],
  "total": 7
}

Estrutura do Schema

Cada node no registry contém as seguintes propriedades:

Propriedades Principais

type
string
required
Identificador único do tipo de node (ex: "input", "transform", "custom-api-call"). Usado para referenciar o node ao criar workflows.
name
string
required
Nome legível do node exibido na interface do usuário (ex: "User Input", "Transform Data").
description
string
Descrição detalhada do que o node faz e quando deve ser usado.
version
string
required
Versão do node seguindo semantic versioning (semver), ex: "1.0.0".
isBuiltIn
boolean
required
Indica se é um node built-in (true) ou customizado (false). Nodes built-in são fornecidos pela plataforma, enquanto customizados são criados pelo tenant.
canPause
boolean
required
Indica se o node pode pausar a execução do workflow (ex: aguardando input do usuário). Nodes com canPause: true permitem workflows interativos.

Schemas de Dados

inputSchema
object
required
Schema que define os campos de entrada esperados pelo node. Cada chave é o nome do campo e o valor é um NodeFieldSchemaDto com especificação completa de tipo, validação e descrição.Exemplo:
{
  "data": {
    "type": "string",
    "description": "Data to process",
    "required": true,
    "minLength": 1,
    "maxLength": 1000
  },
  "options": {
    "type": "object",
    "properties": {
      "format": {
        "type": "enum",
        "enum": ["json", "xml", "csv"],
        "default": "json"
      }
    }
  }
}
outputSchema
object
required
Schema que define os campos de saída produzidos pelo node. Estrutura similar ao inputSchema, mas descreve o que o node retorna após a execução.Exemplo:
{
  "result": {
    "type": "object",
    "description": "Processing result",
    "properties": {
      "success": { "type": "boolean" },
      "data": { "type": "any" },
      "metadata": {
        "type": "object",
        "properties": {
          "processedAt": { "type": "string" },
          "duration": { "type": "number" }
        }
      }
    }
  }
}
configSchema
object
Schema que define as configurações do node (settings que o usuário pode ajustar ao adicionar o node ao workflow). Inclui validações, valores padrão e tipos completos.Exemplo:
{
  "timeout": {
    "type": "number",
    "description": "Timeout in milliseconds",
    "min": 1000,
    "max": 300000,
    "default": 30000,
    "integer": true
  },
  "retryOnError": {
    "type": "boolean",
    "description": "Whether to retry on error",
    "default": false
  },
  "maxRetries": {
    "type": "number",
    "description": "Maximum number of retries",
    "min": 0,
    "max": 5,
    "default": 3,
    "integer": true
  }
}

NodeFieldSchemaDto

O NodeFieldSchemaDto é a estrutura base usada em inputSchema, outputSchema e configSchema. Ele suporta tipos complexos e validações avançadas:

Tipos Suportados

  • string: Texto com validações de comprimento e padrões regex
  • number: Números com validações de min/max e suporte a inteiros
  • boolean: Valores booleanos
  • any: Qualquer tipo (sem validação específica)
  • object: Objetos com propriedades definidas e suporte a propriedades adicionais
  • array: Arrays com schema de itens e validações de tamanho
  • enum: Valores enumerados (lista de valores permitidos)

Propriedades Comuns

type
string
required
Tipo do campo: "string", "number", "boolean", "any", "object", "array" ou "enum".
description
string
Descrição do propósito do campo.
required
boolean
Se o campo é obrigatório (padrão: false).
default
any
Valor padrão do campo se não fornecido.

Validações por Tipo

String

minLength
number
Comprimento mínimo (inclusivo).
maxLength
number
Comprimento máximo (inclusivo).
pattern
string
Padrão regex para validação.

Number

min
number
Valor mínimo (inclusivo).
max
number
Valor máximo (inclusivo).
integer
boolean
Se o número deve ser inteiro (sem decimais).

Object

properties
object
Definição de propriedades do objeto (recursivo, cada propriedade é um NodeFieldSchemaDto).
additionalProperties
boolean
Se propriedades adicionais (não definidas em properties) são permitidas.

Array

items
NodeFieldSchemaDto
Schema dos itens do array (recursivo).
minItems
number
Número mínimo de itens (inclusivo).
maxItems
number
Número máximo de itens (inclusivo).

Enum

enum
array
Lista de valores permitidos: ["value1", "value2", ...].

Padrões e Protocolo

Multi-Tenancy

O Node Registry é isolado por tenant. Cada requisição retorna apenas os nodes disponíveis para o tenant autenticado:
  • Nodes built-in: Fornecidos pela plataforma, podem ser desabilitados por tenant via configuração
  • Nodes customizados: Criados pelo tenant e visíveis apenas para ele
  • Traduções: Aplicadas automaticamente baseadas na configuração de i18n do tenant

Versionamento

  • Nodes seguem semantic versioning (semver)
  • Versões são imutáveis: uma vez criado, um node mantém sua versão
  • Novas versões devem ser criadas como novos nodes ou através de atualizações controladas

Filtragem e Personalização

O registry aplica automaticamente:
  1. Filtragem de built-in nodes: Remove nodes built-in desabilitados na configuração do tenant
  2. Traduções: Aplica traduções baseadas no idioma configurado do tenant
  3. Ativação: Inclui apenas nodes customizados ativos (isActive: true)

Ordenação

A ordem dos nodes na resposta não é garantida. O frontend deve ordenar conforme necessário (ex: por tipo, nome, ou categoria).

Uso no Frontend

O Node Registry é projetado para ser consumido por editores de workflows no frontend. Recomendamos usar o SDK oficial do Triglit para facilitar a integração.

Instalação do SDK

npm install triglit
Aqui está um exemplo de como utilizá-lo:

Exemplo: Construindo um Editor de Workflows

import Triglit from 'triglit';

// 1. Inicializar o cliente do SDK
const client = new Triglit({ 
  apiKey: 'pk_sua_chave_aqui' 
});

// 2. Buscar o registry usando o SDK
const registry = await client.customNodes.retrieveRegistry();

// 3. Renderizar lista de nodes disponíveis
function NodePalette({ registry }) {
  return (
    <div className="node-palette">
      <h3>Nodes Disponíveis</h3>
      {registry.nodes.map(node => (
        <NodeCard
          key={node.type}
          type={node.type}
          name={node.name}
          description={node.description}
          isBuiltIn={node.isBuiltIn}
          onClick={() => addNodeToWorkflow(node.type)}
        />
      ))}
    </div>
  );
}

// 4. Renderizar formulário de configuração do node
function NodeConfigForm({ nodeType, registry }) {
  const node = registry.nodes.find(n => n.type === nodeType);
  if (!node || !node.configSchema) return null;

  return (
    <form>
      {Object.entries(node.configSchema).map(([fieldName, fieldSchema]) => (
        <FieldInput
          key={fieldName}
          name={fieldName}
          schema={fieldSchema}
          // Renderiza input baseado no tipo e validações
        />
      ))}
    </form>
  );
}

// 5. Validar configuração antes de salvar
function validateNodeConfig(nodeType, config, registry) {
  const node = registry.nodes.find(n => n.type === nodeType);
  if (!node || !node.configSchema) return { valid: true };

  const errors = {};
  for (const [fieldName, fieldSchema] of Object.entries(node.configSchema)) {
    const value = config[fieldName];
    
    // Validar required
    if (fieldSchema.required && (value === undefined || value === null)) {
      errors[fieldName] = 'Campo obrigatório';
      continue;
    }

    // Validar tipo
    if (value !== undefined && !validateType(value, fieldSchema.type)) {
      errors[fieldName] = `Tipo inválido. Esperado: ${fieldSchema.type}`;
      continue;
    }

    // Validar constraints específicas do tipo
    if (fieldSchema.type === 'string') {
      if (fieldSchema.minLength && value.length < fieldSchema.minLength) {
        errors[fieldName] = `Mínimo ${fieldSchema.minLength} caracteres`;
      }
      if (fieldSchema.maxLength && value.length > fieldSchema.maxLength) {
        errors[fieldName] = `Máximo ${fieldSchema.maxLength} caracteres`;
      }
      if (fieldSchema.pattern && !new RegExp(fieldSchema.pattern).test(value)) {
        errors[fieldName] = 'Formato inválido';
      }
    }

    if (fieldSchema.type === 'number') {
      if (fieldSchema.min !== undefined && value < fieldSchema.min) {
        errors[fieldName] = `Valor mínimo: ${fieldSchema.min}`;
      }
      if (fieldSchema.max !== undefined && value > fieldSchema.max) {
        errors[fieldName] = `Valor máximo: ${fieldSchema.max}`;
      }
      if (fieldSchema.integer && !Number.isInteger(value)) {
        errors[fieldName] = 'Deve ser um número inteiro';
      }
    }

    if (fieldSchema.type === 'enum' && !fieldSchema.enum?.includes(value)) {
      errors[fieldName] = `Valor deve ser um de: ${fieldSchema.enum.join(', ')}`;
    }
  }

  return {
    valid: Object.keys(errors).length === 0,
    errors
  };
}

Integração com React SDK

O React SDK do Triglit já utiliza o Node Registry internamente:
import { WorkflowEditor } from '@triglit/react-sdk';

function MyWorkflowEditor() {
  return (
    <WorkflowEditor
      apiKey="pk_sua_chave_aqui"
      // O SDK busca automaticamente o registry e renderiza os nodes
      onNodeAdd={(nodeType) => console.log('Node adicionado:', nodeType)}
    />
  );
}

Casos de Uso

1. Construção Dinâmica de UI

O registry permite construir interfaces de edição de workflows sem hardcoding de nodes:
  • Renderizar paleta de nodes baseada no registry
  • Gerar formulários de configuração automaticamente
  • Validar inputs em tempo real usando os schemas

2. Documentação Automática

Os schemas podem ser usados para gerar documentação automática:
  • Listar todos os nodes disponíveis
  • Mostrar exemplos de configuração
  • Explicar inputs e outputs esperados

3. Validação de Workflows

Antes de salvar um workflow, valide se:
  • Todos os nodes usados existem no registry
  • Configurações estão de acordo com os schemas
  • Conexões entre nodes são válidas (outputs compatíveis com inputs)

4. Extensibilidade

O registry facilita a extensão da plataforma:
  • Novos nodes built-in aparecem automaticamente
  • Nodes customizados são imediatamente disponíveis
  • Sem necessidade de atualizar o frontend para novos nodes

Boas Práticas

Cache

O registry pode mudar quando nodes customizados são criados, atualizados ou desativados. Implemente cache com invalidação apropriada.
Recomendações:
  • Cache o registry por alguns minutos (ex: 5-10 minutos)
  • Invalide o cache quando o usuário criar/editar nodes customizados
  • Considere usar ETags ou versionamento para detectar mudanças

Tratamento de Erros

import Triglit from 'triglit';

const client = new Triglit({ apiKey: 'pk_sua_chave_aqui' });

try {
  const registry = await client.customNodes.retrieveRegistry();
  // Usar registry
} catch (error) {
  if (error.status === 401) {
    // Chave de API inválida
  } else if (error.status === 429) {
    // Rate limit excedido
  } else {
    // Erro do servidor
  }
}

Performance

  • Busque o registry uma vez ao carregar o editor
  • Considere paginação se o número de nodes for muito grande (futuro)
  • Use lazy loading para nodes customizados se necessário

Exemplo Completo

import Triglit from 'triglit';
import type { CustomNodeRetrieveRegistryResponse } from 'triglit';

// service/node-registry.service.ts
class NodeRegistryService {
  private client: Triglit;
  private cache: CustomNodeRetrieveRegistryResponse | null = null;
  private cacheExpiry: number = 0;
  private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutos

  constructor(apiKey: string) {
    this.client = new Triglit({ apiKey });
  }

  async getRegistry(forceRefresh = false): Promise<CustomNodeRetrieveRegistryResponse> {
    const now = Date.now();
    
    if (!forceRefresh && this.cache && now < this.cacheExpiry) {
      return this.cache;
    }

    try {
      this.cache = await this.client.customNodes.retrieveRegistry();
      this.cacheExpiry = now + this.CACHE_TTL;
      return this.cache;
    } catch (error) {
      throw new Error(`Failed to fetch registry: ${error.message}`);
    }
  }

  invalidateCache() {
    this.cache = null;
    this.cacheExpiry = 0;
  }

  findNode(
    type: string, 
    registry: CustomNodeRetrieveRegistryResponse
  ): CustomNodeRetrieveRegistryResponse['nodes'][0] | undefined {
    return registry.nodes.find(node => node.type === type);
  }

  validateConfig(
    nodeType: string,
    config: Record<string, unknown>,
    registry: CustomNodeRetrieveRegistryResponse
  ): { valid: boolean; errors: Record<string, string> } {
    const node = this.findNode(nodeType, registry);
    if (!node || !node.configSchema) {
      return { valid: true, errors: {} };
    }

    const errors: Record<string, string> = {};
    
    for (const [fieldName, fieldSchema] of Object.entries(node.configSchema)) {
      const value = config[fieldName];
      
      // Validar required
      if (fieldSchema.required && (value === undefined || value === null)) {
        errors[fieldName] = 'Campo obrigatório';
        continue;
      }

      // Validar tipo
      if (value !== undefined && !this.validateType(value, fieldSchema.type)) {
        errors[fieldName] = `Tipo inválido. Esperado: ${fieldSchema.type}`;
        continue;
      }

      // Validar constraints específicas do tipo
      if (fieldSchema.type === 'string' && typeof value === 'string') {
        if (fieldSchema.minLength && value.length < fieldSchema.minLength) {
          errors[fieldName] = `Mínimo ${fieldSchema.minLength} caracteres`;
        }
        if (fieldSchema.maxLength && value.length > fieldSchema.maxLength) {
          errors[fieldName] = `Máximo ${fieldSchema.maxLength} caracteres`;
        }
        if (fieldSchema.pattern && !new RegExp(fieldSchema.pattern).test(value)) {
          errors[fieldName] = 'Formato inválido';
        }
      }

      if (fieldSchema.type === 'number' && typeof value === 'number') {
        if (fieldSchema.min !== undefined && value < fieldSchema.min) {
          errors[fieldName] = `Valor mínimo: ${fieldSchema.min}`;
        }
        if (fieldSchema.max !== undefined && value > fieldSchema.max) {
          errors[fieldName] = `Valor máximo: ${fieldSchema.max}`;
        }
        if (fieldSchema.integer && !Number.isInteger(value)) {
          errors[fieldName] = 'Deve ser um número inteiro';
        }
      }

      if (fieldSchema.type === 'enum' && !fieldSchema.enum?.includes(value)) {
        errors[fieldName] = `Valor deve ser um de: ${fieldSchema.enum?.join(', ')}`;
      }
    }

    return {
      valid: Object.keys(errors).length === 0,
      errors
    };
  }

  private validateType(value: unknown, type: string): boolean {
    switch (type) {
      case 'string':
        return typeof value === 'string';
      case 'number':
        return typeof value === 'number';
      case 'boolean':
        return typeof value === 'boolean';
      case 'any':
        return true;
      case 'object':
        return typeof value === 'object' && value !== null && !Array.isArray(value);
      case 'array':
        return Array.isArray(value);
      default:
        return false;
    }
  }
}

// Uso
const service = new NodeRegistryService('pk_sua_chave_aqui');
const registry = await service.getRegistry();
const node = service.findNode('input', registry);
const validation = service.validateConfig('input', { timeout: 30000 }, registry);

Referências

Use o Node Registry para construir editores de workflows dinâmicos e extensíveis, sem depender de hardcoding de nodes específicos.