Pular para o conteúdo principal

Blueprint

O que é

Blueprint é o sistema de schemas declarativos do Hypergestor Integrations. Cada integração possui um arquivo .blueprint.yaml que serve como fonte única de verdade para IA, MCP, desenvolvedores e documentação.

A partir desse arquivo, geradores produzem automaticamente: contratos de validação, clients HTTP tipados, flowcharts de mapeamento, specs OpenAPI e tools MCP.

Motivação

Sem Blueprint, cada integração hardcoda credenciais, config, endpoints e mapeamentos. Isso causa:

  • Inconsistência: cada dev implementa de forma diferente
  • Opacidade para IA: modelos não conseguem entender o pipeline sem ler todo o código
  • Duplicação: mesma informação repetida em tipos, docs, testes e configs
  • Fragilidade: mudanças na API externa quebram silenciosamente sem validação

Blueprint resolve centralizando tudo em um YAML declarativo com geradores automáticos.

Estrutura do Blueprint

Cada integração possui um arquivo co-localizado:

hub/connectors/demo/
demo.blueprint.yaml # Fonte única de verdade
specs/
demo-api.yaml # OpenAPI spec da API externa
_gen/ # AUTO-GENERATED (pnpm blueprint:generate)
contracts.ts # Zod schemas + TS types
client.ts # createDemoClient() tipado
base-server.ts # BaseServer + Handler classes
handlers/ # Handler base classes
flow.md # Flowchart Mermaid dos handlers
openapi-internal.yaml # OpenAPI 3.1 — API interna (collect/process, execute)
openapi-external.yaml # OpenAPI 3.1 — API externa (endpoints do integrador)
mcp.json # MCP tools + resources (opcional)
e2e.test.ts # Testes e2e gerados
src/
server.ts # Estende BaseServer
handlers/ # Concretes dos handlers

Schema YAML

# hub/connectors/demo/demo.blueprint.yaml
name: Demo
key: demo
icon: flask
description: Integração de demonstração para testes e referência

openapi: ./specs/test-api.yaml

credentials:
- name: api_key
label: API Key
type: string
required: true
sensitive: true
- name: api_secret
label: API Secret
type: string
required: true
sensitive: true

config:
- name: base_url
label: URL Base
type: string
required: true
default: 'https://api.demo.com/v1'

handlers:
inbound:
get-leads:
description: Busca leads da API Demo para o CRM
trigger: pulling
schedule: 5m
endpoints: [get-leads]
working_data:
- name: lead_id
type: string
map: id
- name: customer_name
type: string
map: nome_completo
- name: lead_source
type: string
map: custom # requer implementação manual no handler
log_data:
- name: lead_id
map: id
- name: customer_name
map: nome_completo

list-vehicles:
description: Lista veículos disponíveis na API Demo
trigger: gateway
endpoints: [list-vehicles]

get-vehicle:
description: Busca detalhes de um veículo na API Demo
trigger: gateway
endpoints: [get-vehicle]

get-lead-status:
description: Consulta status de um lead na API Demo
trigger: gateway
endpoints: [get-lead-status]

outbound:
send-lead-update:
description: Envia atualização de lead do CRM para a API Demo
trigger: pulling
schedule: 10m
endpoints: [update-lead]
working_data:
- name: full_name
type: string
map: custom
- name: email
type: string
map: email
- name: phone
type: string
map: phone
log_data:
- name: full_name
map: custom

endpoints:
inbound:
get-leads:
openapi: GET /leads
list-vehicles:
openapi: GET /vehicles
get-vehicle:
openapi: GET /vehicles/{id}
get-lead-status:
openapi: GET /leads/{id}/status

outbound:
update-lead:
openapi: PUT /leads/{id}

Campos do Schema

CampoDescrição
nameNome de exibição da integração
keyIdentificador único (kebab-case)
iconÍcone para UI
descriptionDescrição da integração
credentialsCampos sensíveis (API keys, secrets) — marcados com sensitive: true
configConfigurações operacionais (URLs, limites) — com default opcional
portPorta HTTP do serviço da integração
handlersOrganizados por direção (inbound / outbound), cada um com trigger, schedule, endpoints, working_data e log_data
endpointsOrganizados por direção (inbound / outbound), cada um com url, method, headers, request/response

