npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@chrono-os/consent-lgpd

v1.0.0

Published

LGPD reutilizável: consentimento (CookieBanner 3-cat, ConditionalScript, ConsentLog), ciclo de vida de direitos do titular (export com link assinado, exclusão com carência, varredura, caixa do DPO) por portas, adaptadores Fastify e NestJS, páginas legais

Downloads

832

Readme

@chrono-os/consent-lgpd

LGPD reutilizável, em duas metades:

  • Consentimento — CookieBanner 3-cat (necessarios/analiticos/marketing), ConditionalScript gated por consent, ConsentLog com hash de IP, formulário simples de DPO e páginas legais (Privacidade, Cookies, Termos, DPO).
  • Direitos do titular (desde 1.0, vindo do SobreVivendo Adulto) — export com link de validade, exclusão com carência, varredura de pendências, caixa do DPO com marcos de prazo. Sem framework: fala com o banco do app por portas; adaptador NestJS opcional.

Status: 1.0.0. Consumidores: Naírio (institucional, calculadoras) na metade de consentimento; SobreVivendo Adulto no ciclo de direitos do titular. Plano original em Naírio/Plan/03-modulos/2026-05-24_modulo-consent-lgpd.md.

Install

Pacote público no npmjs.org — sem necessidade de .npmrc ou token.

yarn add @chrono-os/consent-lgpd

Aplicar migration template (só a metade de consentimento — ConsentLog e DpoRequest simples):

cat node_modules/@chrono-os/consent-lgpd/prisma/schema.template.prisma >> prisma/schema.prisma
yarn prisma migrate dev --name add_consent_lgpd

O template é conferido por teste contra os campos que recordConsent, createDpoRequest e purgeOldConsents gravam (src/core/template-prisma.test.ts).

Tailwind — passo obrigatório no consumer

O banner e o botão de revogar são estilizados com classes Tailwind que vivem dentro do bundle do pacote. O purge do Tailwind só varre o que está no content, então sem a linha abaixo essas classes somem do CSS final:

// tailwind.config.ts do app consumer
content: [
  './app/**/*.{ts,tsx}',
  // ...
  './node_modules/@chrono-os/consent-lgpd/dist/**/*.{js,mjs,cjs}',
]

Como o defeito se manifesta (é enganoso, vale reconhecer de longe): o banner renderiza, mas largura cheia e colado ao fim do conteúdo em vez de flutuar num card no canto. Parte das classes continua funcionando — bg-slate-900, p-6, rounded-lg sobrevivem se outra página do app as usar — o que passa impressão de CSS meio quebrado, não de purge. A confirmação está no computed style: com bottom-4 purgada, um position: fixed sem bottom cai na posição estática do fluxo e getComputedStyle devolve algo como bottom: -3304px; md:max-w-xl purgada aparece como max-width: none.

Conferir em produção sem abrir o navegador:

curl -s "$SEU_SITE/_next/static/chunks/<hash>.css" | grep -c '\.bottom-4{'
# 0 = purgada

Uso

Frontend — banner + script condicional

// app/layout.tsx
import { CookieBanner, ConditionalScript } from "@chrono-os/consent-lgpd/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}

        <CookieBanner
          cookiePolicyHref="/politica-de-cookies"
          onConsentChange={async ({ state, action, sessionId }) => {
            await fetch("/api/consent", {
              method: "POST",
              headers: { "content-type": "application/json" },
              body: JSON.stringify({
                sessionId,
                categories: {
                  necessarios: state.necessarios,
                  analiticos: state.analiticos,
                  marketing: state.marketing,
                  version: state.version,
                },
                action,
              }),
            });
          }}
        />

        {/* Google Analytics 4 — só carrega após consent "analiticos" */}
        <ConditionalScript
          category="analiticos"
          src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"
          async
        />
      </body>
    </html>
  );
}

Frontend — botão revogar consent

// app/politica-de-cookies/page.tsx
import { RevokeButton } from "@chrono-os/consent-lgpd/react";

export default function PoliticaCookiesPage() {
  return (
    <main>
      <h1>Política de Cookies</h1>
      <RevokeButton
        onConsentChange={async ({ state, action, sessionId }) => {
          await fetch("/api/consent", {
            method: "POST",
            headers: { "content-type": "application/json" },
            body: JSON.stringify({ sessionId, categories: state, action }),
          });
        }}
      />
    </main>
  );
}

