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

@azzas/azzas-tracker-web

v2.0.7

Published

Camada única de **data tracking** das lojas web. A loja chama `trackWebEvent`, a lib resolve os parâmetros a partir dos dados brutos da plataforma (produto, orderForm, SLAs...) e faz o push no `dataLayer` no formato esperado pelo GTM/GA4.

Readme

azzas-tracker-web

Camada única de data tracking das lojas web. A loja chama trackWebEvent, a lib resolve os parâmetros a partir dos dados brutos da plataforma (produto, orderForm, SLAs...) e faz o push no dataLayer no formato esperado pelo GTM/GA4.

Toda a inteligência (formatação, normalização, atribuição de vitrine, promoção) fica aqui, não nos repositórios das lojas.


Instalação

NPM

npm install @azzas/azzas-tracker-web
import { trackWebEvent, initTracker } from '@azzas/azzas-tracker-web'

CDN / script tag (expõe o global AzzasTracker)

<script src="https://cdn.jsdelivr.net/npm/@azzas/azzas-tracker-web/dist/mod.global.js"></script>
<script>
  AzzasTracker.trackWebEvent('SEARCH', { adapter: 'META', meta: { search_term: 'vestido', search_found: true, search_quantity: 42 } })
</script>

| Build | Uso | |---|---| | dist/mod.js / dist/mod.cjs | ESM / CJS (import via npm) | | dist/mod.global.js | IIFE ES2018, global AzzasTracker (CDN) | | dist/mod.vtex.global.js | IIFE ES5, sem quebrar o Uglify do checkout VTEX legado |


Uso

1. Configure uma vez

initTracker({ brand: 'reserva', currency: 'BRL' })

brand e currency daqui têm prioridade sobre qualquer valor vindo do payload. Se não chamar, brand é resolvido pelo adapter e currency cai em BRL.

2. Dispare eventos com trackWebEvent

trackWebEvent(EVENT, payload)
  • EVENT: chave de EVENTS ('VIEW_ITEM_LIST', 'ADD_TO_CART', 'PURCHASE'...).
  • payload: { adapter, ...dados brutos, meta, window? }. O adapter diz de onde vêm os dados, a lib extrai os parâmetros. meta carrega o que não dá pra derivar dos dados de um produto (região, termo de busca, banner clicado...).

Por que existe um adapter por loja

O dataLayer recebe sempre o mesmo formato de items, mas cada loja entrega a entidade de produto com um shape diferente. O adapter é a tradução entre os dois. Mesmo campo, caminho diferente em cada origem:

| Campo | DECO | RESERVA / ANIMALE | HEADLESS (PDP) | HEADLESS (PDC) | NV | ORDERFORM | |---|---|---|---|---|---|---| | item_name | alternateName | isVariantOf.name | productName | name | productName | name sem o skuName | | item_id | inProductGroupWithID | isVariantOf.productGroupID | productId | isVariantOf.productGroupID | productId | productId | | item_ref | additionalProperty[RefId] | gtin | productReference | não existe no retorno, vai null | productReference | productRefId | | seller_id | offers.offers[0].seller | offers.offers[].seller.identifier (Animale: o disponível, senão o primeiro) | items[0].sellers[0].sellerId | offers.offers[].seller.identifier (primeiro disponível) | seller default do item ativo | seller | | tamanho | name.split(' - ')[1] | Reserva: name.split(' - ')[1] / Animale: additionalProperty[Tamanho] | items[0].name.split(' - ')[1] | additionalProperty[Tamanho] | name.split(' - ')[1] do item ativo | skuName.split(' - ')[1] |

Repare no HEADLESS: a mesma loja entrega produto num shape na PDP (retorno da search API, com items[]) e em outro na PDC (JSON-LD com isVariantOf). Por isso alguns adapters têm zone. Sem isso, um único getter teria que adivinhar em qual formato o produto veio.

