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

@thewishlistco/storefront-event-capture

v1.1.0

Published

JS capture tag for recording search events on Shopify storefronts. Captures search queries across all major search providers (Shopify native, Algolia, Klevu, Bloomreach) and sends them to the TWC ingest endpoint as `search_submitted` events.

Readme

@thewishlistco/storefront-event-capture

JS capture tag for recording search events on Shopify storefronts. Captures search queries across all major search providers (Shopify native, Algolia, Klevu, Bloomreach) and sends them to the TWC ingest endpoint as search_submitted events.

Installation

Retailers add the script tag to their Shopify theme (layout/theme.liquid) before </head>:

<script
  src="https://d2n9tm43zyz9x3.cloudfront.net/capture.js?tenant=TENANT_ID"
  defer
></script>

Replace TENANT_ID with the tenant identifier provided by TWC at onboarding.

The CloudFront URL above is the one to hand to retailers. Do not point them at unpkg — that serves whatever was last npm published, which is not necessarily what is on the CDN.

How it works

The script reads its own src URL to extract the tenant token — no server-side rendering required. It captures search queries using four complementary strategies:

| Strategy | Covers | Result count | |----------|--------|--------------| | URL query parameter (?q=) | Shopify native, most providers that redirect to a results page | Read from the rendered results page | | Enter key on search input | Live search, autocomplete | Staged — see below | | Form submit | Explicit search form submissions | Staged — see below | | Algolia algolia:search custom event | Algolia InstantSearch | From nbHits |

Deduplication and result counts

A search typed into a form fires two strategies across a page navigation: the keypress on the originating page, and the ?q= check on the results page. These run in separate JS contexts, so in-memory dedup cannot see across them.

Dedup state therefore lives in sessionStorage, which is per-tab and survives navigation. The same query is sent at most once per 30 seconds, which also covers reloads and back-button returns to a results page.

The result count only exists on the results page, so the results page is the canonical event for a navigating search. Enter/submit stage the query rather than posting it; the results page then claims the staged query and posts it with the count. If nothing navigates within 2.5 seconds — a live/AJAX search — the originating page posts it instead. A staged query the shopper abandons is flushed on the next page load with a null count rather than being dropped.

Counts are read from the theme's rendered results total (#ProductCountDesktop, #ProductCount, .product-count__text, [data-results-count] — Dawn and the OS 2.0 themes derived from it). When no selector matches, resultCount is null; the script never falls back to counting product cards, since a paginated grid would undercount and a wrong number is worse than no number.

Identity

Each event carries identity so searches join the rest of the TWC clickstream:

| Field | Source | |-------|--------| | clientId | Shopify _shopify_y visitor cookie (the same value the Shopify web pixel sends as clientId). If absent, a first-party twc_vid cookie (UUID, 1 year). twc_vid is not created when Shopify's Customer Privacy API reports analytics declined — the event is then sent without clientId. | | CustomerId.id | Logged-in Shopify customer id: window.TWC_CUSTOMER.id, else ShopifyAnalytics.meta.page.customerId, else __st.cid. | | CustomerId.email | Only when the theme injects it (optional), before the script tag: |

{% if customer %}
<script>window.TWC_CUSTOMER = { id: {{ customer.id | json }}, email: {{ customer.email | json }} };</script>
{% endif %}

Anonymous searches are attributed to the customer later via clientId (ClickHouse clickstream_resolved).

Events are POSTed to the TWC Lambda ingest endpoint:

POST https://4ag6zmnqub.execute-api.ap-southeast-2.amazonaws.com/getCustomerEvents/

Development

npm install
npm run dev     # watch mode
npm run build   # produces dist/capture.js

Publishing

npm publish     # runs build automatically via prepublishOnly

The compiled dist/capture.js is what gets deployed. The source in src/ is TypeScript and is not included in the published package.

Deploying

The tag is served from S3 behind CloudFront at a stable URL — retailers hardcode it into theme.liquid, so the filename never changes and the CDN copy is mutable. That makes an upload alone insufficient: CloudFront caches per edge location, so POPs that already hold the previous object keep serving it until their TTL expires.

npm run build

aws s3 cp dist/capture.js s3://<bucket>/capture.js \
  --content-type 'application/javascript' \
  --cache-control 'public, max-age=300'

aws cloudfront create-invalidation \
  --distribution-id <DISTRIBUTION_ID> --paths '/capture.js*'

--paths '/capture.js*' rather than /capture.js so the invalidation also covers the per-tenant query-string variants if the distribution caches on query strings.

Setting Cache-Control matters as much as the invalidation. With no Cache-Control and no Expires, CloudFront falls back to the distribution's Default TTL (24h by default) and browsers apply heuristic freshness derived from Last-Modified, which can hold a stale tag in shoppers' caches for days. A short max-age keeps fixes propagating in minutes at negligible origin cost for a ~3 kB file.

Verifying a deploy

The S3 ETag is the md5 of the object, so compare it against the local build:

md5 -q dist/capture.js
curl -sI 'https://d2n9tm43zyz9x3.cloudfront.net/capture.js' | grep -i 'etag\|cache-control\|x-cache'

Matching etag means the edge is serving the build you just made. Re-run against a few curl --resolve targets, or simply retry until x-cache: Hit from cloudfront, to confirm propagation rather than a single cold edge.