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

@tzam-org/sessions

v0.2.1

Published

Sessões agnósticas: token opaco (só o hash é guardado), expiração total e por inatividade, revogação; consentimento de cookies assinado (subcaminho /consent). Persistência por interface implementada em cada app.

Readme

@tzam-org/sessions

Sessões com token opaco: o navegador guarda o token, o banco guarda só o hash. Expiração total, expiração por inatividade e revogação. No subcaminho /consent, o consentimento de cookies (categorias, cookie assinado, versão do aviso) — veja abaixo.

npm

Instalação

pnpm add @tzam-org/sessions

Sem dependências de runtime (usa node:crypto).

Garantias

  • Só o hash é guardado. O token tem 32 bytes aleatórios (base64url); o store recebe o SHA-256 dele (tokenHash). Um vazamento do banco não entrega sessões vivas.
  • Duas expirações: ttlMs (absoluta, a partir do início) e idleMs (sem uso). validate devolve null para sessão desconhecida, revogada, expirada ou ociosa, e para token fora do formato (que nem chega ao store).
  • Revogação vale na próxima requisição. revokeOwn só revoga se a sessão é do usuário informado.
  • ended(token) responde se a sessão foi revogada (logout em outro lugar), sem contar como atividade.

Uso

O app implementa SessionStore sobre o próprio banco. Exemplo em memória:

import { createSessionManager, type SessionRecord, type SessionStore } from '@tzam-org/sessions';

function memoryStore(): SessionStore {
  const rows: SessionRecord[] = [];
  return {
    async create(data) {
      const row: SessionRecord = { ...data, id: String(rows.length + 1), revokedAt: null };
      rows.push(row);
      return row;
    },
    findByTokenHash: async (tokenHash) => rows.find((r) => r.tokenHash === tokenHash) ?? null,
    async touch(id, at) {
      const row = rows.find((r) => r.id === id);
      if (row) row.lastSeenAt = at;
    },
    async revoke(id, at) {
      const row = rows.find((r) => r.id === id);
      if (row) row.revokedAt = at;
    },
    async revokeAllForUser(userId, at) {
      for (const r of rows) if (r.userId === userId && !r.revokedAt) r.revokedAt = at;
    },
    listActiveForUser: async (userId, now) => rows.filter((r) => r.userId === userId && !r.revokedAt && r.expiresAt > now),
  };
}

const sessions = createSessionManager({
  store: memoryStore(),
  ttlMs: 8 * 60 * 60 * 1000, // 8 h no máximo
  idleMs: 30 * 60 * 1000, // 30 min sem uso
});

// Login: grave `token` num cookie HttpOnly; o banco fica só com o hash.
const { token, session } = await sessions.start('u1', { userAgent: 'Mozilla/5.0', ip: '203.0.113.7', authMethod: 'password' });

// A cada requisição: a sessão viva, ou null.
const current = await sessions.validate(token);

await sessions.revokeOwn('u1', session.id); // tela "minhas sessões"
await sessions.revokeAll('u1'); // sair de todos os lugares

Sair com SSO (apps clientes de um provedor OIDC)

Este pacote cuida da sessão local. Quando o app entra por um provedor OIDC (o Tzam), o "Sair" encerra também a sessão do provedor — servidor a servidor, sem levar o navegador lá — e a pessoa volta ao login do próprio app. A dinâmica e os helpers (appLogout, endSession, loginEntry), com o diagrama de sequência, estão no README do @tzam-org/oidc. Daqui o app só passa o revoke da sessão local como endLocal, e guarda junto da sessão o sid do id_token (é ele que nomeia a sessão do provedor no logout e no back-channel).

API

| Nome | Tipo | O que faz | |---|---|---| | createSessionManager({ store, ttlMs, idleMs, clock? }) | função | Cria o gerenciador: start, validate, ended, revoke, revokeOwn, revokeAll, listActive. | | SessionManager | tipo | Retorno de createSessionManager. | | SessionManagerOptions | interface | Opções de createSessionManager. | | SessionStore | interface | O que o app implementa: create, findByTokenHash, touch, revoke, revokeAllForUser, listActiveForUser. | | SessionRecord | interface | id, userId, tokenHash, createdAt, lastSeenAt, expiresAt, revokedAt, userAgent?, ip?, authMethod?. | | hashToken(token) | função | SHA-256 hex do token (o mesmo usado para guardar). |

Erros

createSessionManager lança Error('ttlMs and idleMs must be positive') quando um dos dois não é positivo. As outras funções não lançam por token inválido: validate devolve null e ended, false. Erros do store são repassados.

Consentimento de cookies (@tzam-org/sessions/consent)

Subcaminho sem framework e sem texto de app. O app declara os próprios cookies por categoria e uma versão do aviso; o módulo assina e lê a escolha, e diz o que pode ser gravado.

