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

@sevn/elections-components

v1.3.0

Published

Componentes de apuração eleitoral em tempo real. JavaScript puro, sem framework: use via import ou por uma tag <script> em HTML cru.

Readme

@sevn/elections-components

Componentes de apuração eleitoral em tempo real: mapas, rankings, progresso de urnas, comparação entre turnos. São JavaScript puro — funcionam em qualquer site, com ou sem framework, com ou sem build.

import { Core, StateLeaders } from '@sevn/elections-components';

Core.applyKey('minha-chave-aqui');

const componente = new StateLeaders({
	target: document.querySelector('#placeholder-state-leaders')
});

Instalação

npm install @sevn/elections-components

Não há dependência de peer para instalar. Svelte é detalhe de implementação interno: ele vai embutido no pacote e não aparece na sua árvore de dependências.

A chave

Todo componente precisa de uma chave antes de buscar dados. Ela identifica sua CDN — é o subdomínio da API dedicada à sua conta:

Core.applyKey('minha-chave-aqui');
// → https://minha-chave-aqui-api1.sevnelections.com

Chame uma vez, no boot da aplicação, antes de montar qualquer componente. Sem chave o componente monta normalmente e exibe a mensagem de configuração faltando na própria área dele — nunca lança um erro que derrube sua página.

Alta disponibilidade

A cascata entre servidores é feita pelo @sevn/reqcache, a mesma lib que cuida do cache e da deduplicação de requisições. Cada requisição tenta, em ordem:

  1. {chave}-api1.sevnelections.com
  2. {chave}-api2.sevnelections.com
  3. api-failover.sevnelections.com — servidor genérico de emergência

Cada servidor é tentado uma vez: insistir no mesmo host antes de pular para o próximo só somaria espera para quem está olhando a tela. O timeout de cada tentativa fica por conta da lib, que o escala pela qualidade da conexão — 30s numa rede boa, subindo até 90s em slow-2g. Dá para fixá-lo em Core.configure({ request: { timeoutMs } }), mas raramente é boa ideia: um valor fixo corta a resposta de quem está numa rede ruim.

O que falha fica marcado como fora do ar por 2 minutos: nesse período ele é recusado localmente, sem tocar a rede, para as requisições seguintes não pagarem o timeout dele de novo num dia de pico.

Um 404 não promove servidor. Aqui ele significa "recorte ainda não publicado" — é a resposta correta, não uma indisponibilidade —, então sobe na hora como "Dados ainda não publicados", sem repetir o mesmo caminho nos outros domínios para colher o mesmo 404.

Caindo todos os três, o último valor bom ainda em cache é servido no lugar do erro — a tela segura o número anterior em vez de zerar. Só quando não há nada em cache é que o componente exibe a falha.

O cache é compartilhado entre os hosts — trocar de servidor não invalida nada do que já foi baixado, porque a entrada fica sempre sob a URL do primário.

As três formas de usar

1. Import (Node, Bun, Deno, Vite, webpack, Next, Nuxt…)

import { Core, StateLeaders } from '@sevn/elections-components';

Core.applyKey('minha-chave-aqui');

const componente = new StateLeaders({
	target: document.querySelector('#placeholder'),
	year: 2026,
	defaultOffice: 'governor'
});

// Quando não precisar mais dele:
componente.destroy();

O pacote é tree-shakeable: importar um componente não traz os outros 24.

2. Tag <script>, em HTML cru

<div id="placeholder-state-leaders"></div>

<script src="https://cdn.sevnelections.com/components/v1/state-leaders.js?cid=minha-chave-aqui"></script>
<script>
	const componente = new StateLeaders({
		target: document.querySelector('#placeholder-state-leaders')
	});
</script>

A chave sai do ?cid= da própria tag — não há o que configurar. Cada arquivo expõe a classe com o nome do componente (window.StateLeaders) e também window.SevnElections.Core, se você precisar ajustar algo.

Um arquivo por componente, ~34 KB gzip cada, tudo embutido.

3. CommonJS

const { Core, StateLeaders } = require('@sevn/elections-components');

API

new Componente(opções)

target é obrigatório e aceita um elemento ou um seletor CSS. As demais chaves são as props do componente.