Backend (Fastify)

import Fastify from "fastify";
import { PrismaClient } from "@prisma/client";
import { consentPlugin } from "@chrono-os/consent-lgpd/fastify";

const app = Fastify();
const prisma = new PrismaClient();

await app.register(consentPlugin, {
  prisma,
  ipHashSalt: process.env.CONSENT_IP_HASH_SALT!, // 32+ chars
  // prefix: "/api",                              // opcional
  // routes: { consent: true, dpo: true },        // opcional
  // dpoRateLimit: { max: 5, timeWindow: "1 minute" },
});

Expõe:

  • POST /consent — grava ConsentLog (IP hashed server-side, body validado com Zod)
  • POST /webhooks/dpo — recebe solicitação LGPD art. 18, rate-limited

Páginas legais

import { POLITICA_PRIVACIDADE_MD, renderTemplate } from "@chrono-os/consent-lgpd/pages";

const html = renderTemplate(POLITICA_PRIVACIDADE_MD, {
  razaoSocial: "Naírio Augusto Advogados",
  cnpj: "28.373.650/0001-00",
  dpoNome: "Dr. Naírio Augusto",
  contatoHref: "/contato",
  dpoHref: "/dpo",
  privacidadeHref: "/politica-de-privacidade",
  cookiesHref: "/politica-de-cookies",
  termosHref: "/termos-de-uso",
  ultimaAtualizacao: "2026-05-24",
  versao: "1.0",
  foroComarca: "Porto Alegre/RS",
  operadoresMd: "- Coolify (hospedagem)\n- ClickUp (CRM)\n- Z-API (validação WhatsApp)",
  analyticsScriptsMd: "Google Analytics 4 com IP anonimizado",
  marketingScriptsMd: "Meta Pixel + Google Ads Conversions",
});
// Renderize `html` (markdown) via MDX, react-markdown, etc.

Direitos do titular (1.0)

O núcleo guarda as regras que precisam ser iguais em todo app; o que depende do schema, da fila, do e-mail e do storage o app entrega como portas.

| Caso de uso | Regra que o núcleo garante | |---|---| | solicitarExport | 1 export por titular a cada 24 h (configurável); fila fora do ar não perde o pedido | | processar(id) — EXPORT | coleta → grava no storage → URL com validade (7 d, nunca além do pedido) → READY; storage sem URL cai no modo inline (JSON na solicitação) | | resolverDownload | só o dono (ou token assinado); alheio/inexistente/adulterado dão o mesmo NAO_ENCONTRADA; expirado ou sem prazo → EXPIRADA; FULFILLED só no 1º download | | solicitarExclusao | exige a palavra EXCLUIR; agenda para agora + carência (30 d); o app corta o acesso na mesma transação | | processar(id) — DELETE | antes da carência lança DELETE_NOT_DUE_YET (a fila re-tenta); sem data agendada recusa e alerta; READY numa exclusão não conta como feita | | varrer() | re-enfileira export parado (+1 h) e exclusão vencida com o mesmo jobId; idempotente; nunca lança | | criarCaixaDpo().receber | honeypot (website) → sucesso aparente sem gravar; aviso de recebimento pela fila, com envio direto de reserva; alerta sem nome/e-mail por padrão | | criarCaixaDpo().atualizar | confirmadoEm/resolvidoEm gravados uma vez só; devolve antes/depois para o log de auditoria |

import { criarDireitosTitular, criarCaixaDpo, type PortasDireitosTitular } from "@chrono-os/consent-lgpd/core";

const portas: PortasDireitosTitular = {
  solicitacoes: { buscar, buscarExportRecente, criarExport, criarExclusao, atualizar, listarPresas },
  dadosTitular: { coletar, apagar },        // o que o app guarda do titular
  armazenamento: { gravar, assinarUrl },    // opcional — sem ele, modo inline
  fila: { enfileirar },                     // { enfileirado: false } = sem fila
  notificacao: { exportPronto },            // opcional, best-effort
  alertar, eventos, log,                    // opcionais
};
const direitos = criarDireitosTitular(portas, { carenciaExclusaoDias: 30 });