Garantias

  • essential é sempre aceita e não pode ser desligada (LGPD: o estritamente necessário só é informado). buildConsent e readConsent sempre a incluem; allows(…, 'essential') é sempre true.
  • Opcional só depois do aceite, e sob a versão atual do aviso: consentimento dado a outra versão não libera nada opcional e needsPrompt volta a pedir.
  • Cookie assinado (v1.<payload>.<hmac>, HMAC-SHA256 com chave de 32+ bytes do app). A assinatura cobre o nome do cookie, então um valor assinado para outro cookie não serve. Cookie forjado, adulterado, cortado, vencido (maxAgeDays, padrão 365) ou datado no futuro → null (pede de novo). readConsent nunca lança por valor hostil.
  • O próprio cookie de consentimento é essencial e tem que estar listado nessa categoria: defineConsentPolicy recusa a política sem ele, cookie repetido entre categorias ou categoria repetida.
  • Cookie não declarado nunca é permitido: canSetCookie(nome) é falso para o que não está na política, o que obriga o app a listar tudo o que grava.
  • Prova sem IP. ConsentLog recebe id (o mesmo que vai dentro do cookie assinado), data, versão, categorias e o sub de quem está logado (ou null). IP não é guardado, nem em hash: não acrescenta à prova e seria dado pessoal guardado sem outra finalidade (LGPD art. 6º, III).

Uso

import { acceptedFrom, buildConsent, consentGate, defineConsentPolicy, deriveConsentKey, readConsent, recordConsent, type ConsentLog } from '@tzam-org/sessions/consent';

const policy = defineConsentPolicy({
  version: '2026-10-04',
  cookieName: 'app_consent',
  categories: [
    {
      id: 'essential',
      cookies: [
        { name: 'app_session', purpose: 'Mantém você conectado.', duration: '8 horas' },
        { name: 'app_consent', purpose: 'Guarda esta escolha.', duration: '12 meses' },
      ],
    },
    { id: 'preferences', cookies: [{ name: 'app_theme', purpose: 'Tema escolhido.', duration: '1 ano' }] },
  ],
});

// Chave do app: derivada de um segredo de 32+ caracteres; o salt nomeia o app.
const key = deriveConsentKey('um-segredo-do-app-com-32-caracteres-ou-mais', 'app-exemplo');

// A cada requisição: o que a pessoa escolheu (ou null) e o que pode ser gravado.
const gate = consentGate(readConsent('valor-do-cookie', { policy, key }), policy);
if (gate.canSetCookie('app_theme')) {
  // grava o cookie de tema
}

// No POST do formulário: escolha → cookie assinado + registro.
const accepted = acceptedFrom(policy, { choice: 'custom', picked: ['preferences'] });
if (accepted) {
  const { consent, cookie } = buildConsent({ policy, key, accepted });
  const log: ConsentLog = { record: async () => {} }; // o app grava no próprio banco
  await recordConsent(log, consent, { sub: 'id-da-pessoa' });
  // response.cookies.set(cookie)
}

API

| Nome | Tipo | O que faz | |---|---|---| | defineConsentPolicy({ version, cookieName, categories, maxAgeDays? }) | função | Valida e devolve a ConsentPolicy (com optional). Lança ConsentPolicyError. | | deriveConsentKey(secret, salt) | função | HKDF-SHA256 (info cookie-consent:v1), 32 bytes. Segredo com 32+ caracteres. | | buildConsent({ policy, key, accepted, now?, id? }) | função | { consent, cookie }: cookie HttpOnly, Secure, SameSite=Lax, path=/, maxAge da política. | | readConsent(value, { policy, key, now? }) | função | Consent ou null (ausente, forjado, adulterado, vencido). | | needsPrompt(consent, version) | função | Sem consentimento ou de outra versão → true. | | allows(consent, policy, category) / canSetCookie(consent, policy, name) | função | O que pode ser usado/gravado. | | consentGate(consent, policy) | função | { consent, needsPrompt, allows(category), canSetCookie(name) }. | | acceptedFrom(policy, { choice, picked }) | função | all / essential / custom → categorias opcionais aceitas; outra escolha → null. | | recordConsent(log, consent, { sub? }) | função | Grava ConsentRecord pelo ConsentLog do app; erro do log é repassado. | | ESSENTIAL | constante | 'essential'. | | ConsentPolicy, ConsentPolicyInput, ConsentCategory, CookieInfo, Consent, ConsentCookie, ConsentRecord, ConsentLog, ConsentGate | tipos | |

Erros: defineConsentPolicy lança ConsentPolicyError (versão, nome do cookie, categoria ou maxAgeDays fora do formato; essential ausente; cookie de consentimento fora de essential; repetições). deriveConsentKey, buildConsent e readConsent lançam Error para segredo ou chave curtos (erro de configuração, nunca de entrada do navegador).


Faz parte dos módulos @tzam-org/* (github.com/Tzam-St/packages-modules): sem framework e sem texto de produto.