new StateLeaders({ target: '#ph', year: 2026, mock: true });

componente.destroy()

Desmonta e remove do DOM. Devolve uma Promise, caso você precise esperar a saída terminar. Chamar duas vezes não faz nada.

Core

| Método | Para quê | | ------------------------------------------ | --------------------------------------------- | | Core.applyKey(chave) | Configura a conta. Obrigatório. | | Core.setTheme({ primary, secondary, … }) | Sobrescreve as cores. | | Core.configure(opções) | Ajusta hosts, CDN de assets, cache, timeouts. | | Core.getConfig() | Lê a configuração atual. | | Core.isReady() | Diz se já há hosts resolvidos. | | Core.activeHost() | Host que respondeu por último. Diagnóstico. |

Estilo

O CSS é injetado sozinho no momento em que o componente monta. Não há folha para linkar.

Os componentes herdam a tipografia da sua página e trazem o próprio reset escopado às raízes sevn_* — nada do que eles definem vaza para o resto do seu site.

As cores são custom properties com prefixo --sevn-, então você pode sobrescrevê-las pela cascata:

:root {
	--sevn-color-primary: #0a3e27;
	--sevn-color-secondary: #c38214;
}

Ou pela API, que dá no mesmo:

Core.setTheme({ primary: '#0a3e27', secondary: '#c38214' });

Se preferir carregar os tokens antes do JS, o arquivo também é publicado:

import '@sevn/elections-components/styles.css';

Props comuns

Todos os 25 componentes aceitam:

| Prop | Tipo | Padrão | Para quê | | ---------- | ------------- | -------------- | ------------------------------------------------------------------------------------------------- | | mock | boolean | false | Renderiza com dados de exemplo, sem tocar na API. Útil para montar o layout antes de ter a chave. | | rawStyle | boolean | false | Remove o cartão externo (borda, fundo, respiro) para encaixar no seu layout. | | header | HeaderProps | por componente | { title, description, tag, tagColor } — o cabeçalho exibido no topo. |

Catálogo

