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. Ozod-to-openapiadiciona.openapi()por patch deZodType.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 estouravaTypeError: zodSchema.openapi is not a functionde dentro denode_modules. Como declarar schemas emschemas/*.tse 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:
extendZodWithOpenApipassa a ser reexportado, para quem precisa de.openapi()nas próprias instâncias de schema (parâmetro comparam,extendcomanyOf) aplicar o patch no próprio entrypoint sem declarar@asteasolutions/zod-to-openapicomo 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$refsem precisar carregar um valor de retorno. Oregister()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. Ozreexportado passa a ser documentado como a instância já estendida.
[0.30.0] — 2026-09-05¶
Adicionado¶
-
tasks:
TaskManager.registeraceita um{ description, schedule }opcional eTaskManager.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:
BaseJobModeleJobStore— 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 ocancelrecusa run já em estado terminal em vez de reescrevê-lo por baixo do operador. O store não é acoplado aoTaskManagerde 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}/taskscom 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 oconfigureFileLoggingescreve 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|jsonbaixa 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 — semlogDirresponde 404, porque o payload expõe traceback e metadado de request. -
admin: o SQL console, desligado por padrão.
makeAdminRouter({ sqlConsole })serve{prefix}/sqlatrás de uma política: capabilities (read/insert/update/delete/ddl/drop/admin), allowlist e denylist de tabela, recusa deUPDATE/DELETEsemWHERE, 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 capabilityadmin— a mais privilegiada, não a menos. Toda tentativa, permitida ou recusada, chega aoonAudit; 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 emAdminModel({ inlines })renderizam as linhas que apontam para o registro: uma tabela somente leitura com link para o admin do filho ou — comeditable— 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.canDeleteadiciona 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 paramultipart/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 colunaNOT NULLsem default; no edit, não enviar arquivo mantém o atual em vez de limpar a coluna. RegistraruploadFieldssemuploadStoragelevanta 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õeGET/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. OparseCsvexportado 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 porGET {prefix}/m/{slug}/autocomplete/{campo}?q=, que consulta ossearchFieldsdo 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:
busboyentra como peer opcional, exigido só por quem configurauploadFieldsoucanImport; 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/MultipartLimitErrorsão exportados para quem precisa do mesmo tratamento.
Corrigido¶
- admin:
createdBy/updatedBynã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 flagscanCreate/canEdit/canDelete— não as substitui — e o painel esconde o que recusa: model semVIEWsome 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 responde404(a view não existe); política recusando responde403(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/updatedBycom 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. PassandoAdminModel({ auditModel }), o painel também renderiza a timeline de mudanças do registro lida daquela tabelaBaseAuditLogModel— 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 entradasmetricCard(label, compute, helpText?)calculadas do banco no carregamento, em três formatos:value,trend(▲/▼ com a variação percentual) epartition(uma barra por segmento). Card cujocomputelevanta é logado e renderiza como card de erro, em vez de derrubar o dashboard.trendPercentdevolvenullcontra base zero — porcentagem contra zero é indefinida, não infinita. -
admin: lenses — presets salvos de listagem. Entradas
adminLens({ name, filters, orderBy })passadas emAdminModel({ 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/updatedByda 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 novoauditFieldNames()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
canEdite à colunaisActive) ou Delete (condicionada acanDelete) à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 emAdminModel({ actions: [...] }). Cada uma entra no dropdown com o namespacecustom:<nome>, então nunca colide com uma ação embutida. O handler recebe osidsmarcados, 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 (ounullpara nenhum). Handler que levanta é registrado no log e volta como banner de erro, não como500.
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 dolistDisplaydo 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;Datevira ISO,bigintvira string decimal, binário vira base64.makeAdminRouter({ exportMaxRows })limita as linhas (default5000), 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 primeirosearchFieldsdo admin referenciado (caindo paraname/title/email/label/referencee, 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 helpersforeignKeyFields,foreignKeyLabeleforeignKeyTablepassam 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.
AdminSiteguarda o registro,AdminModelconfigura um model, emakeAdminRouter(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:
UserModelAuthBackendautentica contra uma subclasse deBaseUserModel, admitindo só linhasisActiveeisAdmin. Passando umAdminMfaVerifier, o principal que habilitou TOTP passa por/admin/mfadepois da senha — assim o painel nunca vira a porta mais fraca de uma conta protegida por MFA. -
admin:
AdminSessionStoreemite sessão stateless em cookie assinado (HMAC-SHA256,HttpOnly,SameSite=Lax,Securepor default) carregando o token CSRF que todo formulário de escrita devolve. Segredo com menos de 32 caracteres é recusado na construção.AdminThemere-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-jssobe 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 comofalse— 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:
mountSwaggerUiemountRedocpassam a emitir<link rel="icon">, com defaultDEFAULT_DOCS_FAVICON— um SVG inline emdata:URI. Sem ele o browser pede/favicon.icona 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.SwaggerOptionseRedocOptionsaceitamfavicon?: string | false;falseomite a tag. Closes #7. -
api:
mountRedocserve o renderer do próprio serviço quando a nova peer opcionalredocestá instalada, então a página de referência funciona em rede fechada. NovoRedocOptions.bundle:"auto"(default — local quando disponível, CDN quando não),"local"(lança no mount se oredocfaltar, para deploy air-gapped não degradar em silêncio para um pedido à CDN) e"cdn".RedocOptions.bundlePathserve uma cópia vendorizada;scriptUrlcontinua 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 doSwaggerUIBundle, 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), edeepLinkingepersistAuthorizationviramtrue(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.supportedSubmitMethodsmanté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 emuilanç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,resolveRedocBundlee o tipoRedocBundleSource.
Corrigido¶
-
api: a página do Redoc não renderiza mais em branco quando o bundle falha ao carregar. CDN bloqueada, CSP restritiva ou
scriptUrlerrado faziam oRedoc.initnunca 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 emcdn.redoc.ly, de dentro do bundle.
[0.22.0] — 2026-08-30¶
Corrigido¶
-
BREAKING (comportamento) — schemas:
?ascending=falseagora realmente ordena descendente.paginationFilterSchema.ascending,cursorPaginationFilterSchema.ascendingesyncFilterSchema.includeDeletederam construídos comz.coerce.boolean(), que éBoolean(input)— toda string não-vazia viratrue,"false"e"0"inclusive — então não havia como mandarfalsepela URL. Passaram a usar o novolooseBoolean. Closes #4. -
BREAKING (comportamento) — settings:
DEBUG=falseagora desliga o debug. O campoDEBUGdoserverSettingsShapetinha o mesmo defeito dez.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/enabledefalse/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.envcai no default); repassa booleanos de verdade; e recusa qualquer outra coisa, para um typo virar erro de validação em vez de umfalsesilencioso. Construído sobre oz.stringbool()do zod 4. A metadata OpenAPI é fixada emtype: booleancom 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:
envBooleanpassa a ser olooseBooleansob 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 éZodErrorem vez de umfalsesilencioso, e variável vazia (SMTP_USE_TLS=) agora cai no default do campo em vez de ser lida comofalse. 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
envBooleanda receita de configuração documenta a recusa de token desconhecido e a regra da variável vazia, e linka olooseBoolean.
[0.21.0] — 2026-08-30¶
Alterado¶
- BREAKING — deps:
zodsaiu de dependency direta e virou peer dependency obrigatória em^4.0.0;@asteasolutions/zod-to-openapisubiu de^7.3.0para^9.1.0. Instale ozod@^4junto 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 cadeiaz.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. Oz.ZodTypeAnydas assinaturas públicas depaginationSchema,cursorPaginationSchema,syncPaginationSchema,loadSettingseAdminResourcevirouz.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 ozdo SDK é a instância dezoddo consumidor, que o.openapi()está nela e que o documento gera a partir de schemas criados com umimport { 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-jspara>=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/docspara/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¶
- Nova receita Schemas (base, resposta e paginação) —
toDict,baseResponseSchema, o padrão Create/Update/Response e paginação por offset vs. cursor. - Nova receita API:
createApp, OpenAPI, Swagger e Redoc — referência completa das opções docreateApp, o hookconfiguree a cablagem de OpenAPI em 3 passos.
[0.20.0] — 2026-07-06¶
Adicionado¶
- db:
wrapWithSlowQueryLog(log de queries lentas via wrap de driver) ebackupDatabase(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 interfaceUploadStoragesobre MinIO/S3, peerminioopcional). cli:lint,configeuser.
[0.18.0] — 2026-07-06¶
Adicionado¶
- utils:
sendFileDownload(Range/206),sendBytesDownload,resolveDownloadPath(anti-traversal) econfigureFileLogging(arquivos por nível +500.log); coreaddLogSink; apimakeLogsRouter.
[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) elogEntrySchema.
[0.16.0] — 2026-07-06¶
Adicionado¶
- api: clientes OAuth2/OIDC (
GoogleOAuthClient,GitHubOAuthClient,OIDCProvider) +generateOAuthState,WebhookSignatureVerifier(HMAC em tempo constante sobre o corpo cru) emakeToolSpecRouter(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-inBaseUserModel/BaseUserTokenModel/BaseUserRefreshTokenModel.
[0.14.0] — 2026-07-06¶
Adicionado¶
- testing: helpers de banco em memória agnósticos de framework —
createTestDatabase(models)sobe um enginetempest-db-jssobre 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,requestTracingMiddlewareeprometheusMiddleware/HttpMetrics.
Alterado¶
- api:
requestIdMiddlewarevalida oX-Request-IDde 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 helpersenvBoolean(parseia"false"comofalse) eenvList(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) eregisterExceptionHandlers. - schemas:
zcom OpenAPI,baseResponseSchema, paginação offset e cursor. - settings:
loadSettings,baseAppSettingsShape. - db: re-export do
tempest-db-js,BaseModele 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.