Como um adapter nasce: o types.ts de cada adapter é o type da entidade de produto copiado direto da loja, não uma modelagem nossa. Antes de criar, a pergunta pro time da loja é: "a entidade de produto é uma só, padronizada no site inteiro, ou muda dependendo de onde está sendo usada?". Se muda, cada variação vira uma zone. Se algum campo não existe na origem (ex.: item_ref na PDC do headless), o getter devolve null em vez de inventar.

A tipagem (PayloadFor<E>) garante em tempo de compilação que:

  • eventos com items não aceitam adapter: 'META';
  • os campos de meta obrigatórios pro evento são exigidos.

Exemplos

products precisa ter exatamente o shape que o adapter espera. O type de produto de cada adapter está em src/params/adapters/<adapter>/types.ts e foi copiado direto da loja. A tipagem de trackWebEvent é inferida pelo adapter informado: se o objeto que você passa não bate com esse type, o TS acusa erro na própria chamada. Não adapte o produto na mão pra "passar": se o shape da loja mudou, o adapter é que precisa ser atualizado. Os únicos campos extras aceitos são index e item_list_name.

Vitrine (Deco)

trackWebEvent('VIEW_ITEM_LIST', {
  adapter: 'DECO',
  products: products.map((p, index) => ({ ...p, index, item_list_name: 'Vitrine Home' })),
  meta: { region: 'home' },
})

Clique no produto

trackWebEvent('SELECT_ITEM', {
  adapter: 'RESERVA',
  products: [{ ...product, index, item_list_name: 'Lançamentos' }],
  meta: { region: 'home' },
})

Checkout (orderForm VTEX)

trackWebEvent('ADD_SHIPPING_INFO', {
  adapter: 'ORDERFORM',
  orderForm: vtexjs.checkout.orderForm,
  meta: { pre_filled: false },
})

Evento sem produto

trackWebEvent('SEARCH', {
  adapter: 'META',
  meta: { search_term: 'vestido', search_found: true, search_quantity: 42 },
})

Banner promocional (ver Promotion)

trackWebEvent('SELECT_PROMOTION', {
  adapter: 'META',
  meta: {
    region: 'home',
    promotion_name: 'Black Friday',
    creative_name: 'banner-hero',
    creative_slot: 'slot-1',
    promotion_url: '/colecao/black-friday', // só pro match interno, não vai pro dataLayer
  },
})

Dados do usuário

trackWebEvent('CUSTOM_USER_INFO', {
  adapter: 'META',
  meta: { user_info: { account: 'lojausereserva', platform: 'store', userEmail: '[email protected]' } },
})

Formulário (manuallyInfo)

// checkout/shipping: manda só a fatia de endereço
trackWebEvent('CUSTOM_USER_INFO', {
  adapter: 'META',
  meta: { user_info: { account: 'lojafarm', platform: 'store', userEmail: '[email protected]',
    manuallyInfo: { address: { street: 'Av Paulista', number: '1000', postalCode: '01310-100' } },
  }},
})

user_info consulta /_v/user-info no domínio da conta (account × platform, ver PROD_DOMAINS) e devolve customer + address. CPF sempre sai com hash SHA-256 em cpf e limpo em document.

O manuallyInfo é o que o usuário acabou de digitar, então entra por cima do Master Data, campo a campo:

  • Cada formulário manda só a sua fatia. checkout/profile e account/profile mandam os campos pessoais no nível de cima; checkout/shipping manda address. A fatia que o formulário não cobre continua vindo do Master Data.
  • Só sobrescreve o que foi preenchido. '', null e undefined são ignorados e o valor do Master Data permanece. false conta como preenchido, senão o opt-out nunca chegaria no CRM.
  • Se a API falhar, sobra o manuallyInfo mais email/id do contexto.

A rota /_v/user-info não é da VTEX: é um app VTEX IO nosso, instalado em cada conta. Ele recebe o email e consulta o Master Data com AppKey/AppToken da conta:

