Ir para o conteúdo

Changelog

Todas as mudanças relevantes deste projeto são documentadas aqui. O formato segue Keep a Changelog e o projeto adota SemVer.

Histórico completo

Esta página lista os destaques recentes. O histórico versão a versão (0.2.0–0.11.0) vive no CHANGELOG.md do repositório.

[0.32.0] — 2026-09-06

Adicionado

  • asyncapi: createAsyncApiRegistry() documenta a superfície WebSocket como um documento AsyncAPI 3.0, servido ao lado do /openapi.json.

O OpenAPI descreve uma requisição e a resposta dela. Socket não tem esse formato — a conexão fica aberta, as mensagens vão nos dois sentidos e o servidor fala sem ninguém pedir —, então rota de socket era documentada em prosa, e prosa não gera cliente. O registry espelha o de OpenAPI: registre o canal, as mensagens e as operações, e o createApp({ asyncapi: { registry, info } }) serve o resultado em /asyncapi.json.

const asyncapi = createAsyncApiRegistry()
  .registerChannel({ name: "socket", address: "/ws", handshakeHeaders })
  .registerMessage({ name: "SubscribeFrame", schema: subscribeFrame })
  .registerOperation({
    name: "subscribe",
    channel: "socket",
    direction: "clientToServer",
    messages: ["SubscribeFrame"],
  });

A direção é declarada, nunca inferida. O action do AsyncAPI é relativo a quem publicou o documento, então um documento escrito pelo servidor grafa o envio do cliente como receive. Um consumidor gerado inverte todos eles, e o sinal errado produz um cliente que compila, passa no type-check e faz o oposto. Por isso o registry aceita clientToServer / serverToClient, que não têm como ser lidos de trás para frente, e emite o action sozinho. O documento carrega também x-tempest-perspective: "server", para nenhum leitor precisar supor.

O payload de cada mensagem é gerado dos schemas zod do chamador, pelo mesmo caminho que alimenta o OpenAPI — então a forma documentada e a forma que o servidor aceita são um objeto só, não dois que podem divergir. Operação que nomeia canal ou mensagem não registrada levanta na geração: um $ref pendurado produz documento estruturalmente válido cujo cliente gerado fica sem aquele frame, em silêncio.

Os headers do handshake são embutidos no binding ws em vez de referenciados. A especificação tipa bindings.ws.headers como oneOf: [Schema, Reference], e um {"$ref": ...} puro satisfaz os dois ramos — então o oneOf vê duas correspondências e o documento falha no próprio JSON Schema do AsyncAPI. Medido das duas formas contra o meta-schema oficial: embutido valida com zero erros.

Exports novos: createAsyncApiRegistry, generateAsyncApiDocument, mountAsyncApiJson, AsyncApiRegistry, ASYNCAPI_VERSION, PERSPECTIVE_EXTENSION e os tipos AsyncApi*. O createApp ganha a opção asyncapi ao lado de openapi. Receita: AsyncAPI: documentando o WebSocket.

[0.31.0] — 2026-09-05