Trigger

O campo trigger define o modo de operação do handler — como ele é ativado.

ValorDescriçãoPipeline
pullingBusca dados ativamente no schedule (polling)collect → process (transform interno)
listenerEscuta eventos externos (webhook) — não tem collect, recebe dados passivamenteprocess (transform interno)
gatewayProxy síncrono — recebe request, chama API externa, retorna respostaexecute → transform (interno)

Handlers pulling requerem schedule. Handlers listener não têm schedule nem rota de collect.

Schedule

O campo schedule define o intervalo mínimo entre execuções de um handler pulling. O scheduler roda a cada 1 minuto e enfileira handlers cuja última execução foi há mais tempo que o schedule definido.

FormatoExemploComportamento
Intervalo5m, 10m, 1hRoda quando última execução >= intervalo
Horário fixo"08:00"Roda todos os tenants naquele horário
Múltiplos horários"08:00,16:00"Roda em cada horário listado

O schedule é propriedade da integração/handler, não do tenant. Todos os tenants com o handler habilitado seguem o mesmo schedule.

Direção dos Endpoints

DireçãoSignificadoExemplo
inboundIntegrador → Nós (buscamos dados do sistema externo)GET leads da API Demo
outboundNós → Integrador (enviamos dados para o sistema externo)PUT lead para API Demo

Field Mapping

Cada campo de working_data, request e response possui:

PropriedadeDescrição
nameNome ou path do campo (suporta . para nested e [] para arrays)
typeTipo: string, number, boolean, date, object, array
mapNome do campo de origem, ou custom para mapeamento manual no código
mockValor mock opcional para testes e modo simulação
nullabletrue se o campo pode vir null — gera .nullable() no Zod, | null em TS
hiddentrue para omitir o campo quando usado como override de endpoint openapi: (ver §)

Campos com map: custom requerem implementação manual no handler — o gerador produz um placeholder/TODO.

Notação de paths em Response

Campos de response suportam . para nesting de objetos e [] para arrays em qualquer nível. O parser trata ambos simetricamente — não é necessário declarar containers, só as folhas:

# Response é um array de objetos (API retorna [{id, nome}, ...])
response:
- name: '[].id'
type: string
- name: '[].nome'
type: string

# Objeto nested (não precisa declarar 'customer' separado)
response:
- name: customer.email
type: string
- name: customer.address.city
type: string

# Objeto com um campo que é array (não precisa declarar 'results[]')
response:
- name: total
type: number
- name: results[].id
type: string

# Arrays aninhados
response:
- name: items[].id
type: string
- name: items[].contacts[].phone
type: string

# Mix livre
response:
- name: '[].cliente.codigo'
type: number
- name: '[].servicos[].valor'
type: number
nullable: true

Regras:

  • name: 'campo' — leaf simples no objeto response
  • name: 'a.b'b vira folha dentro de a (objeto nested inferido)
  • name: 'campo[]' — array de objetos (container; raramente necessário declarar sozinho)
  • name: 'campo[].sub'sub é campo de cada item do array campo (suficiente, sem precisar do container)
  • name: '[]' / '[].campo' — a raiz da response é um array
  • A composição é recursiva e simétrica: items[].contacts[].phone ou customer.address.city ou items[].cliente.codigo
  • O primeiro separador que aparece no path decide o nível — a.b[].c nesta como objeto a → array b → folha c

O gerador produz tipos separados para arrays raiz:

// Para response com [] raiz:
export const GetLeadsResponseItemSchema = z.object({ id: z.string(), ... });
export type GetLeadsResponseItem = z.infer<typeof GetLeadsResponseItemSchema>;
export const GetLeadsResponseSchema = z.array(GetLeadsResponseItemSchema);
export type GetLeadsResponse = z.infer<typeof GetLeadsResponseSchema>;

Tipagem dos Handlers

Os tipos CollectResponse, TransformResponse e ProcessResponse do @hg/core suportam generics:

CollectResponse<TPayload = unknown>   // items[].payload tipado
TransformResponse<TMapped = unknown> // mapped tipado
ProcessResponse // sucesso/erro (sem generic)

O gerador usa os tipos do blueprint para tipar os base handlers automaticamente:

