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

@avsbhq/browser

v1.1.1

Published

Browser client SDK for the [A vs B](https://app.avsb.cloud) platform.

Readme

@avsbhq/browser

Browser client SDK for the A vs B platform.

Evaluate feature flags, run A/B experiments, and track conversion events in any browser context. For React apps, use @avsbhq/react which wraps this SDK with hooks and a provider.


1. Install

npm install @avsbhq/browser

Requires a modern browser with ES2020 support. Both ESM and CJS builds are shipped.

No mandatory peer dependencies. Install @avsbhq/utils if you need pino/winston logger adapters, OpenFeature integration, or middleware helpers.


2. Quickstart

import { AvsbClient } from '@avsbhq/browser'

const client = new AvsbClient({
  sdkKey: 'sdk_production_...',
  context: { kind: 'user', key: 'u_123', plan: 'pro' },
})

const result = await client.onReady()
if (!result.success) {
  // degraded: client still works on defaults; poll continues
}

const flag = client.getBoolFlag('new-checkout-flow', false)
if (flag.isEnabled()) {
  renderNewCheckout()
}

client.track('checkout_started', { value: 99.0, properties: { step: 'cart' } })

// Flush and tear down before app unloads
window.addEventListener('beforeunload', () => { void client.close() })

Construct the client once at application boot — not per page or per component.


3. SDK keys

Get the SDK key for the environment you want to target from your A vs B project's Environments page:

  1. Log in to app.avsb.cloud
  2. Open your project and go to Environments
  3. Copy the SDK key (format sdk_<environment>_<id>, e.g. sdk_production_clp1a2b3c4d5e6)

SDK keys are scoped to a single environment and grant flag-read access only — they cannot write to your project or read from other environments. They are safe to embed in browser bundles within those bounds.

VITE_AVSB_SDK_KEY=sdk_production_...
NEXT_PUBLIC_AVSB_SDK_KEY=sdk_production_...

Rotate keys from the Environments page. After rotation, the old key stops working for new SDK fetches within 5 minutes. In-memory datafiles that are already loaded continue to function until the next poll.


4. Identity

identify(context)

Replace the current evaluation context entirely. All flags are re-evaluated and any changed flags emit a flagChange event. Bucketing rehashes immediately.

// After user signs in
client.identify({
  kind: 'user',
  key: user.id,
  email: user.email,
  plan: user.plan,
})

updateAttributes(partial, contextKind?)

Merge new attributes into the current context without replacing the entire context. Defaults to the 'user' kind.

// User upgrades their plan mid-session
client.updateAttributes({ plan: 'enterprise' })

// Update organization attributes in a multi-context
client.updateAttributes({ tier: 3 }, 'organization')

alias(previousContext, newContext)

Record that an anonymous visitor and an identified user are the same person. Sends an alias event to the analytics pipeline and re-saves sticky bucket assignments from the previous context key under the new key.

// On sign-in: link the anonymous session to the identified user
await client.alias(
  { kind: 'user', key: anonymousId },
  { kind: 'user', key: user.id }
)

reset()

Return to an anonymous context and clear local sticky assignments. Call this on logout.

client.reset()

getContext()

Read-only snapshot of the current evaluation context.

const ctx = client.getContext()

5. Multi-context

Evaluate flags simultaneously against multiple context kinds — a user, their organization, and their device:

client.identify({
  kind: 'multi',
  user: { kind: 'user', key: 'u_123', plan: 'pro' },
  organization: { kind: 'organization', key: 'org_456', tier: 'enterprise' },
  device: { kind: 'device', key: 'd_abc', os: 'ios' },
})

When a multi-context is active, rules can target any context kind. A rule might bucket on user.key while matching an audience condition on organization.tier. The hashAttribute in each rule's datafile entry controls which context kind drives the bucket.


6. Reading flags

All evaluation methods return a Flag<T> object rather than a raw value. Access .value for the primitive or use the convenience methods on the Flag.

Typed variants

Use the typed methods to get a runtime type-safety check against the datafile's declared flag type. A type mismatch logs a warning and returns the default.

const darkMode    = client.getBoolFlag('dark-mode', false)
const theme       = client.getStringFlag('theme', 'light')
const maxItems    = client.getNumberFlag('max-results', 25)
const config      = client.getJsonFlag<{ timeout: number }>('api-config', { timeout: 5000 })

Generic getFlag<T>

Use when the type is dynamic or when you need an escape hatch:

const flag = client.getFlag<boolean>('checkout-v2', false)

The Flag<T> object

flag.value          // T — the variation value
flag.isEnabled()    // true if source === 'rule' && value is truthy
flag.variationKey   // 'on' | 'off' | 'control' | null (null = default served)
flag.exists()       // false only when source === 'not_found'
flag.source         // 'rule' | 'sticky' | 'holdout' | 'runtimeOverride' | 'default' | ...
flag.ruleId         // which rule matched (null if none)
flag.reasons        // string[] — human-readable evaluation path
flag.evaluatedAt    // ms epoch timestamp

getAllFlags(options?)

Returns all evaluated flags as a Record<string, Flag>. Exposures are suppressed by default.

const all = client.getAllFlags()
const allWithExposures = client.getAllFlags({ fireExposures: true })

DecideOption — per-call overrides

import { DecideOption } from '@avsbhq/browser'

const flag = client.getBoolFlag('my-flag', false, {
  decideOptions: [DecideOption.DISABLE_EXPOSURE],
})

Available options:

  • DISABLE_EXPOSURE — do not fire an exposure event for this call
  • INCLUDE_REASONS — populate flag.reasons even when normally suppressed
  • EXCLUDE_VARIABLES — return the variation key only, no variable payload
  • IGNORE_STICKY_BUCKET — skip sticky assignment lookup
  • ENABLED_FLAGS_ONLY — return default if the flag is not actively enabled

7. Tracking events

client.track('purchase_completed', {
  value: 199.0,
  properties: { plan: 'annual', sku: 'PRO_ANNUAL' },
})

TrackPayload fields:

  • value — numeric metric quantity (revenue, count, duration, etc.)
  • properties — free-form analytics payload forwarded verbatim
  • (No revenue field — use value instead)

Events are queued and flushed in batches. Events queued before onReady() resolves are held and flushed automatically after the tracker initializes.


8. Error handling

logger option

import { createLogger, consoleTransport } from '@avsbhq/browser'

const client = new AvsbClient({
  sdkKey: '...',
  context: { kind: 'user', key: 'u_1' },
  logger: createLogger({
    level: 'info',
    transports: [consoleTransport({ level: 'warn' })],
  }),
})

For pino or winston adapters:

import { createPinoTransport } from '@avsbhq/utils/log/pino'
import pino from 'pino'

const logger = createLogger({
  level: 'info',
  transports: [createPinoTransport({ logger: pino(), level: 'info' })],
})

onError callback

const client = new AvsbClient({
  sdkKey: '...',
  context: { kind: 'user', key: 'u_1' },
  onError: (err, source) => {
    myMonitoring.captureException(err, { tags: { source } })
  },
})

source is one of 'init' | 'poll' | 'stream' | 'track' | 'eval'.

Event bus

const unsubscribe = client.on('error', (err) => {
  myMonitoring.captureException(err)
})

client.on('configUpdate', ({ publishedAt, reason }) => {
  myLogger.info('datafile updated', { publishedAt, reason })
})

client.on('flagChange', ({ flagKey, previousValue, newValue }) => {
  // re-render or trigger side effects
})

9. SSR / hydration

Pre-fetch the datafile on the server and pass it as bootstrap to skip the initial network round-trip on the client:

// Server — fetch and serialize
import { AvsbClient } from '@avsbhq/browser'
// or use @avsbhq/next/server for App Router

const serverClient = new AvsbClient({
  sdkKey: process.env.AVSB_SDK_KEY!,
  context: userContext,
})
await serverClient.onReady()
const bootstrap = await serverClient.utils.serializeBootstrap(userContext)
// Pass `bootstrap` to the client as a serialized prop / window var
// Client — hydrate from the bootstrap blob
const client = new AvsbClient({
  sdkKey: 'sdk_production_...',
  context: userContext,
})
client.utils.hydrateBootstrap(bootstrapBlobFromServer)
// onReady() resolves immediately with source: 'bootstrap'

Bootstrap guarantees the same variation values on both the server render and the initial client render, preventing hydration mismatches.

For Next.js App Router, use @avsbhq/next and its AvsbHydrator server component instead of wiring this manually.


10. Graceful shutdown

Tab unload

window.addEventListener('beforeunload', () => {
  void client.flush()  // sendBeacon or fetch({keepalive:true})
})

Full teardown

await client.flush()  // drain in-flight events
await client.close()  // flush + stop polling + destroy tracker

close() calls flush() internally — calling both is safe but redundant.

Streaming

If streaming: true is configured, close() terminates the SSE connection and drains any pending events before resolving.


11. Testing

Install @avsbhq/test and use createMockClient to replace the real SDK in tests:

import { createMockClient, TestData } from '@avsbhq/test'

const td = TestData.flag('checkout-v2')
  .booleanFlag()
  .variationForUser('u_1', true)
  .fallthroughVariation(false)

const client = createMockClient({ flags: [td.build()] })

// In your component test — inject via AvsbTestProvider or pass directly
expect(client.getBoolFlag('checkout-v2', false).value).toBe(true)

For React component tests:

import { AvsbTestProvider } from '@avsbhq/test'

render(
  <AvsbTestProvider mockFlags={{ 'checkout-v2': true }}>
    <CheckoutButton />
  </AvsbTestProvider>
)

12. Migration

From LaunchDarkly JS

| LaunchDarkly JS | @avsbhq/browser | |---|---| | initialize(clientId, context, options) | new AvsbClient({ sdkKey, context }) | | client.waitForInitialization() | await client.onReady() | | client.variation('key', default) | client.getBoolFlag('key', false).value | | client.variationDetail('key', default) | client.getFlag('key', default) | | client.identify(context) | client.identify(context) | | client.track('event', { metricValue }) | client.track('event', { value }) | | client.on('change:key', cb) | client.on('flagChange', ({ flagKey, newValue }) => ...) | | client.flush() | await client.flush() |

Key differences:

  • getFlag returns a Flag<T> object, not a raw value. Access .value for the primitive.
  • All typed variants (getBoolFlag, getStringFlag, etc.) require an explicit defaultValue.
  • track uses value not metricValue for the numeric metric.
  • Multi-context is a first-class concept with kinded EvalContext rather than a separate LDMultiKindContext wrapper.

From Statsig JS

| Statsig JS | @avsbhq/browser | |---|---| | Statsig.initialize(clientKey, user) | new AvsbClient({ sdkKey, context }) | | Statsig.checkGate('key') | client.getBoolFlag('key', false).isEnabled() | | Statsig.getExperiment('key').get('param', default) | client.getFlag('key', default).value | | Statsig.logEvent('event', value, metadata) | client.track('event', { value, properties: metadata }) | | Statsig.updateUser(user) | client.identify(context) |