| Entidade | Busca | Campos | |---|---|---| | CL (cliente) | email | birthDate, phone, homePhone, firstName, lastName, gender, id, document, userId | | AD (endereço) | userId do cliente | city, country, neighborhood, street, postalCode, number, state |

Por isso a lista de domínios em PROD_DOMAINS: a chamada precisa ir pro host onde o app está instalado (.myvtex.com na loja, secure.* no checkout) pra não cair em CORS. Conta nova = instalar o app e adicionar o domínio aqui.

É necessário configurar a apiToken e apiKey nas configurações da app.


Como funciona

trackWebEvent(EVENT, payload)
  → EVENTS[EVENT].requiredParams          quais parâmetros o evento precisa
  → adapters[payload.adapter][param]()    cada getter extrai um parâmetro dos dados brutos
  → itemsWithPromotion(items)             enriquece items com promoção (se houver)
  → pushToDataLayer(event, parameters)    push no window.dataLayer
flowchart LR
    Store["Loja<br/>trackWebEvent(EVENT, payload)"] --> Events["EVENTS[EVENT]<br/>requiredParams"]
    Events --> Formatter["formatter<br/>getParameters()"]
    Formatter --> Adapter["adapters[payload.adapter]<br/>1 getter por param"]
    Adapter --> Params["parameters"]
    Params -->|"tem items?"| Promo["itemsWithPromotion()<br/>enriquece com banner"]
    Promo --> Push["pushToDataLayer()"]
    Params --> Push
    Push --> DL[("window.dataLayer")]

    Store -.->|"SELECT_PROMOTION"| Cookie[("cookie<br/>__azzas_trk_promo")]
    Cookie -.-> Promo
    Store -.->|"PURCHASE"| Clear["limpa cookie"]

Push no dataLayer:

| hasEcommerce | Formato | |---|---| | true | { ecommerce: null } seguido de { event, ecommerce: { ...params } } | | false | { event, ...params } |

Erros nunca quebram a loja: são capturados e logados como [DT] Error tracking event .... Getter faltando pro adapter loga [DT] Missing getter "x" for adapter "Y" e o parâmetro vai como null.


Adapters

Todos herdam o META (getters que leem só de meta) e sobrescrevem o que depende dos dados brutos.

| Adapter | Payload esperado | Origem | |---|---|---| | META | { meta } | Eventos sem produto | | DECO | { products } (JSON-LD Product do Deco) | Lojas Deco | | RESERVA | { products } | Reserva | | ANIMALE | { zone: 'PDP' \| 'PDC', products } | Animale | | HEADLESS | { zone: 'PDP' \| 'PDC', products } | Lojas headless VTEX (zona Minicart tipada, ainda não roteada no adapter) | | NV | { products } (VTEX search API) | ByNV | | ORDERFORM | { orderForm } | Checkout VTEX (carrinho, frete, pagamento, compra) | | PICKUP | { flag_pickup, shippings } (SLAs) | Consulta de CEP |

Todo payload aceita brand como override pontual e window (pra push em outro contexto, ex.: iframe).

Alguns getters leem campos da raiz do payload, não de meta (não estão na tipagem, passe como any se precisar):

| Campo | Getter | Usado em | |---|---|---| | appliedCoupon | coupon | ADD_COUPON, PURCHASE | | appliedSellerCodName | seller_cod_name | ADD_COUPON, PURCHASE | | couponMessage | coupon_message | ADD_COUPON (fallback: orderForm.messages com "cupom") | | items, value, line_items, total_discount, available_grid | idem | override pronto, pula a extração |

Em products, cada item pode levar index e item_list_name — é isso que alimenta items[].index, items[].item_list_name e o match de promoção.


meta

