Criando um Provedor de Nós Customizados (Enterprise)
Este guia mostra como criar um provedor HTTP de nós customizados para o QANode Enterprise. Um provedor é um servidor HTTP que implementa o contrato de API e pode ser escrito em qualquer linguagem.
Convenções
Nomenclatura de Arquivos
Arquivos de definição de nó devem usar a extensão .node. seguida da extensão da linguagem:
| Linguagem | Extensão |
|---|---|
| JavaScript | .node.js, .node.mjs, .node.cjs |
| Python | .node.py |
| Java | .node.java |
| C# | .node.cs |
| Go | .node.go |
Essa convenção distingue arquivos de nó (que exportam manifest e execute) de arquivos auxiliares (utils, helpers, etc.) que não devem usar .node. no nome.
Organização: Uma Pasta por Nó
Cada nó customizado deve ter sua própria pasta contendo:
- O arquivo de definição do nó (
.node.jsou equivalente) - Arquivos auxiliares (helpers, utils — sem
.node.no nome) package.jsonou equivalente para dependências próprias do nó
meu-provedor/
├── server.js # Servidor HTTP (auto-loader)
├── package.json # Dependências do servidor
├── validar-cpf/
│ ├── validar-cpf.node.js # Definição do nó
│ ├── cpf-utils.js # Auxiliar (NÃO usa .node.)
│ └── package.json # Dependências do nó
├── validar-cnpj/
│ ├── validar-cnpj.node.js # Definição do nó
│ ├── cnpj-utils.js # Auxiliar
│ └── package.json
└── gerar-dados/
├── gerar-dados.node.js # Definição do nó
├── generators.js # Auxiliar
└── package.json
Importante: Somente arquivos com
.node.no nome são reconhecidos como nós. Outros arquivos.jsna mesma pasta são ignorados pelo loader e podem ser usados livremente como módulos auxiliares.
Passo 1: Crie a Definição do Nó
Cada arquivo .node.js exporta manifest e execute:
// validar-cpf/validar-cpf.node.js
import { validateCPF } from './cpf-utils.js';
export const manifest = {
type: 'validar-cpf',
name: 'Validar CPF',
category: 'Validação',
// timeoutMs: 300000, // opcional — padrão: 30 min
inputSchema: {
cpf: {
type: 'string',
required: true,
description: 'CPF a ser validado (com ou sem formatação)'
}
},
outputSchema: {
valid: { type: 'boolean' },
formatted: { type: 'string' },
message: { type: 'string' }
}
};
export async function execute({ inputs, runId, nodeId }) {
const cpf = (inputs.cpf || '').replace(/\D/g, '');
const valid = validateCPF(cpf);
const formatted = cpf.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
return {
status: 'success',
logs: [`CPF recebido: ${inputs.cpf}`, `Válido: ${valid}`],
outputs: {
valid,
formatted,
message: valid ? 'CPF válido' : 'CPF inválido'
},
artifacts: []
};
}
Arquivo auxiliar (sem .node. no nome):
// validar-cpf/cpf-utils.js
export function validateCPF(cpf) {
if (cpf.length !== 11 || /^(\d)\1+$/.test(cpf)) return false;
let sum = 0, rest;
for (let i = 1; i <= 9; i++) sum += parseInt(cpf[i - 1]) * (11 - i);
rest = (sum * 10) % 11;
if (rest === 10 || rest === 11) rest = 0;
if (rest !== parseInt(cpf[9])) return false;
sum = 0;
for (let i = 1; i <= 10; i++) sum += parseInt(cpf[i - 1]) * (12 - i);
rest = (sum * 10) % 11;
if (rest === 10 || rest === 11) rest = 0;
return rest === parseInt(cpf[10]);
}
Passo 2: Crie o Servidor (Auto-Loader)
O servidor HTTP escaneia automaticamente as pastas de nós, detectando arquivos .node.js:
// server.js
import express from 'express';
import fs from 'fs';
import path from 'path';
import { pathToFileURL } from 'url';
const app = express();
app.use(express.json());
const nodes = [];
const executors = {};
// Auto-loader: busca arquivos *.node.js recursivamente
function findNodeFiles(dir) {
const results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...findNodeFiles(fullPath));
} else if (entry.name.endsWith('.node.js') || entry.name.endsWith('.node.mjs')) {
results.push(fullPath);
}
}
return results;
}
// Carrega todos os nós
const nodeFiles = findNodeFiles(path.resolve('.'));
for (const filePath of nodeFiles) {
try {
const mod = await import(pathToFileURL(filePath).href);
const manifest = mod.manifest || mod.default?.manifest;
const execute = mod.execute || mod.default?.execute;
if (manifest?.type && typeof execute === 'function') {
nodes.push(manifest);
executors[manifest.type] = execute;
console.log(`Loaded: ${manifest.name} (${manifest.type}) from ${path.basename(filePath)}`);
}
} catch (err) {
console.warn(`Failed to load ${filePath}: ${err.message}`);
}
}
// GET /manifest — lista os nós disponíveis
app.get('/manifest', (req, res) => {
res.json({ nodes });
});
// POST /execute — executa um nó
app.post('/execute', async (req, res) => {
const { nodeType, inputs, runId, nodeId } = req.body;
const executor = executors[nodeType];
if (!executor) {
return res.json({
status: 'failed',
logs: [],
outputs: {},
error: { message: `Nó não encontrado: ${nodeType}` }
});
}
try {
const result = await executor({ inputs, runId, nodeId });
res.json(result);
} catch (err) {
res.json({
status: 'failed',
logs: [err.stack || err.message],
outputs: {},
error: { message: err.message }
});
}
});
// GET /health — verificação de saúde (opcional)
app.get('/health', (req, res) => {
res.json({ ok: true, nodeCount: nodes.length });
});
app.listen(4010, () => {
console.log(`Provedor rodando em http://localhost:4010 (${nodes.length} nós carregados)`);
});
Passo 3: Teste Localmente
# Instale dependências de cada nó
cd validar-cpf && npm install && cd ..
cd validar-cnpj && npm install && cd ..
# Inicie o servidor
node server.js
# Teste o manifesto
curl http://localhost:4010/manifest
# Teste a execução
curl -X POST http://localhost:4010/execute \
-H "Content-Type: application/json" \
-d '{"nodeType": "validar-cpf", "inputs": {"cpf": "123.456.789-09"}}'
Passo 4: Registre no QANode
- No QANode, vá em Configurações → Provedores Customizados
- Clique em + Adicionar Provedor
- Preencha:
- Nome:
Validações - URL Base:
http://localhost:4010 - Token (opcional): Token de autenticação Bearer
- Nome:
- Clique em Testar Conexão → deve retornar sucesso com contagem de nós
- Salve
Passo 5: Use no Fluxo
- Abra o editor de fluxos
- Na paleta, localize a categoria Validação (ou Custom Nodes)
- Arraste o nó Validar CPF para o canvas
- Configure o campo CPF:
{{ steps.extract.outputs.extracts.cpf }} - Conecte ao fluxo e execute
Exemplo com Timeout Customizado
Para nós que executam operações longas, defina timeoutMs no manifesto:
// processar-lote/processar-lote.node.js
export const manifest = {
type: 'processar-lote',
name: 'Processar Lote',
category: 'Processamento',
timeoutMs: 1800000, // 30 minutos
inputSchema: {
dados: { type: 'string', required: true, description: 'Caminho ou JSON dos dados' },
formato: { type: 'string', enum: ['csv', 'json', 'xml'], default: 'json' }
},
outputSchema: {
processados: { type: 'number' },
erros: { type: 'number' },
resultado: { type: 'array' }
}
};
export async function execute({ inputs }) {
// Operação longa...
const resultado = await processarDados(inputs.dados, inputs.formato);
return {
status: 'success',
logs: [`Processados: ${resultado.length} registros`],
outputs: {
processados: resultado.length,
erros: 0,
resultado
},
artifacts: []
};
}
Se timeoutMs não for definido, o padrão é 30 minutos (300.000ms).
Exemplo em Outras Linguagens
A convenção .node. se aplica em qualquer linguagem. O importante é que o auto-loader do seu servidor saiba identificar os arquivos de nó:
Python
meu-provedor-python/
├── server.py # Servidor FastAPI
├── requirements.txt
├── validar-cpf/
│ ├── validar_cpf.node.py # Definição do nó
│ ├── utils.py # Auxiliar (sem .node.)
│ └── requirements.txt
└── gerar-dados/
├── gerar_dados.node.py
└── requirements.txt
# validar-cpf/validar_cpf.node.py
from .utils import validate_cpf_digits
manifest = {
"type": "validar-cpf",
"name": "Validar CPF",
"category": "Validação",
"inputSchema": {
"cpf": {"type": "string", "required": True}
},
"outputSchema": {
"valid": {"type": "boolean"}
}
}
def execute(inputs, run_id=None, node_id=None):
cpf = inputs.get("cpf", "").replace(".", "").replace("-", "")
valid = validate_cpf_digits(cpf)
return {
"status": "success",
"logs": [f"CPF: {cpf}, Válido: {valid}"],
"outputs": {"valid": valid},
"artifacts": []
}
Java / C# / Go
Siga a mesma estrutura: cada nó em sua pasta com o arquivo .node.{ext} e os auxiliares sem .node. no nome. O servidor HTTP da linguagem escolhida implementa o auto-loader que busca os arquivos .node.*.
Boas Práticas
- Uma pasta por nó — mantém dependências e auxiliares isolados
- Nomenclatura
.node.— apenas para arquivos de definição de nó, nunca para auxiliares package.jsonpor nó — cada nó gerencia suas próprias dependências- Logs descritivos — inclua logs que ajudem a diagnosticar problemas
timeoutMsno manifesto — defina para nós que excedem 30 minutos- Output Schema — defina
outputSchemapara que o QANode mostre os campos no autocomplete - Docker — considere containerizar o provedor para facilitar o deploy
Próximos Passos
- Contrato da API — Especificação completa dos endpoints
- Exemplos — Mais exemplos em várias linguagens
- Nós Locais Desktop — Alternativa sem servidor HTTP