| Componente | Arquivo UMD | Título padrão | Props próprias | | -------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FinalResults | final-results.js | Presidente | showAd: boolean = trueyear: number = 2026round: number = 1 | | StateWinners | state-winners.js | Quem ganhou em cada estado | year: number = 2026round: number = 1defaultUf: string \| null | | VoteCountingProgress | vote-counting-progress.js | Evolução da apuração | year: number = 2026round: number = 1 | | VoteComparisonByRound | vote-comparison-by-round.js | Comparação de votos entre 1º e 2º turno | defaultOffice: 'president' \| 'governor' = 'president'defaultUf: string \| null | | CountingProgress | counting-progress.js | Progresso da Apuração | year: number = 2026defaultOffice: 'president' \| 'governor' = 'president'round: number = 1defaultUf: string \| null | | StateLeaders | state-leaders.js | Veja quem lidera em cada estado | year: number = 2026defaultOffice: 'president' \| 'governor' = 'president' | | CurrentLeader | current-leader.js | Liderança Atual | year: number = 2026round: number = 1 | | TopDispute | top-dispute.js | Disputa no topo | year: number = 2026 | | CandidateRanking | candidate-ranking.js | Posições de liderança | year: number = 2026 | | LiveSummary | live-summary.js | Resumo em tempo real | year: number = 2026round: number = 1 | | CandidatePerformanceByMunicipality | candidate-performance-by-municipality.js | Consulte o desempenho dos candidatos por município | year: number = 2026defaultOffice: 'president' \| 'governor' = 'president'defaultUf: string \| nulldefaultMunicipality: string \| nulldefaultRound: '1turno' \| '2turno' \| '' = '' | | RoundResultsAnalysis | round-results-analysis.js | Análise de resultados por turno | year: number = 2026defaultOffice: 'president' \| 'governor' = 'president'defaultUf: string \| nulldefaultMunicipality: string \| null | | ElectionStatus | election-status.js | Situação eleitoral | year: number = 2026defaultOffice: 'president' \| 'governor' = 'president'defaultUf: string \| nulldefaultMunicipality: string \| null | | CandidateResultsByMunicipality | candidate-results-by-municipality.js | Consulte o desempenho dos candidatos por município | year: number = 2026 | | MunicipalityPerfomanceMap | municipality-perfomance-map.js | Consulte o desempenho dos candidatos por município | round: number = 1year: number = 2026 | | CandidateByState | candidate-by-state.js | Governador por Estado | round: number = 1year: number = 2026 | | CandidateCarrouselStateResultsViewer | candidate-carrousel-state-results-viewer.js | Governador | year: number = 2026office: 'president' \| 'governor' = 'governor'defaultUf: string \| null | | CandidateDuelStateResultViewer | candidate-duel-state-result-viewer.js | Governador | round: number = 1year: number = 2026showPosition: boolean = falsepositionTagColor: stringdefaultUf: string \| null | | CandidateMapByState | candidate-map-by-state.js | Governador por Estado | year: number = 2026round: number = 1 | | ElectionRoundAnalysis | election-round-analysis.js | Análise de resultados por turno | year: number = 2026defaultUf: string \| nulldefaultOffice: 'president' \| 'governor' = 'governor' | | CandidateMultiRingChartPerformanceByMunicipality | candidate-multi-ring-chart-performance-by-municipality.js | Consulte o desempenho dos candidatos por município | year: number = 2026defaultUf: string \| nulldefaultMunicipality: string \| nulldefaultOffice: 'president' \| 'governor' \| 'senator' = 'governor' | | ResultsByOffice | results-by-office.js | Resultados por cargo | year: number = 2026defaultOffice: 'senator' \| 'federal_deputy' \| 'state_deputy' = 'senator'itemsPerPage: number = 10defaultUf: string \| nulldefaultMunicipality: string \| null | | VotingMap | voting-map.js | Mapa de Votação | showAd: boolean = falseyear: number = 2026round: number = 1defaultUf: string \| nulldefaultMunicipality: string \| nulldefaultOffice: 'president' \| 'governor' \| 'senator' = 'president' | | CandidateCountingProgress | candidate-counting-progress.js | Evolução da Apuração para Governador | year: number = 2026defaultUf: string \| null | | PartyPerformance | party-performance.js | Desempenho por Partido (senador/deputado) | year: number = 2026defaultUf: string \| nulldefaultOffice: 'federal_deputy' \| 'state_deputy' \| 'senator' = 'federal_deputy' |

Limitações conhecidas

Os tamanhos internos usam rem, que resolve contra o font-size do <html> da sua página. Se você usa a técnica de html { font-size: 62.5% }, os componentes vão encolher junto. Enquanto isso não muda, contorne isolando o container:

#placeholder {
	font-size: 16px;
}
html {
	font-size: 100%;
}

Desenvolvimento

Este repositório contém tanto a biblioteca quanto o showcase que a demonstra.

bun install
bun run dev          # showcase em http://localhost:4321
bun run check        # svelte-check
bun run test         # testes unitários (transporte, config)

Empacotamento:

bun run build:pkg    # ESM + CJS + tipos + estilos + 25 bundles UMD
bun run test:types   # type-check de um consumidor contra o dist publicado
bun run serve:test   # sobe test/umd.html para exercitar o UMD no navegador
bun run build:cdn    # monta dist-cdn/ na estrutura do R2

Os componentes são escritos em Svelte 5 (src/components), sobre primitivos internos (src/lib). Nada de src/lib é público: mudanças ali não são breaking changes. O que o consumidor vê é src/index.ts — classes JS geradas por defineComponent, com as props tipadas em src/components/props.ts.

O showcase é SvelteKit, mas o pacote não é: vite.lib.config.ts e scripts/build-umd.mjs não usam o plugin do SvelteKit. O @sveltejs/package não é usado em lugar nenhum — ele não bundla, só copia .svelte, e era exatamente isso que obrigava o consumidor a ter Svelte.

Publicação

npm publish          # prepublishOnly roda build, testes e publint

Deploy para o R2

cp .env.example .env    # preencha as chaves do R2 (abaixo)
bun run deploy:cdn      # build dos UMD -> dist-cdn/ -> upload -> purge do edge