Corrigido

  • api: registry.register() não depende mais da ordem de avaliação de módulo. O zod-to-openapi adiciona .openapi() por patch de ZodType.prototype, e o zod v4 copia os membros do protótipo para dentro da instância na construção — então um schema construído antes do módulo do SDK ser avaliado nunca recebia o patch, e registrá-lo estourava TypeError: zodSchema.openapi is not a function de dentro de node_modules. Como declarar schemas em schemas/*.ts e importar o SDK só na camada de docs é a ordem natural, a ordem que quebrava era a comum — e a falha caía no boot.

createOpenApiRegistry() agora devolve um registry que re-marca esse schema com .meta({ id }) — o que constrói uma instância nova, já patchada — antes de repassar para a biblioteca. O mesmo call site funciona nas duas ordens, e o registerParameter é normalizado do mesmo jeito. Valor que não é nem schema estendido nem schema zod v4 passa a falhar com uma mensagem que nomeia a causa e aponta para npm ls zod, em vez de um TypeError vindo de dependência. Closes #19.

Adicionado

  • api: extendZodWithOpenApi passa a ser reexportado, para quem precisa de .openapi() nas próprias instâncias de schema (parâmetro com param, extend com anyOf) aplicar o patch no próprio entrypoint sem declarar @asteasolutions/zod-to-openapi como dependência direta e ter que manter a versão em sincronia com a do SDK na mão.

Alterado

  • docs: a receita de OpenAPI passa a ensinar .meta({ id }) como o jeito padrão de nomear um componente. É nativo do zod v4, imune à ordem de import, e marca o próprio schema — então qualquer rota que o importe emite $ref sem precisar carregar um valor de retorno. O register() fica com seção própria, com o aviso de que o valor devolvido é a cópia marcada: rota que referencia a variável original sai com corpo inline, que foi como um gateway terminou com 18 rotas e nenhum componente. O z reexportado passa a ser documentado como a instância já estendida.

[0.30.0] — 2026-09-05

Adicionado

  • tasks: TaskManager.register aceita um { description, schedule } opcional e TaskManager.inventory() informa o que este processo rodaria, ordenado por nome. O schedule é registrado e exibido, nunca interpretado — o manager consome fila, ele não agenda.

  • tasks: BaseJobModel e JobStore — uma linha persistida por unidade de trabalho longo, para as perguntas que um broker não responde (o export de ontem terminou? por que aquele import falhou? o que está rodando agora?) terem onde morar. Cada transição é um método, não um update cru, então "terminou" sempre move as mesmas colunas juntas, e o cancel recusa run já em estado terminal em vez de reescrevê-lo por baixo do operador. O store não é acoplado ao TaskManager de propósito: nem toda mensagem enfileirada merece linha durável, e o worker que escreve uma normalmente quer campos de domínio que o envelope nunca carregou.

  • admin: a página de tasks. makeAdminRouter({ tasks }) serve {prefix}/tasks com as duas metades — o inventário declarado e os runs registrados, com filtro por status e nome — mais a tela por run com payload, resultado, erro e tentativas, e botão Cancel enquanto o run não está terminal. Qualquer uma das metades pode ser omitida, e seção sem fonte fica de fora em vez de renderizar vazia. O que a página deliberadamente não mostra é profundidade de fila: nenhum broker expõe isso, e um número que parecesse essa resposta seria pior que nenhum.

  • admin: a folha de estilo embutida ganhou as cores de badge de status de job, numa constante própria para o port base continuar cópia verbatim re-sincronizável.

[0.29.0] — 2026-09-05

Adicionado

  • admin: a página de logs. makeAdminRouter({ logDir }) lê os arquivos JSON estruturados que o configureFileLogging escreve e os serve em {prefix}/logs — com filtro por fonte e por busca, paginado, mais recente primeiro, com badge colorido por nível. Registro com traceback vira um <details> recolhido cuja summary é a mensagem, então página cheia de 500 continua escaneável sem JavaScript. A busca casa mensagem, logger e traceback, porque quem caça um 500 costuma ter um pedaço do trace. {prefix}/logs/export?format=md|json baixa a mesma seleção filtrada (máximo 500 registros): o markdown põe cada traceback num bloco cercado e declara quantos dos casamentos ele carrega, para export parcial nunca se passar por completo. A página é opt-in — sem logDir responde 404, porque o payload expõe traceback e metadado de request.

  • admin: o SQL console, desligado por padrão. makeAdminRouter({ sqlConsole }) serve {prefix}/sql atrás de uma política: capabilities (read / insert / update / delete / ddl / drop / admin), allowlist e denylist de tabela, recusa de UPDATE/DELETE sem WHERE, e teto de linhas. Statement é classificado por parse (node-sql-parser, novo peer opcional), não por match de string, e o que o parser não entende exige a capability admin — a mais privilegiada, não a menos. Toda tentativa, permitida ou recusada, chega ao onAudit; hook que levanta é logado e engolido, já que trilha de auditoria que derruba o que audita acaba desligada.

A documentação diz sem rodeio o que isso é: defesa em profundidade, não fronteira de segurança. A fronteira que segura é o usuário do banco, e sqlConsole.run existe para o console poder ser apontado a um papel restrito.

analyzeSql, checkSqlPolicy, SqlCapability e os helpers de log (toLogEntry, filterLogEntries, renderLogEntriesMarkdown, renderLogEntriesJson) ficam exportados para quem monta as próprias telas.

  • api: readLogEntries(dir, source) passa a ser exportado — o leitor que o endpoint JSON de logs e a página do admin agora compartilham, para os dois nunca discordarem sobre o que "o log de erro" contém.

[0.28.0] — 2026-09-05

Adicionado

  • admin: inlines — models filhos exibidos na tela de detalhe do pai. Entradas adminInline({ model, fkField }) passadas em AdminModel({ inlines }) renderizam as linhas que apontam para o registro: uma tabela somente leitura com link para o admin do filho ou — com editable — um formset in-place com uma linha de inputs por filho mais uma linha em branco, salvos num submit só que cria, edita e exclui de uma vez. canDelete adiciona o checkbox de exclusão por linha, condicionado à flag do admin do filho.

A coluna que aponta para o pai fica fora do formset, e toda linha submetida é verificada como pertencente a este pai antes de ser gravada ou excluída: chave de linha vem do navegador, então uma submissão forjada poderia nomear o filho de outro pai e transformar a página em superfície de edição da tabela inteira. Linha em branco é ignorada, e linha que falha na validação volta com os valores submetidos e o erro por campo, enquanto o resto do submit é salvo.

groupInlineSubmission fica exportado para quem faz o parse da mesma convenção row.<chave>.<coluna> por conta própria.

[0.27.0] — 2026-09-05

Adicionado

  • admin: upload de arquivo e imagem. AdminModel({ uploadFields, uploadStorage }) renderiza essas colunas String como input de arquivo, muda o formulário para multipart/form-data, grava o arquivo pelo backend de storage e guarda a chave devolvida (<slug>/<campo>/<uuid>.<ext>). No create o arquivo só é obrigatório para coluna NOT NULL sem default; no edit, não enviar arquivo mantém o atual em vez de limpar a coluna. Registrar uploadFields sem uploadStorage levanta erro na construção, porque falhar no boot é melhor que falhar no primeiro upload de produção. makeAdminRouter({ maxUploadBytes }) limita o tamanho (default 10 MB).

  • admin: import CSV. AdminModel({ canImport: true }) expõe GET/POST {prefix}/m/{slug}/import, que cria linhas em massa a partir de um CSV UTF-8 enviado. Cada linha passa pela mesma coerção e validação do formulário; as que falham voltam numa tabela numerada como a planilha numera (começando em 2, já que a linha 1 é o header) com o motivo, e as que passaram são criadas — import parcial é resultado normal, não erro. O parseCsv exportado segue a RFC 4180 (vírgula entre aspas, quebra de linha embutida, aspas duplicadas) e remove o BOM que o Excel escreve.

  • admin: autocomplete de chave estrangeira. AdminModel({ autocompleteFields }) transforma a FK numa caixa de busca servida por GET {prefix}/m/{slug}/autocomplete/{campo}?q=, que consulta os searchFields do admin referenciado e devolve até 20 opções — dispensando o pré-carregamento de 1000 linhas que um <select> exige. Na edição, a caixa abre com o rótulo da linha atual, não com o id. Onde o SDK Python puxa HTMX de CDN, aqui vão ~30 linhas de DOM puro: script de terceiro num console de operador é requisição que deploy fechado não faz e CSP estrita precisa liberar.

  • deps: busboy entra como peer opcional, exigido só por quem configura uploadFields ou canImport; o erro diz o comando de instalação. O Express não faz parse de multipart, e parser de formato de fio é o caso de depender em vez de reimplementar. parseMultipart / isMultipart / MultipartLimitError são exportados para quem precisa do mesmo tratamento.

Corrigido

  • admin: createdBy / updatedBy não são mais oferecidos como campo de formulário. O painel os carimba sozinho, então valor digitado ali era descartado em silêncio no submit — campo que não faz nada é pior que campo nenhum.

[0.26.0] — 2026-09-05

Adicionado

  • admin: controle de acesso por papel. makeAdminRouter({ accessPolicy }) recebe um predicado (principal, admin, action) consultado em toda ação de model. Ele compõe com as flags canCreate / canEdit / canDelete — não as substitui — e o painel esconde o que recusa: model sem VIEW some da sidebar e do dashboard, ação recusada sai do dropdown de massa, e os botões + New / Edit / Delete só aparecem quando a política deixa. Flag desligada responde 404 (a view não existe); política recusando responde 403 (existe, e você não pode usá-la) — juntar os dois esconderia configuração errada atrás de um "sem permissão".

  • admin: trilha de auditoria. Create e edit carimbam createdBy / updatedBy com o operador quando o model declara essas colunas, e o detail ganha um painel Audit com os timestamps e os atores já resolvidos para nome pelo auth backend. Passando AdminModel({ auditModel }), o painel também renderiza a timeline de mudanças do registro lida daquela tabela BaseAuditLogModel — ação, ator, o diff campo a campo e o contexto gravado, cada entrada num <details> recolhido para histórico longo continuar escaneável sem JavaScript. O painel só lê a trilha; quem escreve continua sendo o service.

  • admin: cards de métrica no dashboard. new AdminSite({ dashboardCards }) recebe entradas metricCard(label, compute, helpText?) calculadas do banco no carregamento, em três formatos: value, trend (▲/▼ com a variação percentual) e partition (uma barra por segmento). Card cujo compute levanta é logado e renderiza como card de erro, em vez de derrubar o dashboard. trendPercent devolve null contra base zero — porcentagem contra zero é indefinida, não infinita.

  • admin: lenses — presets salvos de listagem. Entradas adminLens({ name, filters, orderBy }) passadas em AdminModel({ lenses }) viram abas acima da tabela e aplicam via ?lens=<slug>. Os filtros da lens são ANDados com o que o operador digitou, a ordenação dela vale até alguém clicar num cabeçalho de coluna, e a lens ativa viaja nos links de paginação, ordenação e export.

Alterado

  • admin: o detail move createdAt / updatedAt / createdBy / updatedBy da lista de campos para o novo painel de auditoria, onde "quem e quando" lê melhor ao lado do histórico do que espalhado entre os campos de domínio. AdminModel.detailFieldNames() não os devolve mais; o novo auditFieldNames() devolve.

[0.25.0] — 2026-09-05

Adicionado

  • admin: ações em massa na list view. Cada linha ganha um checkbox mais um "selecionar tudo", e a barra de ação aplica Activate / Deactivate (condicionadas a canEdit e à coluna isActive) ou Delete (condicionada a canDelete) às linhas marcadas, informando quantas mudaram. Toda submissão carrega o token CSRF da sessão.

  • admin: ações customizadas com adminAction({ label }, handler), passadas em AdminModel({ actions: [...] }). Cada uma entra no dropdown com o namespace custom:<nome>, então nunca colide com uma ação embutida. O handler recebe os ids marcados, um repository na sessão do request, a sessão de banco, o request, a sessão do admin e o operador que disparou, e devolve { message, category } para exibir um banner (ou null para nenhum). Handler que levanta é registrado no log e volta como banner de erro, não como 500.

Onde o SDK Python usa o decorator @admin_action, aqui adminAction devolve o descritor: o handler continua uma função comum, chamável e testável (action.handler(ctx)), sem sintaxe de decorator para ligar no build de quem consome.

  • admin: export CSV / JSON em GET {prefix}/m/{slug}/export.csv|json, respeitando busca, filtros, ordenação e as colunas do listDisplay do request — list view e export agora resolvem a query por um caminho de código só, porque export que discorda em silêncio da página de onde saiu é pior que export nenhum. O CSV segue a RFC 4180; Date vira ISO, bigint vira string decimal, binário vira base64. makeAdminRouter({ exportMaxRows }) limita as linhas (default 5000), para um clique numa tabela grande não virar incidente.

  • admin: select de chave estrangeira. Coluna FK cujo model de destino está registrado no mesmo site vira <select> das linhas relacionadas, no formulário e no filtro da listagem, com label do primeiro searchFields do admin referenciado (caindo para name/title/email/label/reference e, por último, a identidade). FK para tabela não registrada continua input de texto — dropdown vazio seria pior que o campo cru. Opções limitadas a 1000 linhas. Os helpers foreignKeyFields, foreignKeyLabel e foreignKeyTable passam a ser exportados para quem monta as próprias telas.

[0.24.0] — 2026-09-05

Adicionado

  • admin: um painel admin server-rendered — a UI de operador no estilo Django que o SDK FastAPI entrega, agora neste SDK. AdminSite guarda o registro, AdminModel configura um model, e makeAdminRouter(site, { engine, authBackend, secretKey }) monta login, dashboard, list views (busca, filtros por tipo de coluna, colunas ordenáveis, paginação) e formulários de criar/editar/excluir auto-derivados sob /admin.

Widget, filtro e validação saem da metadata de coluna do tempest-db-js (columnsOf), não de um segundo schema que o projeto teria de manter em sincronia: coluna enum vira <select> com os membros, coluna datetime vira input datetime-local mais um par de filtros de/até, varchar(>255) vira textarea. AdminSite.automap registra o barrel de models inteiro de uma vez e pula as bases abstratas.

Nada novo é instalado: o HTML e a folha de estilo de ~32 KB (portada verbatim do tempest-fastapi-sdk) são strings deste pacote, a sessão é assinada com node:crypto, e a senha passa pelo PasswordUtils que já existia. Não há template engine nem JavaScript de framework — a sidebar off-canvas responsiva é CSS puro.

  • admin: UserModelAuthBackend autentica contra uma subclasse de BaseUserModel, admitindo só linhas isActive e isAdmin. Passando um AdminMfaVerifier, o principal que habilitou TOTP passa por /admin/mfa depois da senha — assim o painel nunca vira a porta mais fraca de uma conta protegida por MFA.

  • admin: AdminSessionStore emite sessão stateless em cookie assinado (HMAC-SHA256, HttpOnly, SameSite=Lax, Secure por default) carregando o token CSRF que todo formulário de escrita devolve. Segredo com menos de 32 caracteres é recusado na construção. AdminTheme re-estiliza o painel por campos tipados injetados como custom properties CSS, recusando valor que escaparia do bloco <style> injetado.

Alterado

  • BREAKING — admin: o admin JSON foi renomeado para o painel HTML poder assumir os nomes que ele tem no tempest-fastapi-sdk. AdminSite → AdminJsonSite, makeAdminRouter → makeAdminJsonRouter, e os tipos ganharam o mesmo infixo (AdminResource → AdminJsonResource, AdminField → AdminJsonField, AdminListQuery/AdminListResult → AdminJsonListQuery/AdminJsonListResult, AdminRouterOptions → AdminJsonRouterOptions).

Não cabe alias deprecado aqui: o significado antigo e o novo colidem no mesmo identificador. Atualize o import e a chamada do construtor — comportamento, rotas e o prefixo padrão /admin continuam iguais. Montar o painel e o admin JSON na mesma aplicação agora exige dar um prefix diferente para um deles.

  • deps: o piso do peer tempest-db-js sobe para >=0.8.0 (dev dependency e scaffold do CLI para ^0.8.0), mantendo o consumidor na fundação de banco atual.

Corrigido

  • admin: o formulário de criação em branco agora pré-preenche o default literal de cada coluna, então submetê-lo intocado grava o que o banco teria gravado. Sem isso, um flag default(true) chegava como false — o painel desativava silenciosamente toda linha que criava. Pego em browser real, não pelo type-checker.

  • admin: o detail view renderiza todas as colunas, sem ser mais estreitado pelo listDisplay. A listagem é um resumo escaneável; cortar o detail do mesmo jeito escondia dado sem nenhum outro lugar para lê-lo.

[0.23.0] — 2026-08-30

Adicionado

  • api: mountSwaggerUi e mountRedoc passam a emitir <link rel="icon">, com default DEFAULT_DOCS_FAVICON — um SVG inline em data: URI. Sem ele o browser pede /favicon.ico na raiz da origem, que num serviço só de API é 404, 401 atrás do middleware de auth, ou cai numa rota SPA: um erro vermelho no console a cada visita a /docs. SwaggerOptions e RedocOptions aceitam favicon?: string | false; false omite a tag. Closes #7.

  • api: mountRedoc serve o renderer do próprio serviço quando a nova peer opcional redoc está instalada, então a página de referência funciona em rede fechada. Novo RedocOptions.bundle: "auto" (default — local quando disponível, CDN quando não), "local" (lança no mount se o redoc faltar, para deploy air-gapped não degradar em silêncio para um pedido à CDN) e "cdn". RedocOptions.bundlePath serve uma cópia vendorizada; scriptUrl continua ganhando dos dois.

redoc é peer opcional, não dependency: o pacote traz 22 dependências e peers em react, react-dom, styled-components, mobx e core-js — bounds que nenhum serviço backend deveria herdar só para renderizar uma página de referência. Quem quer Redoc offline opta com npm install redoc; o resto não paga nada.

  • api: SwaggerOptions.ui — passthrough mesclado no construtor do SwaggerUIBundle, então qualquer opção do Swagger UI que o JSON carregue fica alcançável sem o SDK modelar uma a uma. Três defaults passam a diferir do Swagger UI: layout é "BaseLayout" no lugar de "StandaloneLayout" (o standalone renderiza a topbar Explore, um campo de URL editável que carrega qualquer spec de qualquer origem — o ponto do editor do Swagger, superfície errada para uma página que documenta um serviço), e deepLinking e persistAuthorization viram true (operação linkável, e credencial que sobrevive ao reload). ui: { layout: "StandaloneLayout" } traz a página antiga de volta, com o script do standalone preset junto. supportedSubmitMethods mantém o default do Swagger UI, então o Try it out continua executando todo verbo até alguém restringir — o que agora é possível para API cujas chamadas são irreversíveis. Função passada em ui lança no mount em vez de ser descartada em silêncio pela serialização JSON. Closes #8.

  • api: exports novos DEFAULT_DOCS_FAVICON, REDOC_CDN_URL, resolveRedocBundle e o tipo RedocBundleSource.

Corrigido

  • api: a página do Redoc não renderiza mais em branco quando o bundle falha ao carregar. CDN bloqueada, CSP restritiva ou scriptUrl errado faziam o Redoc.init nunca rodar e a página subia vazia, o que lê como serviço quebrado. Agora ela diz qual URL falhou, confirma que o documento OpenAPI continua servido, e explica como resolver.

  • api: título de página e URL de favicon passam por escape de HTML, e valor embutido em <script> escapa <. Antes, título vindo de configuração podia fechar o elemento <title> e injetar markup.

Docs

  • A receita de API ganha as seções Favicon, Configurando o Swagger UI e Redoc offline (bilíngue), com a tabela do bundle, o aviso de rede fechada e a nota honesta de que o Redoc ainda busca a marca d'água dele em cdn.redoc.ly, de dentro do bundle.

[0.22.0] — 2026-08-30

Corrigido

  • BREAKING (comportamento) — schemas: ?ascending=false agora realmente ordena descendente. paginationFilterSchema.ascending, cursorPaginationFilterSchema.ascending e syncFilterSchema.includeDeleted eram construídos com z.coerce.boolean(), que é Boolean(input) — toda string não-vazia vira true, "false" e "0" inclusive — então não havia como mandar false pela URL. Passaram a usar o novo looseBoolean. Closes #4.

  • BREAKING (comportamento) — settings: DEBUG=false agora desliga o debug. O campo DEBUG do serverSettingsShape tinha o mesmo defeito de z.coerce.boolean(), então qualquer valor não-vazio — "false" incluído — ligava o debug.

Adicionado

  • schemas: looseBoolean(defaultValue) — o campo booleano para valores que chegam como texto (query string, variável de ambiente). Lê true/1/yes/on/y/enabled e false/0/no/off/n/disabled, sem diferenciar maiúsculas e com trim; trata valor vazio ou só com espaço como ausente (então entrada não preenchida no .env cai no default); repassa booleanos de verdade; e recusa qualquer outra coisa, para um typo virar erro de validação em vez de um false silencioso. Construído sobre o z.stringbool() do zod 4. A metadata OpenAPI é fixada em type: boolean com o default, então o documento descreve o que o cliente manda, e não a união usada para parsear.

Alterado

  • BREAKING (comportamento) — settings: envBoolean passa a ser o looseBoolean sob o nome do domínio de settings — uma implementação só, compartilhada com os filtros de query, em vez de duas listas de token que divergem. Mesma assinatura, e todo token aceito antes continua parseando igual. Duas coisas mudam: token não reconhecido agora é ZodError em vez de um false silencioso, e variável vazia (SMTP_USE_TLS=) agora cai no default do campo em vez de ser lida como false. As duas fazem config de ambiente errada falhar no boot em vez de degradar em silêncio. Afeta todo campo booleano de settings: LOG_JSON, SMTP_USE_TLS, SMTP_USE_SSL, MINIO_SECURE, SESSION_*, AUTH_*.

Docs

  • Seção nova looseBoolean — o booleano que chega como texto na receita de campos validados (bilíngue), com a tabela de tokens e a nota de OpenAPI.
  • A seção envBoolean da receita de configuração documenta a recusa de token desconhecido e a regra da variável vazia, e linka o looseBoolean.

[0.21.0] — 2026-08-30

Alterado

  • BREAKING — deps: zod saiu de dependency direta e virou peer dependency obrigatória em ^4.0.0; @asteasolutions/zod-to-openapi subiu de ^7.3.0 para ^9.1.0. Instale o zod@^4 junto com o SDK (npm install zod@^4) — passo a passo em Migração para zod 4.

Motivo: como dependency, o SDK trazia a própria cópia do zod. Um projeto em zod 4 acabava com duas instâncias no node_modules e, como o zod-to-openapi funciona por patch de protótipo do ZodType, ele patcheava o zod 3 do SDK — não o zod 4 do projeto. Registrar um schema do projeto falhava com TypeError: zodSchema.openapi is not a function, e patchear a instância do projeto à mão só empurrava o erro para UnknownZodTypeError: Unknown zod object type. Como peer, existe uma instância só, compartilhada, e tanto o instanceof ZodType quanto o patch atravessam a fronteira do pacote. Closes #2.

  • schemas: schemas internos migrados para os idiomas do zod 4 — z.uuid(), z.email(), z.url() (a cadeia z.string().uuid() está deprecated no zod 4), z.record(z.string(), z.unknown()) (o tipo da chave passou a ser obrigatório) e .loose() no lugar do .passthrough() deprecated. O z.ZodTypeAny das assinaturas públicas de paginationSchema, cursorPaginationSchema, syncPaginationSchema, loadSettings e AdminResource virou z.ZodType. As grafias do zod 3 continuam parseando, então schema de consumidor segue funcionando.

  • cli: o template do scaffold agora fixa zod@^4.0.0.

Adicionado

  • tests: tests/zod-instance.test.ts — guarda de regressão provando que o z do SDK é a instância de zod do consumidor, que o .openapi() está nela e que o documento gera a partir de schemas criados com um import { z } from "zod" puro.

Docs

  • Nova página Migração para zod 4 (bilíngue) — o breaking change, o install antes/depois, os renames de API e o diagnóstico das "duas instâncias".

[0.20.1] — 2026-07-09

Alterado

  • deps: tempest-db-js para >=0.4.0 (peer), ^0.4.0 (dev) e no template do CLI. Sem mudança de API; build e suíte completa verdes no 0.4.0.

Corrigido

  • api: o Swagger UI agora carrega os assets ao ser acessado em /docs (sem barra final), não só em /docs/. O HTML referenciava os assets por caminho relativo (./assets/…), que o navegador resolvia contra /docs para /assets/… — um 404 que deixava a página em branco/sem estilo. As URLs dos assets agora são absolutas (/docs/assets/…) e resolvem nos dois caminhos.

Docs

[0.20.0] — 2026-07-06

Adicionado

  • db: wrapWithSlowQueryLog (log de queries lentas via wrap de driver) e backupDatabase (backup por dialeto: pg_dump/cópia SQLite). auth: renderAuthResultPage / renderPasswordResetFormPage (páginas HTML opcionais).

[0.19.0] — 2026-07-06

Adicionado

  • storage: S3UploadStorage (mesma interface UploadStorage sobre MinIO/S3, peer minio opcional). cli: lint, config e user.

[0.18.0] — 2026-07-06

Adicionado

  • utils: sendFileDownload (Range/206), sendBytesDownload, resolveDownloadPath (anti-traversal) e configureFileLogging (arquivos por nível + 500.log); core addLogSink; api makeLogsRouter.

[0.17.0] — 2026-07-06

Adicionado

  • schemas: tipos de campo validados (centsField, priceField, slugField, hexColorField, percentField, latitudeField, …), paginação delta-sync (syncFilterSchema / syncPaginationSchema), buildPaginationLinkHeader (RFC-5988) e logEntrySchema.

[0.16.0] — 2026-07-06

Adicionado

  • api: clientes OAuth2/OIDC (GoogleOAuthClient, GitHubOAuthClient, OIDCProvider) + generateOAuthState, WebhookSignatureVerifier (HMAC em tempo constante sobre o corpo cru) e makeToolSpecRouter (manifesto em /tool-spec).

[0.15.0] — 2026-07-06

Adicionado

  • db: camada avançada — TenantScopedRepository (isolamento multi-tenant), BaseOutboxModel + OutboxRelay (outbox transacional), BaseAuditLogModel + snapshot/diffSnapshots (trilha de auditoria) e modelos base opt-in BaseUserModel / BaseUserTokenModel / BaseUserRefreshTokenModel.

[0.14.0] — 2026-07-06

Adicionado

  • testing: helpers de banco em memória agnósticos de framework — createTestDatabase(models) sobe um engine tempest-db-js sobre SQLite em memória com as tabelas refletidas dos models; withTestDatabase(models, fn) escopa a um bloco e sempre fecha.

[0.13.0] — 2026-07-06

Adicionado

  • api/middlewares: middlewares de endurecimento HTTP — rateLimitMiddleware (janela deslizante; store memória + Redis; chaves por IP/header/JWT), bodySizeLimitMiddleware (413), csrfMiddleware + generateCsrfToken, idempotencyMiddleware (store memória + Redis), GracefulShutdown, requestTracingMiddleware e prometheusMiddleware / HttpMetrics.

Alterado

  • api: requestIdMiddleware valida o X-Request-ID de entrada contra uma whitelist ASCII antes de reusá-lo (evita CRLF/log injection).

[0.12.0] — 2026-07-06

Adicionado

  • settings: fragmentos de settings por domínio, espelhando os mixins do tempest-fastapi-sdk — authSettingsShape, jwtSettingsShape, emailSettingsShape, redisSettingsShape, rabbitmqSettingsShape, sessionSettingsShape, uploadSettingsShape, minioSettingsShape, webPushSettingsShape, webSocketSettingsShape, logSettingsShape, tokenSettingsShape (mesmos nomes de env + defaults). Mais os helpers envBoolean (parseia "false" como false) e envList (CSV → string[]).

Documentação

  • recipes/settings: novo guia bilíngue de settings tipados.
  • recipes/database: novo guia bilíngue (models + repositories).

[0.1.0] — 2026-06-29

Adicionado

  • Fundação: tooling TypeScript rígido, alias @ (sem .js), build dual ESM + CJS + .d.ts (tsup), Biome e Vitest.
  • core: JSONLogger, contexto de request-id (AsyncLocalStorage), defineEnum.
  • exceptions: AppException + subclasses HTTP (Conflict, NotFound, Unauthorized, Forbidden, Validation, TooManyRequests, InvalidToken, ExpiredToken), MessageCatalog (i18n) e registerExceptionHandlers.
  • schemas: z com OpenAPI, baseResponseSchema, paginação offset e cursor.
  • settings: loadSettings, baseAppSettingsShape.
  • db: re-export do tempest-db-js, BaseModel e helpers de coluna.
  • services / controllers: BaseService, BaseController.
  • utils: CPF/CNPJ/CEP/telefone/UF + cidades, datetime, dict, tokens opacos, AttemptThrottle, PasswordUtils (bcrypt), JWTUtils.
  • auth: schemas, UserAuthService, middleware JWT, guardas de role, makeAuthRouter.
  • api: createApp, runServer, Swagger UI + Redoc nativos, health.
  • CLI: new, generate, secret, docker-compose, db.

Pendente

Ainda não portado do tempest-fastapi-sdk: sessions, cache (Redis), queue (RabbitMQ), tasks, webpush, websockets, feature flags, storage, metrics, admin, SSE, e os fluxos de MFA / email / reset de senha.