HandlerMétodoReturn TypeBody Cast
InboundcollectCollectResponse<EndpointResponseItem>
InboundtransformTransformResponse<WorkingData>payload: EndpointResponseItem (chamado internamente pelo /process)
OutboundcollectCollectResponse (payload do CRM, sem tipo)
OutboundtransformTransformResponse<WorkingData>payload: Record<string, unknown> (chamado internamente pelo /process)
AmbosprocessProcessResponseRecebe { payload }, aplica transform internamente, depois processa com { mapped }
GatewayexecuteGatewayResponse{ query, body } — retorno passa por transform antes de devolver

Geradores

Contracts (pnpm blueprint:generate:contracts)

Gera schemas Zod + tipos TypeScript para cada endpoint, handler e config:

// generated/contracts.ts (AUTO-GENERATED)
import { z } from 'zod';

export const DemoCredentialsSchema = z.object({
api_key: z.string(),
api_secret: z.string(),
});
export type DemoCredentials = z.infer<typeof DemoCredentialsSchema>;

// Endpoint com response [] gera Item + array
export const GetLeadsResponseItemSchema = z.object({
id: z.string(),
nome_completo: z.string(),
email: z.string(),
telefone: z.string(),
});
export type GetLeadsResponseItem = z.infer<typeof GetLeadsResponseItemSchema>;
export const GetLeadsResponseSchema = z.array(GetLeadsResponseItemSchema);
export type GetLeadsResponse = z.infer<typeof GetLeadsResponseSchema>;

export const GetLeadsWorkingDataSchema = z.object({
lead_id: z.string(),
customer_name: z.string(),
lead_source: z.string(),
});
export type GetLeadsWorkingData = z.infer<typeof GetLeadsWorkingDataSchema>;

Client (pnpm blueprint:generate:client)

Gera factory function com métodos tipados, mapping automático e suporte a mock:

// generated/client.ts (AUTO-GENERATED)
export function createDemoClient(options: { credentials: DemoCredentials; config: DemoConfig; mock?: boolean }) {
return {
async getLeads() {
/* ... */
},
async updateLead(data: UpdateLeadRequest) {
/* ... */
},
async listVehicles() {
/* ... */
},
async getVehicle(id: string) {
/* ... */
},
async getLeadStatus(id: string) {
/* ... */
},
};
}
  • Métodos derivados dos endpoint keys: get-leadsgetLeads()
  • Template interpolation: {{credentials.api_key}} resolvido em runtime
  • Validação Zod em requests e responses
  • mock: true retorna dados dos campos mock do schema

Flow (pnpm blueprint:generate:flow)

Gera flowcharts Mermaid por handler mostrando o pipeline completo:

OpenAPI (pnpm blueprint:generate:openapi) — opcional

Gera spec OpenAPI 3.1 descrevendo a API externa que a integração consome.

Override de fields via OpenAPI spec

Quando um endpoint usa openapi: METHOD /path, o request e response são resolvidos a partir do spec. Se o spec está errado (tipo trocado, campo faltando, campo que não queremos expor), declare só os ajustes no YAML — o merge é patch por name literal, não substituição.

openapi: ./specs/foo-api.yaml

endpoints:
inbound:
get-agendamento:
openapi: GET /v1/agenda/{codEmpresa}
response:
- name: 'servicos[].valor'
type: number # corrige tipo trocado no spec
- name: 'cliente.cpf'
hidden: true # omite do output
- name: 'cliente.telefone'
nullable: true # spec não marcou mas a API devolve null
- name: 'extra_field'
type: string # campo que o spec esqueceu — adiciona

Regras do merge:

  • Match por name (literal, mesma grammar das folhas — cliente.codigo, servicos[].valor, results[].customer.email): chaves declaradas sobrescrevem as do spec; chaves omitidas preservam o resolvido.
  • Sem match no spec: o field é adicionado, silenciosamente.
  • hidden: true: remove o field. Cascata implícita — hidden: true em cliente também remove cliente.codigo, cliente.nome, cliente.endereco.rua, etc.

Isto só vale quando o endpoint tem openapi:. Sem a ref, request/response continua sendo a definição completa e obrigatória.

Null-safety

Quando o spec declara nullable: true (OpenAPI 3.0) ou type: ['string', 'null'] (3.1), o gerador propaga automaticamente: Zod emite .nullable(), TS emite | null. Se o spec está incompleto, marque manualmente no override com nullable: true.