| Campo | Usado em | |---|---| | region | quase todos (home, pdp, minicart...) | | pre_filled | checkout (add_personal_info, add_shipping_info, add_payment_info) | | search_term, search_found, search_quantity | SEARCH | | zipcode | SEARCH_ZIPCODE | | promotion_name, creative_name, creative_slot, promotion_url | VIEW_PROMOTION, SELECT_PROMOTION | | content_type | SELECT_CONTENT | | method, type | AUTH_ACTION | | item_ref, size | NOTIFY_ME | | color, size, price_range, category, ordering | REFINE_RESULTS | | slot_per_line | GRID_SIZE | | user_info | CUSTOM_USER_INFO | | brand | override quando initTracker não foi chamado |


Eventos

Chave → nome no dataLayer → parâmetros. Fonte: src/core/constants.ts.

Catálogo / navegação

| Evento | event | Params | |---|---|---| | VIEW_ITEM_LIST | view_item_list | region, brand, line_items, items | | SELECT_ITEM | select_item | brand, region, items, line_items | | VIEW_ITEM / CUSTOM_VIEW_ITEM | view_item / custom_view_item | brand, line_items, currency, value, available_grid, items | | SEARCH | search | brand, search_term, search_found, search_quantity | | REFINE_RESULTS | refine_results | brand, region, color, price_range, size, category, ordering | | GRID_SIZE | grid_size | brand, slot_per_line | | SELECT_CONTENT | select_content | brand, content_type | | NOTIFY_ME | notify_me | brand, size, item_ref | | VIEW_PROMOTION | view_promotion | brand, region, currency, promotion_name, creative_slot, creative_name | | SELECT_PROMOTION | select_promotion | brand, region, currency, promotion_name, creative_slot, creative_name |

Carrinho

| Evento | event | Params | |---|---|---| | ADD_TO_CART / CUSTOM_ADD_TO_CART | add_to_cart / custom_add_to_cart | brand, region, line_items, currency, value, items | | REMOVE_FROM_CART / CUSTOM_REMOVE_FROM_CART | remove_from_cart / custom_remove_from_cart | brand, region, line_items, currency, value, items | | ADD_TO_WISHLIST | add_to_wishlist | brand, region, line_items, currency, value, items | | CUSTOM_VIEW_CART | custom_view_cart | brand, region, line_items, currency, value, total_discount, subtotal, items | | ADD_COUPON | add_coupon | brand, region, coupon, coupon_message, seller_cod_name | | SEARCH_ZIPCODE | search_zipcode | brand, region, shippings, zipcode, flag_pickup |

Checkout

| Evento | event | Params | |---|---|---| | BEGIN_CHECKOUT / CUSTOM_BEGIN_CHECKOUT | begin_checkout / custom_begin_checkout | brand, line_items, currency, value, total_discount, subtotal, items | | ADD_PERSONAL_INFO | add_personal_info | brand, pre_filled, line_items, currency, value, total_discount, subtotal | | ADD_SHIPPING_INFO / CUSTOM_ADD_SHIPPING_INFO | add_shipping_info / custom_add_shipping_info | brand, pre_filled, shipping, shipping_tier, line_items, currency, value, total_discount, subtotal, items | | ADD_PAYMENT_INFO / CUSTOM_ADD_PAYMENT_INFO | add_payment_info / custom_add_payment_info | brand, pre_filled, line_items, currency, value, total_discount, payment_type, subtotal, items | | ORDER_REVIEWED | order_reviewed | brand, line_items, currency, value, total_discount, subtotal, payment_type | | PURCHASE / CUSTOM_PURCHASE | purchase / custom_purchase | brand, shipping_tier, line_items, currency, value, shipping, transaction_id, total_discount, payment_type, seller_cod_name, subtotal, coupon, items |

Outros

| Evento | event | Params | |---|---|---| | AUTH_ACTION | auth_action | region, method, type, brand | | CUSTOM_USER_INFO | custom_user_info | user_info |

Variantes CUSTOM_* têm os mesmos params do evento padrão e só mudam o nome no dataLayer. Use quando a VTEX já dispara nativamente o evento com o mesmo nome (acontece na loja e no checkout), pra não duplicar no GTM.

