Um SDK moldado pelo primeiro app que o usou

Vitor Ayres (abre em nova aba)

Desenho de plantas gerado a partir do primeiro commit do repositório mercos-sdk.
Plantas geradas pelo primeiro commit de mercos-sdk (6a33360).

Publicamos o mercos-sdk, um cliente em TypeScript para a API do Mercos, em 19 de setembro de 2026. No dia seguinte, o primeiro app que o usou, um app interno de estoque de um distribuidor, entregou uma lista de dez achados, cada um marcado como medido ou inferência, com uma sugestão para o SDK. Seis viraram mudança no mesmo dia.

Este texto registra o que esse primeiro uso mudou no desenho do SDK, o que recusamos e por quê, e o que o app adotou ou não. O que a API do Mercos faz está no post sobre a API do Mercos. Aqui o assunto é o processo.

Resumo

  • 10 versões em cerca de dois dias, da 0.1.0 à 0.4.3, com 34 commits.
  • O app anotou 10 achados, todos medidos no sandbox, cada um com uma sugestão.
  • A 0.4.0 respondeu a 6: gancho de observação, listagem por página, busca por id, criar e ler, intervalo mínimo, máscara de token e cliente congelado.
  • Dissemos não a duas partes dos pedidos: espaçamento adaptativo, e leitura por id no sandbox com listagem em produção.
  • O app não adotou tudo: criar e ler, e intervalo mínimo, ficaram de fora. A listagem por página só entrou 19 dias depois.
  • No npm, publicação confiável com fila de staging. A latest segue a ordem de aprovação, não o semver.
  • A 0.4.2 e a 0.4.3 ficaram 19 dias na fila de staging antes de serem aprovadas.

O ponto de partida: uma requisição por vez

A 0.1.0 tinha o que a documentação do Mercos e os nossos testes no sandbox pediam: fetch nativo sem dependências, uma fila única de requisições por cliente, repetição automática no 429 com teto de espera, paginação por alterado_apos com o cursor recuado para não perder registros do mesmo segundo, até duas repetições de leitura em falha de rede e nunca de escrita, um tipo de erro que normaliza os quatro formatos do campo erros, e tipos para as 169 operações documentadas.

A fila existe porque o limite de requisições do Mercos é global. Disparar em paralelo só disputa o mesmo limite. No 429, o SDK espera o tempo que a resposta pede, mais meio segundo.

O primeiro commit é de 19 de setembro à 1h07.

Os dez achados do app

Em 20 de setembro, o app sincronizou seis cadastros e enviou um pedido no sandbox, com a 0.1.0 e depois com a 0.3.0, e anotou o que viu:

# Achado Sugestão para o SDK
1 O Mercos soma o IPI do cadastro ao item do pedido documentar a conta
2 O ST do cadastro não entra no pedido documentar
3 “Sem valor” vem como 0 ou "", não como null normalizar ou documentar
4 O POST /v2/pedidos não devolve o total utilitário de leitura de volta
5 Ler pedido por id só existe no sandbox utilitário que cubra os dois ambientes
6 Quase toda chamada leva um 429 antes do 200 espaçar as requisições no cliente
7 A máscara de token altera os dados da resposta mascarar só mensagens
8 Não há como observar as requisições sem embrulhar o fetch gancho de observação
9 O cliente aceita ter um recurso sobrescrito congelar o objeto
10 Quantidade fracionada e item sem tabela são aceitos nada, só registro

Os achados 1, 2, 3 e 10 são comportamento da API, e ficaram como documentação. Dois dos outros merecem detalhe.

O achado 7 veio de um teste com o token "a". O SDK trocava o texto do token por *** em tudo que devolvia, e o campo total do pedido voltou como tot***l. Com tokens reais, longos e aleatórios, a colisão é improvável. Mesmo assim, a máscara reescrevia um JSON que a aplicação ia usar como dado.

O achado 9 foi erro do app, e por isso virou mudança no SDK. O app fez Object.assign sobre o cliente para embrulhar um recurso, o recurso novo chamava o antigo, e depois do assign a chamada apontava para ele mesmo. Recursão infinita, que só apareceu no envio real. Um cliente congelado falha na hora da escrita, com a causa ao lado.