openapi_fixes — normalização do spec

Para specs com malformações estruturais comuns, há fixes opt-in no root do blueprint:

openapi: ./specs/foo-api.yaml
openapi_fixes:
- untyped_array_items
  • untyped_array_items — trata schema com items mas sem type como type: array; desfaz o idiom invertido { items: { type: "array", oneOf: [ref] } } (observado no spec da NBS).

Cada fix aplicado sai como warning no console com o path exato, tornando o patching visível e escopado por integração.

MCP (pnpm blueprint:generate:mcp) — opcional

Gera definições MCP (Model Context Protocol):

  • Tools por endpoint: demo_get_leads, demo_update_lead, demo_list_vehicles com input/output schemas
  • Resources com mapeamentos: metadata dos field mappings por tool
  • Flow resource (demo_describe_flow): flowchart completo por handler — permite que a IA entenda o pipeline, identifique campos custom e debugue problemas de dados sem ler código

Comandos

ComandoDescrição
pnpm blueprint:validateValida todos os .blueprint.yaml
pnpm blueprint:generateGera contracts + client + flow (padrão)
pnpm blueprint:generate:contractsGera apenas Zod schemas + types
pnpm blueprint:generate:clientGera apenas client HTTP tipado
pnpm blueprint:generate:serverGera apenas BaseServer + Handler classes
pnpm blueprint:generate:flowGera apenas flowcharts Mermaid
pnpm blueprint:generate:openapiGera apenas OpenAPI spec
pnpm blueprint:generate:mcpGera apenas MCP tools + resources
pnpm blueprint:testTesta todas as integrações (sobe mocker)
pnpm blueprint:test demoTesta uma integração específica
pnpm blueprint:mockerSobe mock server da API externa