Contrato das portas que o núcleo não consegue checar sozinho (está nos JSDoc de tipos.ts):

  • criarExclusao é uma transação: solicitação + soft-delete + revogar sessões.
  • apagar é idempotente e não apaga a solicitação — ela fica com titularId = null (FK SetNull) como prova da exclusão.
  • atualizar não lança se a linha sumiu (updateMany).

Link assinado próprio (opcional). Com linkAssinado: { segredo, urlDownload }, o e-mail de "export pronto" leva um link para o app (token HMAC com validade) em vez da URL do storage, e resolverDownload({ solicitacaoId, token }) aceita esse token sem sessão. Assinatura válida não basta: o banco ainda precisa confirmar solicitação, dono e prazo — então apagar o titular revoga o link. Primitivas avulsas: assinarLinkExport / verificarLinkExport.

Erros. Casos esperados voltam como { ok: false, erro }; os que precisam derrubar o job lançam (ExclusaoNaoVencidaError, ExclusaoSemAgendamentoError). Confira pelo .code, não por instanceof: /core e /nestjs são bundles separados, cada um com a sua cópia da classe.

NestJS

import { ConsentLgpdModule, InjectDireitosTitular, ouLancar, type DireitosTitular } from "@chrono-os/consent-lgpd/nestjs";

@Module({
  imports: [
    ConsentLgpdModule.forRootAsync({
      imports: [PortasModule],
      inject: [MinhasPortas],
      useFactory: (p: MinhasPortas) => ({
        direitos: { portas: p.direitos(), opcoes: { carenciaExclusaoDias: 30 } },
        caixaDpo: { portas: p.caixaDpo() },
      }),
    }),
  ],
})

// no controller — rota, guard e auditoria continuam do app
const { solicitacao } = ouLancar(await this.direitos.solicitarExport({ titularId, ipHash }));

ouLancar/excecaoHttpLgpd traduzem os erros para HttpException com { error, message, details } (429 RATE_LIMITED, 404 NOT_FOUND, 425 NOT_READY, 410 EXPIRED, 500 PERSIST_FAILED...). Implementação de referência: sobrevivendo-backend/apps/api/src/modules/lgpd/lgpd-portas.ts.

API

| Entrypoint | Exporta | |---|---| | /react | CookieBanner, ConditionalScript, RevokeButton, useConsent, readConsent/writeConsent/clearConsent/hasConsented/getSessionId, constantes | | /core | consentimento: hashIp, hashIpHmacDiario, verificarIpHash, recordConsent, createDpoRequest, purgeOldConsents · direitos do titular: criarDireitosTitular, criarCaixaDpo, assinarLinkExport, verificarLinkExport, tipos das portas | | /nestjs | ConsentLgpdModule, InjectDireitosTitular, InjectCaixaDpo, DIREITOS_TITULAR, CAIXA_DPO, ouLancar, excecaoHttpLgpd (peer opcional @nestjs/common) | | /fastify | consentPlugin + as funções de consentimento do /core | | /schema | consentStateSchema, consentLogPayloadSchema, dpoRequestPayloadSchema, assuntoDpoSchema, statusPedidoDpoSchema, pedidoDpoSchema, CONFIRMACAO_EXCLUSAO, types Zod, constantes | | /pages | POLITICA_PRIVACIDADE_MD, POLITICA_COOKIES_MD, TERMOS_USO_MD, PAGINA_DPO_MD, renderTemplate, LegalPageVars |

Roadmap

| Fase | Conteúdo | ETA | |---|---|---| | 0 | Estrutura + CI + 0.1.0 stub + schema Zod | 2026-05-31 ✅ | | 1 | Banner + ConditionalScript + ConsentLog + DPO + páginas legais (0.2.0) | 2026-06-28 ✅ | | 2 | Calculadora Naírio adota o pacote (deadline legal) | 2026-07-12 ✅ | | 3 | 1.0 — fusão do ciclo de direitos do titular do SVA + adaptador NestJS | 2026-09-10 ✅ |

Versionamento

SemVer. Releases via tag vX.Y.Z no main. Ver CHANGELOG.md.