deploy:cdn fala a API S3 do R2 direto, compara o MD5 local com o ETag remoto e sobe só o que mudou — os ~5.600 mapas ficam parados depois da primeira vez. Variações:

bun run deploy:cdn:dry           # mostra o diff sem enviar nada
bun run deploy:cdn:components    # só os bundles UMD (o caso comum)
bun run cdn:cors                 # aplica r2-cors.json, uma vez só

As chaves

Duas, no .env (que é gitignored):

1. Token do R2, para o upload. Dashboard → R2 Object Storage → APIManage API tokensCreate API token

  • Permissão: Object Read & Write
  • Escopo: Apply to specific buckets → o bucket de produção
  • Copie Access Key ID e Secret Access Key. O Account ID é o subdomínio do endpoint S3, que aparece em Settings do bucket.

2. Token de purge (opcional), para invalidar o edge na hora. Dashboard → My Profile → API TokensCreate TokenCustom token

  • Permissão: Zone → Cache Purge → Purge
  • Zone Resources: Include → Specific zone → sevnelections.com
  • O Zone ID está no Overview da zona.

Sem o segundo par o deploy funciona igual — o edge só se atualiza sozinho em até s-maxage em vez de imediatamente.

Cache: o que é imutável e o que não é

Os dois tipos de arquivo têm ciclos de vida opostos, e tratá-los igual era um bug real: os bundles UMD subiam immutable num caminho que é sobrescrito a cada release, o que prendia o navegador do cliente na versão antiga por até um ano, sem nem revalidar no reload.

| Caminho | Muda no mesmo endereço? | cache-control | | ------------------------------- | ----------------------------- | -------------------------------------------------- | | static/v1/** | Não — mudar é publicar /v2/ | max-age=31536000, immutable | | components/releases/<id>/*.js | Nunca | max-age=31536000, immutable | | components/v1/*.js | Sim, a cada release | max-age=300, s-maxage=60, stale-while-revalidate |

components/v1/<nome>.js é o endereço colado no HTML do cliente: precisa continuar estável e poder mudar de conteúdo. O navegador segura 5 minutos e o edge 60 segundos; passado isso, o stale-while-revalidate serve a cópia velha na hora e busca a nova em segundo plano, então a expiração nunca custa latência para quem está acompanhando apuração.

Todo deploy também arquiva uma cópia imutável em components/releases/<id>/<nome>.js, com <id> derivado do conteúdo dos bundles. É o que permite fixar uma versão ou voltar atrás sem depender do alias.

Em dia de apuração, para encurtar a janela:

CDN_ALIAS_MAX_AGE=30 CDN_ALIAS_S_MAXAGE=10 bun run deploy:cdn

O embed do cliente leva ?cid=, e a Cloudflare cacheia por URL completa — o que faria o purge acertar só a URL sem query. Por isso a zona tem uma Cache Rule, Componentes UMD: ignora query string na chave de cache, em /components/*: todas as variantes ?cid= compartilham uma entrada, então um purge cobre todos os clientes. É seguro porque a cid é lida no navegador, a partir da própria tag <script> — o arquivo servido não varia com a query.

Essa mesma zona tem uma regra anterior (Cache JSON files) que aplica edge_ttl: override_origin 300 em todo o host da CDN. Na prática o edge segura 300s em vez dos 60s do s-maxage; o purge no fim do deploy é o que torna isso irrelevante.

Se algum arquivo em static/v1/** mudar de conteúdo no mesmo caminho, o deploy avisa — quem já baixou não verá a versão nova. O caminho certo nesse caso é subir o assetsVersion em src/core/config.ts (v1v2).

Fallback pelo wrangler

scripts/upload-cdn.mjs continua existindo para quando não há chave S3 à mão: usa wrangler r2 object put (autenticação por wrangler login) e um manifesto local para retomar de onde parou. É bem mais lento — um processo Node por objeto — mas escreve os mesmos cabeçalhos, porque os dois scripts compartilham scripts/cdn-policy.mjs.

wrangler login
bun run build:cdn
node scripts/upload-cdn.mjs --bucket=SEU_BUCKET --dry-run
node scripts/upload-cdn.mjs --bucket=SEU_BUCKET

Licença

MIT