Formato de items

| Campo | Descrição | |---|---| | item_id / item_sku / item_ref | productId / skuId / referência | | item_name, item_brand | nome e marca (capitalizada em Reserva/NV/orderForm, minúscula no Deco; Farm normalizada pra farmrio / farmetc) | | item_category, item_category2 | outlet | bazar | sale | Coleção; categoria da árvore | | item_variant, item_variant2 | cor e tamanho (tamanho em maiúsculo) | | price, discount, quantity, index | preço final, desconto (lista − final), qtd, posição na lista | | item_url, image_url, seller_id | link, imagem (500x500), seller | | item_list_name, item_shipping_tier | vitrine de origem, frete do item (checkout) | | promotion_name, creative_name, creative_slot | preenchidos pelo Promotion quando o item veio de um banner |


Promotion

Em teste (rollout) em Reserva, Farm e Animale. A lógica é agnóstica de adapter e roda pra qualquer loja que dispare SELECT_PROMOTION; o rollout é sobre quais lojas já emitem o evento.

Objetivo: atribuir ao produto o banner que originou o clique, mesmo que a compra aconteça em outra página ou subdomínio (loja → checkout).

Fluxo

  1. SELECT_PROMOTION salva a promoção no cookie __azzas_trk_promo (domínio raiz, vale em todos os subdomínios). VIEW_PROMOTION só reporta, não grava.
  2. Em qualquer evento com items, cada item tenta casar com uma promoção salva:
    • por vitrine: item_list_name do item == pathname da promotion_url do banner;
    • por anotação: o item já foi vinculado a uma promoção antes.
  3. Casou → o item recebe promotion_name, creative_name, creative_slot.
  4. PURCHASE / CUSTOM_PURCHASE limpa o cookie.

Anotação: só SELECT_ITEM, ADD_TO_CART e CUSTOM_ADD_TO_CART gravam no cookie de qual promoção o item veio (intenção real). VIEW_ITEM_LIST não anota de propósito. É a anotação que mantém a atribuição no carrinho/checkout, onde não existe item_list_name de vitrine.

| Regra | Valor | |---|---| | Janela pra casar por vitrine | 10 min após o clique no banner | | Promoção com item anotado | vive até 24h (ou até o purchase) | | Máx. de promoções no cookie | 5 (descarta a mais antiga) | | Máx. de itens anotados | 30 (descarta os mais antigos) | | Vitrine casa com 2 promoções | vale a do clique mais recente |

promotion_url nunca vai pro dataLayer; existe só pro match. Código: src/core/promotion.ts.


Debug

Adicione ?__trk_inspect__ na URL. Cada trackWebEvent loga no console [DT vX.Y.Z] EVENT com { context, parameters }.


Desenvolvimento

npm run build          # tsup → dist/
npm run dev:npm        # build em watch
npm run dev:deno       # build + pack + serve em http://localhost:4507 (simula CDN)

Pra testar via CDN no consumidor, aponte o script pra http://localhost:4507/dist/mod.global.js.

Publicar

npm run release:homolog   # prerelease -preview.N, tag "preview"
npm run release:prod      # patch + publish

Contribuindo

  • Novo evento: adicionar em EVENTS com name, hasEcommerce, requiredParams. Todo param listado precisa existir como getter nos adapters que vão disparar o evento, senão sai null (com erro no console).
  • Novo parâmetro: criar o getter no META se vier só de meta; nos adapters específicos se depender dos dados brutos.
  • Novo adapter: pasta em src/params/adapters/<nome>/ com index.ts (...metaAdapter + getters), types.ts; registrar em adapters no formatter.ts e no union AdapterPayload em types.ts.
  • Nos consumidores: mande os dados o mais crus possível. Tratamento é responsabilidade da lib.
  • Documente eventos novos no Notion.

Authors

  • Lucas Soares