CLI descobre blueprints via glob hub/connectors/*/*.blueprint.yaml ou aceita path específico. Aceita nome curto (ex: demo resolve para hub/connectors/demo/demo.blueprint.yaml).

Server (pnpm blueprint:generate:server)

Gera classes base para handlers e servidor Fastify:

// .gen/handlers/base-get-leads.ts (AUTO-GENERATED)
import type { CollectResponse, TransformResponse, ProcessResponse } from '@hg/core';
import type { GetLeadsResponseItem, GetLeadsWorkingData } from '../contracts.js';

export abstract class BaseGetLeads {
http!: HttpClient;
endpoint!: DemoClient;

async collect(_req: FastifyRequest): Promise<CollectResponse<GetLeadsResponseItem>> {
return { items: [] };
}
async transform(req: FastifyRequest): Promise<TransformResponse<GetLeadsWorkingData>> {
const { payload } = req.body as { payload: GetLeadsResponseItem };
const mapped: GetLeadsWorkingData = {
lead_id: payload.id,
customer_name: payload.nome_completo,
lead_source: undefined!, // custom — requer override
};
return { mapped };
}
async process(_req: FastifyRequest): Promise<ProcessResponse> {
return { success: true };
}
}

O desenvolvedor estende as classes para customizar apenas o necessário:

// src/handlers/get-leads.ts
import { BaseGetLeads } from '../../.gen/handlers/base-get-leads.js';

export class GetLeads extends BaseGetLeads {
async collect(_req: FastifyRequest) {
const leads = await this.endpoint.getLeads();
return { items: leads.map((lead) => ({ externalId: lead.id, payload: lead })) };
}
// transform() usa auto-mapping da classe base
// process() customizado para gravar no CRM
}

Cada handler é uma classe com métodos collect, transform e process (standard) ou execute e transform (gateway). O transform() gerado aplica automaticamente os mapeamentos de campos definidos no blueprint (exceto campos map: custom, que requerem override manual). O transform não é um endpoint HTTP — é chamado internamente pelo serviço dentro da rota /process (standard) ou /execute (gateway). Os tipos fluem da base class — o override herda as assinaturas tipadas sem precisar de anotação explícita.

Package: @hg/blueprint

packages/blueprint/
src/
cli.ts # Entry point dos scripts pnpm
loader.ts # loadBlueprint() — parse YAML + validação
validator.ts # Meta-schema Zod que valida .blueprint.yaml
derive-meta.ts # deriveIntegrationMeta() — blueprint → IntegrationMeta
mocker.ts # Mock server Fastify que simula API externa
test-runner.ts # Test runner genérico — sobe mocker, testa handlers
test-reporter.ts # Formatação de resultados no terminal
generators/
contracts.ts # Gera Zod schemas + TS types
client.ts # Gera client HTTP tipado com mapping + mock
server.ts # Gera BaseServer + Handler classes
flow.ts # Gera flowchart por handler (Mermaid)
openapi.ts # Gera OpenAPI 3.1 spec
mcp.ts # Gera MCP tools + resources
utils/
response-tree.ts # Parse response fields com notação [] → árvore nested
interpolation.ts # Resolve {{credentials.x}}, {{config.y}}
type-mapping.ts # SchemaFieldType → Zod / TS / OpenAPI type
naming.ts # toPascalCase, toCamelCase
handlers.ts # flattenHandlers() — merge inbound/outbound
mock-builder.ts # buildMockPayload/Config/Credentials a partir do blueprint
runtime-schema.ts # Constrói Zod schemas em runtime para validação

Environments

Cada integração define seus environments no blueprint YAML. Environments permitem separar URLs, config e credentials por ambiente (homolog, prod, mock), com overrides por tenant.

Definição no Blueprint

environments:
environments:
- key: homolog
label: Homologação
default: true
urls:
api: 'https://homologacao.example.com/v1'
token: 'https://homologacao.example.com/token'
- key: prod
label: Produção
urls:
api: 'https://api.example.com/v1'
token: 'https://api.example.com/token'
url_fields:
- name: api
label: URL Base da API
- name: token
label: URL de Autenticação
configurable: true # permite o tenant sobrescrever URLs

Um environment mock é gerado automaticamente apontando para localhost:4900 (mock server compartilhado).

Resolução de Configuração

Quando o gateway ou runner executa um handler, URLs/config/credentials são resolvidos em 4 camadas (prioridade decrescente):

PrioridadeFonteLocalDescrição
1Overridehub_config_overrides (tenant)Valores específicos por environment e tenant
2Config basehub_configs (tenant)Valores base do tenant (flat, sem segmentação)
3Blueprint YAML*.blueprint.yamlplt_typesDefaults por environment definidos no blueprint
4OpenAPI specspecs/*.yamlURLs extraídas da spec (fallback estrutural)

Merge por campo: override[campo] ?? config_base[campo] ?? blueprint_default[campo].

Banco de Dados

  • plt_types.environments (public) — definição de environments e url_fields, registrada via pnpm db:prepare
  • hub_configs.environment (tenant) — environment ativo do tenant, sem default (obrigatório selecionar)
  • hub_config_overrides (tenant) — overrides por environment, permite múltiplos por env quando multiple: true

Sem environment selecionado, gateway retorna 400.

Funções de Resolução (@hg/core)

  • resolveEnvironmentUrls(base, type, environment, override?) — merge URLs
  • resolveEnvironmentConfig(base, type, environment, override?) — merge config
  • resolveEnvironmentCredentials(base, type, environment, override?) — merge credentials

Mock Server

hub/mocker/ — sobe todos os connectors num único Fastify na porta 4900. Inclui endpoints OAuth token mock (POST /, /token, /v1/token) e registra rotas com e sem prefixo de slug. Iniciado via nx run @hg/hub-mocker:dev.

Integração com Arquitetura Existente

  • Runner não muda — exceto passar credentials e config no body das chamadas HTTP
  • IntegrationMeta derivado do blueprint via deriveIntegrationMeta(blueprint) — embutido no generated/server.ts
  • BaseServer gerado registra rotas Fastify e instancia handlers — src/server.ts estende e customiza
  • Handler classes geradas com collect(), transform(), process() (standard) ou execute(), transform() (gateway) — dev estende apenas o que precisa. Transform é método interno, não endpoint HTTP
  • Handlers usam o client gerado em vez de fetch manual
  • Contracts validam nas fronteiras (response da API externa, working_data no map)
  • Integrations continuam stateless — credentials/config recebidos via request

Dependências

PackageOndeMotivo
zod@hg/coreRuntime validation dos contracts
yaml@hg/blueprintParse dos .blueprint.yaml
glob@hg/blueprintDescoberta de blueprints