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
Identificador único do tipo de node (ex: "input", "transform", "custom-api-call"). Usado para referenciar o node ao criar workflows.
Nome legível do node exibido na interface do usuário (ex: "User Input", "Transform Data").
Descrição detalhada do que o node faz e quando deve ser usado.
Versão do node seguindo semantic versioning (semver), ex: "1.0.0".
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.
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
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"
}
}
}
}
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" }
}
}
}
}
}
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
Tipo do campo: "string", "number", "boolean", "any", "object", "array" ou "enum".
Descrição do propósito do campo.
Se o campo é obrigatório (padrão: false).
Valor padrão do campo se não fornecido.
Validações por Tipo
String
Comprimento mínimo (inclusivo).
Comprimento máximo (inclusivo).
Padrão regex para validação.
Number
Valor mínimo (inclusivo).
Valor máximo (inclusivo).
Se o número deve ser inteiro (sem decimais).
Object
Definição de propriedades do objeto (recursivo, cada propriedade é um NodeFieldSchemaDto).
Se propriedades adicionais (não definidas em properties) são permitidas.
Array
Schema dos itens do array (recursivo).
Número mínimo de itens (inclusivo).
Número máximo de itens (inclusivo).
Enum
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:
- Filtragem de built-in nodes: Remove nodes built-in desabilitados na configuração do tenant
- Traduções: Aplica traduções baseadas no idioma configurado do tenant
- 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
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
};
}
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
}
}
- 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.