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

@personizely/sdk

v0.1.0

Published

Personizely JavaScript SDK - isomorphic full-stack experiment, feature-flag and personalization decisioning for the browser, Node and the edge

Readme

@personizely/sdk

Isomorphic full-stack experiment, feature-flag and personalization decisioning for Personizely — in the browser, Node and at the edge.

The SDK speaks the same protocol as the Personizely snippet: it loads your per-website datafile, decides campaigns locally (no per-decision network call), and reports impressions/conversions to the tracker. You reference the real Personizely ids you already have — run a campaign by its id, read the response.

  • Zero dependencies, dual ESM/CJS, fully typed.
  • Local decisioning — deterministic, sticky bucketing with no round-trips.
  • Isomorphic — one visitor in the browser, many concurrent visitors on a server.
  • Pluggable stickiness — deterministic hashing by default; cookie, memory or your own store.

Scope: the SDK decides full-stack campaigns — feature flags plus full-stack experiments and personalizations. Content, theme, product, shipping, URL-split and template campaigns carry execution details that the Personizely snippet applies to the page/checkout, so they are not run by the SDK (runCampaign returns null for them, and runAllCampaigns skips them).

npm install @personizely/sdk

Quick start

import { Personizely } from '@personizely/sdk'

const ply = new Personizely({ apiKey: 'your-website-key' })
await ply.ready()

// In the browser the visitor defaults to the `_ply` cookie. Server-side, set it:
ply.setVisitor('user-42', { plan: 'pro' })

// Run a campaign by its Personizely id — the promise resolves to the response.
const result = await ply.runCampaign(1234)

// Full-stack feature flags:
if (result?.getFeature('checkout-v2').enabled) {
  const color = result.getFeature('checkout-v2').get('color', 'blue')
}

// A/B experiment — control vs. variant:
if (result && !result.control) {
  render(result.variationId, result.variationName)
}

// Track events & conversions, then drain before exit (Node):
ply.trackEvent('add_to_cart', 4999)
ply.trackGoal('purchase', 4999)
await ply.flush()

Concepts

The visitor lives on the client

Set the visitor once; every method reads it as ambient state — no id on each call.

ply.setVisitor('user-42', { plan: 'pro' }, { url: 'https://shop.test/pricing' })
await ply.runCampaign(1234)
ply.trackEvent('viewed_pricing')

attributes are your own values (used by audience rules like custom fields and data). context describes the request — everything is optional and evaluated only if provided:

context: {
  url, referrer, userAgent,           // → device / os / browser / query / entry-page
  utm: { source, medium, campaign },  // → utm rules
  locale, ip, cart, cookies,          // ip → geo via /filters; cart → cart rules
}

In the browser, context is auto-derived from location / navigator / document.

Serving many visitors on one server

Mutating a shared visitor would race across concurrent requests. Create a cheap per-request scope instead — it shares the loaded datafile and transport:

const ply = new Personizely({ apiKey })   // once, at boot
await ply.ready()

app.get('/', async (req) => {
  const visitor = ply.forVisitor(req.userId, { plan: req.plan }, {
    userAgent: req.headers['user-agent'],
    url: req.url,
    ip: req.ip
  })
  const flag = (await visitor.runCampaign(1234))?.getFeature('checkout-v2')
  // ...
})

Running everything at once

const all = await ply.runAllCampaigns()   // decides every campaign in the datafile
all.get(1234)?.control
all.getFeature('checkout-v2').enabled      // search a flag across all full-stack results
for (const result of all) { /* ... */ }

Bucketing (stickiness)

By default assignments are a deterministic hash of (visitorId, campaignId): sticky across calls and processes, with zero storage. Swap it out via bucketing:

import { Personizely, hashBucketing, cookieBucketing, memoryBucketing } from '@personizely/sdk'

new Personizely({ apiKey, bucketing: cookieBucketing() })   // browser: random + cookie persistence
new Personizely({ apiKey, bucketing: hashBucketing('salt') }) // reshuffle buckets with a salt

Bring your own (e.g. Redis) by implementing the interface — the SDK owns the weighted-share math, so an adapter only handles persistence and/or randomness:

import type { BucketingAdapter } from '@personizely/sdk'

const redisBucketing: BucketingAdapter = {
  async read (key)  { return JSON.parse(await redis.get(`ply:${key.visitorId}:${key.experimentId}`) || 'null') },
  async write (key, a) { await redis.set(`ply:${key.visitorId}:${key.experimentId}`, JSON.stringify(a)) }
}

Tracking

| Method | Effect | | --- | --- | | runCampaign(id) / runAllCampaigns() | Auto-emit a campaign/view impression when the visitor lands in a campaign (variant or control), deduped per visitor. | | trackEvent(name, value?) | Custom event; auto-attributes matching action: 'event' goals. | | trackGoal(nameOrId, value?) | Fire a specific goal (conversion) by name or id. | | identifyVisitor(traits) | Attach identity traits to the visitor. | | flush() | Await every in-flight request — call before a short-lived process exits. |

Values are monetary amounts in your currency's minor unit convention as the snippet uses them. Requests go to the tracker host from your datafile.

Datafile

The SDK fetches {cdnUrl}/{apiKey}.json. To decide with a datafile you already have (SSR, edge, tests), pass it directly and skip the fetch:

const ply = new Personizely({ apiKey, datafile })   // raw `data` object or normalized

Refresh it when campaigns change (scopes pick it up by reference):

await ply.refresh()                         // manual
new Personizely({ apiKey, pollInterval: 60_000 })   // or auto

Options

| Option | Default | Description | | --- | --- | --- | | apiKey | — | Public website key. Required unless a datafile with websiteId is passed. | | visitor | _ply cookie (browser) | { id, attributes, context }. | | datafile | — | Pre-supplied config; skips the CDN fetch. | | cdnUrl | Personizely CDN | Datafile host. | | trackerUrl | datafile.server | Tracker host override. | | bucketing | hashBucketing() | Stickiness adapter. | | fetch | global fetch | Custom fetch (old Node, proxies, tests). | | pollInterval | — | Auto-refresh the datafile every N ms. | | onAudienceUnresolved | 'exclude' | How to treat filters the SDK can't resolve (fail-closed by default). | | consent | — | false marks tracking as no-consent. | | onError | — | Callback for transport/refresh errors. |

License

MIT