O que entrou na 0.4.0, e o que mudou no caminho

A 0.4.0 saiu às 20h36 do mesmo dia e respondeu aos achados 4 a 9: createAndRead em pedidos, find em todo recurso, a opção minIntervalMs, a máscara só no que alimenta um erro, a opção onAttempt com listPages, e o cliente congelado.

Três pedidos mudaram de forma no caminho.

onRequest virou onAttempt. O app pediu um gancho onRequest ou onResponse. O nome ficou onAttempt porque um timeout não tem resposta, e o gancho dispara em toda tentativa, com método, rota modelo, status, duração, número da tentativa e a espera aplicada. O evento não carrega corpo, query nem cabeçalho, então um log dele não vaza token nem dado. E uma exceção dentro do gancho é engolida: um observador não pode quebrar uma requisição.

find lista sempre. O app sugeriu leitura por id no sandbox e listagem em produção, escondendo a diferença de ambiente. O SDK lista nos dois. Com um caminho só, o que o sandbox e a homologação exercitam é o que produção roda. O since é obrigatório, porque sem ele uma busca sem resultado percorreria o histórico inteiro da conta.

minIntervalMs não é adaptativo. O app sugeriu que o SDK aprendesse o intervalo a partir do retry-after do 429. Recusamos. O limite de produção pode ser outro, e um intervalo aprendido de um único 429 poderia deixar um cliente lento para sempre. Um número explícito é previsível. A medição no sandbox, seis leituras seguidas com cinco 429 contra zero com 5,5 segundos de intervalo, está no post sobre a API.

O que o app adotou, e o que não

Mudança O app adotou?
find sim
máscara só em erros sim, vem com a versão
onAttempt sim, no lugar do embrulho do fetch
cliente congelado sim, a porta virou um objeto novo em volta do cliente
createAndRead não: se a leitura falhar, o id do pedido se perde
minIntervalMs não: falta medir qual intervalo o Mercos aceita
listPages só em 9 de outubro, com a 0.4.3

A recusa do createAndRead tinha razão, e virou a segunda rodada.

Segunda rodada: 0.4.1 a 0.4.3

Com o onAttempt ligado, o app mediu mais quatro coisas no mesmo dia. O sandbox pede 5,5 segundos depois de cada requisição, e quatro leituras seguidas viraram sete requisições. O onAttempt não dizia por que não houve resposta, se timeout ou falha de rede. O since do find exigia o formato e o fuso de ultima_alteracao, que o app montava à mão. E o createAndRead não dizia o que acontece quando a leitura falha depois de o pedido existir.

A 0.4.1, às 21h19, respondeu a três dos quatro: o erro de createAndRead passou a carregar o id criado em createdId, o since passou a aceitar um Date convertido para o horário do Brasil, e o evento de onAttempt ganhou um campo error com "network" ou "timeout". O espaçamento adaptativo continuou recusado.

A 0.4.2, às 23h38, veio de um teste nosso: o cursor das listagens passou a sair com espaço em vez de T, porque a rota de divisões respondia 422 ao formato da documentação e todas aceitam o espaço. A 0.4.3, às 23h50, corrigiu o tipo de alteração de pedido: um item sem id era incluído de novo em vez de alterado, e o tipo não tinha o campo.

A tabela inteira, com a hora do commit no horário de Brasília e a publicação no npm em UTC:

Versão Commit npm Origem
0.1.0 19/09 18:56 19/09 22:03Z, à mão primeira versão
0.1.1 19/09 19:36 nunca campo vazio no erro de validação
0.1.2 19/09 19:57 19/09 23:00Z fila de staging
0.2.0 20/09 13:11 20/09 21:28Z estoque, categorias, formas de pagamento
0.3.0 20/09 18:18 20/09 21:28Z request, list e resource genéricos
0.3.1 20/09 18:47 20/09 23:39Z tipo do pedido e filtros
0.4.0 20/09 20:36 20/09 23:41Z seis achados do app
0.4.1 20/09 21:19 21/09 01:09Z segunda rodada
0.4.2 20/09 23:38 09/10 19:33Z cursor com espaço
0.4.3 20/09 23:50 09/10 19:34Z, latest id no item da alteração

Publicar no npm sem token

O pacote usa a publicação confiável do npm: nenhum token guardado, e o workflow de release prova a origem com OIDC. A primeira tentativa, a 0.1.1, recebeu 403 com “OIDC permission denied”. O publicador confiável tinha permissão só para mandar a versão para a fila de staging, não para publicar direto. A 0.1.1 ficou com tag e sem npm, e desde a 0.1.2 o fluxo é: a tag dispara o workflow, o workflow faz npm stage publish com atestado de procedência, e alguém aprova no site do npm, com 2FA. A publicação em staging existe desde maio de 2026. O staging exige npm 11.15, mais novo que o npm do Node 24.

Desde a 0.4.2, a release é um comando, pnpm release patch, que ajusta a versão, data o changelog, roda a verificação, commita, cria a tag assinada e envia branch e tag juntos.

A pegadinha é a latest. Ela vai para a última versão aprovada, sem olhar o semver. Em 20 de setembro, a 0.3.0 foi aprovada às 21:28:39 e a 0.2.0 seis segundos depois, então a latest ficou na 0.2.0 até a aprovação da 0.3.1, duas horas mais tarde. A regra ficou escrita no README: com duas versões na fila, aprovar na ordem das versões.

A fila também guarda o que ninguém aprova. A 0.4.2 e a 0.4.3 tiveram tag, Release no GitHub e workflow concluído em 20 de setembro, e ficaram em staging até 9 de outubro, quando foram aprovadas com 16 segundos de intervalo, na ordem certa. Durante 19 dias a latest foi a 0.4.1, e ninguém percebeu, porque o app que motivou tudo isso estava fixado na 0.4.0. No mesmo 9 de outubro ele passou para a 0.4.3 e adotou o listPages: o contador de requisições saiu, e com ele a requisição desperdiçada a cada corte de página.

Como verificamos

As horas dos commits vêm do git log do repositório, e as de publicação, do registro do npm, consultado em 9 de outubro de 2026, depois da aprovação das duas últimas versões. Os achados do app foram medidos no sandbox do Mercos em 20 de setembro, com o SDK 0.1.0, 0.3.0 e 0.4.0, e estão num documento do app com a marca de medido ou inferência em cada item.

O que não medimos: o limite de requisições em produção, o código de erro da leitura por id bloqueada em produção, e se o cursor com espaço teria aparecido no app.

O SDK tem licença MIT e não tem vínculo com a Mercos. O app é de um cliente e não é público; o que ele contornou para caber no plano grátis da Cloudflare está no post sobre as gambiarras.

Se a sua empresa precisa de um cliente para uma API que a documentação descreve pela metade, é esse o trabalho que fazemos em código aberto e em integrações.

Perguntas

Quanto tempo levou da primeira versão do mercos-sdk à 0.4.3?
Cerca de 47 horas. O primeiro commit é de 19 de setembro de 2026 à 1h07, e a release 0.4.3 é de 20 de setembro às 23h50. São 10 tags e 34 commits.
Quantos achados do app viraram mudança no SDK?
Dos 10 achados da primeira rodada, 6 entraram na 0.4.0. Da segunda rodada, 3 dos 4 entraram na 0.4.1. Os outros são comportamento da API e ficaram como documentação.
Por que a versão 0.1.1 não está no npm?
O publicador confiável do npm recebeu 403 ao publicar direto. Desde a 0.1.2, a versão vai para a fila de staging e alguém aprova no site do npm, com 2FA.
A tag latest do npm segue o semver?
Não. Ela segue a última versão aprovada na fila. Com duas versões na fila, aprovar na ordem errada deixa a latest numa versão mais antiga, como aconteceu com a 0.2.0 e a 0.3.0.
O app usa a versão mais nova do SDK?
Desde 9 de outubro de 2026, sim: ele passou da 0.4.0 para a 0.4.3 e trocou o contador de requisições pelo listPages que tinha pedido. Ficou 19 dias atrás da latest.

Como podemos ajudar?

Oi (abre em nova aba)