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/utils

v1.0.1

Published

Utility library for the [A vs B](https://app.avsb.cloud) SDK ecosystem.

Readme

@avsbhq/utils

Utility library for the A vs B SDK ecosystem.

Provides timing helpers, resilience primitives, structured logging, context builders, snapshot/bootstrap serialization, decision recording, OpenFeature integration, request-scoped middleware for eight frameworks, and unified storage adapters. Most utilities are accessed through client.utils.* on any AvsbClient or AvsbServer instance. Some primitives are also importable directly.


1. Install

npm install @avsbhq/utils

Install peer dependencies only for the features you use:

# For pino logger adapter
npm install pino

# For winston logger adapter
npm install winston

# For OpenFeature
npm install @openfeature/server-sdk   # or @openfeature/web-sdk for browser

# For Express middleware
npm install express

# For Fastify middleware
npm install fastify

Node.js 18 or later.


2. Quickstart

All utilities are available on the SDK client after construction:

import { AvsbClient } from '@avsbhq/browser'

const client = new AvsbClient({ sdkKey: '...', context: { kind: 'user', key: 'u_1' } })
await client.onReady()

// Use utilities through client.utils
const snapshot = client.utils.snapshot()
const debounced = client.utils.debounce(mySearch, 300)
const bootstrap = await client.utils.serializeBootstrap()

For server-side:

import { AvsbServer } from '@avsbhq/node'

const server = new AvsbServer({ sdkKey: process.env.AVSB_SDK_KEY! })
await server.onReady()

const recorder = server.utils.createDecisionRecorder({ sinks: [otelSink] })

3. SDK keys

@avsbhq/utils contains no networking code and does not use SDK keys directly. SDK keys are managed by @avsbhq/browser and @avsbhq/node.


4. Identity

Not applicable as a standalone utility. Context helpers are available in the context sub-module:

import {
  defineContextSchema,
  mergeContext,
  redactAttributes,
  anonymousId,
  userContext,
} from '@avsbhq/utils'

// Shorthand context factory
const ctx = userContext('u_123', { plan: 'pro' })
// { kind: 'user', key: 'u_123', plan: 'pro' }

// Merge attributes into an existing context
const updated = mergeContext(ctx, { country: 'US' })

// Generate an anonymous ID (UUID v4)
const anonId = anonymousId()

// Redact private attributes before logging
const safe = redactAttributes(ctx, ['email', 'phone'])

5. Multi-context

import { defineContextSchema } from '@avsbhq/utils'
import { z } from 'zod'

const Ctx = defineContextSchema({
  user: z.object({ id: z.string(), plan: z.enum(['free', 'pro', 'enterprise']) }),
  organization: z.object({ id: z.string(), tier: z.number() }),
})

const ctx = Ctx.builder()
  .setUser('u_123', { id: 'u_123', plan: 'pro' })
  .setOrganization('org_456', { id: 'org_456', tier: 3 })
  .build()

// Validates at build time against the declared schema
const result = Ctx.validate(ctx)
if (!result.success) { /* handle errors */ }

defineContextSchema integrates with avsb codegen — the CLI generates the schema file automatically from your platform's declared registeredAttributes.


6. Timing helpers

import { once, debounce, throttle, retry, sleep, withTimeout, withDeadline } from '@avsbhq/utils'

// Or via client:
const { once, debounce, throttle, retry, sleep, withTimeout, withDeadline } = client.utils

// Run a function exactly once (idempotent wrapper)
const initOnce = once(initialize)
initOnce()  // runs
initOnce()  // no-op

// Debounce user input
const handleSearch = debounce(search, 300)
handleSearch.cancel()  // cancel pending call

// Throttle scroll events
const handleScroll = throttle(onScroll, 16)

// Retry with backoff
const data = await retry(() => fetchData(), {
  maxAttempts: 3,
  baseDelayMs: 100,
  backoff: 'exponential',
})

// Add a timeout to any promise
const result = await withTimeout(fetchDatafile(), 5000)

// Deadline (absolute ms timestamp)
const result2 = await withDeadline(fetchDatafile(), Date.now() + 5000)

7. Resilience

import { FallbackChain, circuitBreaker } from '@avsbhq/utils'

// FallbackChain: try each layer in order, return the first that succeeds
const chain = new FallbackChain([
  { name: 'redis', fn: () => redis.get(key) },
  { name: 'memory', fn: () => memoryCache.get(key) },
  { name: 'default', fn: () => defaultValue },
])
const value = await chain.get()

// Circuit breaker: open after N failures, half-open after reset timeout
const safeFetch = circuitBreaker(fetchDatafile, {
  failureThreshold: 5,
  resetTimeoutMs: 30_000,
  onOpen: () => logger.warn('circuit open'),
})

8. Structured logging

import { createLogger, consoleLogger, noopLogger } from '@avsbhq/utils'
import { createPinoTransport } from '@avsbhq/utils/log/pino'
import { createWinstonTransport } from '@avsbhq/utils/log/winston'
import pino from 'pino'
import winston from 'winston'

// Console logger (default, zero deps)
const logger = consoleLogger('warn')

// Multi-transport pino logger
const pinoLogger = createLogger({
  level: 'info',
  prefix: 'avsb',
  transports: [
    createPinoTransport({ logger: pino(), level: 'info' }),
  ],
})

// Winston adapter
const winstonLogger = createLogger({
  level: 'info',
  transports: [
    createWinstonTransport({ logger: winston.createLogger(), level: 'warn' }),
  ],
})

// Child logger (inherits parent settings, scoped prefix)
const requestLogger = logger.child('request-handler')
requestLogger.info('flag evaluated', { flagKey: 'checkout-v2' })

// Pass to the SDK
const server = new AvsbServer({ sdkKey, logger: pinoLogger })

9. Context builder and visitor data

import { AvsbClient } from '@avsbhq/browser'

const client = new AvsbClient({ sdkKey, context: { kind: 'user', key: 'u_1' } })

// Pre-built context factories for common web signals
const browserCtx = client.utils.context.Browser()
// { kind: 'device', key: fingerprint, os: 'macos', browser: 'chrome', viewport: '...', ... }

const geoCtx = client.utils.context.Geolocation({ country: 'US', region: 'CA' })
// { kind: 'geo', key: 'US:CA', country: 'US', region: 'CA' }

const pageCtx = client.utils.context.PageView({ url: window.location.href })
// { kind: 'pageview', key: pathname, path: '/checkout', referrer: '...' }

10. Snapshots and bootstrap

// Synchronous snapshot of all current flag values
const snap = client.utils.snapshot()
// { capturedAt: 1717000000000, datafileVersion: '...', flags: { 'key': Flag<T> }, contextSummary: [...] }

// Force-refresh datafile then snapshot
const fresh = await client.utils.snapshotFresh()

// SSR bootstrap: serialize for handoff to the client
const blob = await client.utils.serializeBootstrap(ctx)
// { format: 'avsb-bootstrap-v1', snapshot: {...}, datafile: {...}, producedAt: ... }

// Client: consume the bootstrap blob (skips initial network fetch)
client.utils.hydrateBootstrap(blob)

// Load a datafile from a URL, KV store, or filesystem
const df = await client.utils.bootstrapFromUrl('https://cdn.avsb.cloud/datafile/prod.json')
const df2 = await client.utils.bootstrapFromKV(kvNamespace, 'my-datafile')
const df3 = await client.utils.bootstrapFromFile('/etc/avsb/datafile.json')

11. Change streams

// Async iterator over flagChange events
for await (const change of client.utils.changes()) {
  myLogger.info('flag changed', {
    flagKey: change.flagKey,
    from: change.previousValue,
    to: change.newValue,
  })
}

// Typed per-flag callback
const unsub = client.utils.onFlagChange('checkout-v2', (flag) => {
  analytics.track('flag_updated', { value: flag.value })
})
unsub()  // stop listening

// Wait for a flag to reach a specific value (with optional timeout)
const flag = await client.utils.waitForFlag('feature-gate', (f) => f.isEnabled(), { timeout: 10_000 })

12. Decision recorder and warehouse sinks

Record every flag evaluation and stream it to one or more sinks for analytics, compliance, or debugging:

import { createOtelSink } from '@avsbhq/utils'
import { createSnowflakeSink } from '@avsbhq/utils'
import { createClickHouseSink } from '@avsbhq/utils'

const recorder = server.utils.createDecisionRecorder({
  sample: 0.1,                         // record 10% of decisions
  privateAttributes: ['email', 'ip'],  // redact before forwarding
  bufferSize: 1000,
  flushInterval: 5000,
  sinks: [
    createOtelSink({ exporter: myOtelExporter }),
    createSnowflakeSink({ connection: snowflakeConn, table: 'avsb_decisions' }),
  ],
})

// Attach to the server's evaluation pipeline
server.on('evaluation', ({ flagKey, flag, contextKeys }) => {
  recorder.record({ flagKey, value: flag.value, ...flag })
})

// Flush and close on shutdown
await recorder.close()

Available sinks:

  • createOtelSink — OpenTelemetry exporter
  • createSentrySink — Sentry client
  • createDatadogSink — Datadog HTTP API
  • createSnowflakeSink — Snowflake Node connector
  • createBigQuerySink@google-cloud/bigquery
  • createRedshiftSink — Redshift Data API (aws-sdk-js-v3)
  • createClickHouseSink@clickhouse/client

13. Request-scoped middleware

Eight framework adapters. All open an AsyncLocalStorage scope so getRequestClient() works anywhere in the call chain without passing the client explicitly.

Express

import { expressMiddleware } from '@avsbhq/utils/middleware/express'
import { getRequestClient } from '@avsbhq/utils'

app.use(expressMiddleware(server, {
  contextFrom: (req) => ({
    kind: 'user' as const,
    key: req.user?.id ?? 'anon',
  }),
}))

// In any nested function — no need to pass client
function myService() {
  const client = getRequestClient()
  return client.getBoolFlag('my-flag', false)
}

Other adapters: fastifyPlugin, honoMiddleware, koaMiddleware, nextAppMiddleware, nextPagesMiddleware, lambdaMiddleware, workersMiddleware.


14. OpenFeature provider

import { AvsbProvider } from '@avsbhq/utils/openfeature'
import { OpenFeature } from '@openfeature/server-sdk'
import { AvsbServer } from '@avsbhq/node'

const server = new AvsbServer({ sdkKey: process.env.AVSB_SDK_KEY! })
await server.onReady()

OpenFeature.setProvider(new AvsbProvider(server))

const client = OpenFeature.getClient()
const enabled = await client.getBooleanValue('checkout-v2', false, { targetingKey: 'u_123' })

Browser variant (@openfeature/web-sdk):

import { AvsbWebProvider } from '@avsbhq/utils/openfeature'
import { OpenFeature } from '@openfeature/web-sdk'
import { AvsbClient } from '@avsbhq/browser'

const avsbClient = new AvsbClient({ sdkKey: '...', context: { kind: 'user', key: 'u_1' } })
OpenFeature.setProvider(new AvsbWebProvider(avsbClient))

15. Storage adapters

Unified storage interface covering both sticky bucketing and datafile caching:

import {
  createMemoryAdapter,
  createRedisAdapter,
  createLocalStorageAdapter,
  createIndexedDBAdapter,
  createCookieAdapter,
  createDynamoDBAdapter,
  createPostgresAdapter,
  createDurableObjectAdapter,
} from '@avsbhq/utils'

// Redis (recommended for server-side, multi-process)
const redis = createClient({ url: process.env.REDIS_URL })
const adapter = createRedisAdapter({ client: redis, prefix: 'avsb:', datafileTtlMs: 60_000 })

// DynamoDB
const adapter2 = createDynamoDBAdapter({ client: dynamoClient, tableName: 'avsb-storage' })

// Cloudflare Durable Objects
const adapter3 = createDurableObjectAdapter({ stub: env.AVSB_DO })

Big segment stores (separate from sticky bucketing):

import { createRedisBigSegmentStore, createDynamoDBBigSegmentStore } from '@avsbhq/utils'

const bigSegments = createRedisBigSegmentStore({
  client: redis,
  prefix: 'avsb:segments:',
  refreshInterval: 30_000,
})

16. Track helpers

import { Transaction, itemOf } from '@avsbhq/utils'

// Batch multiple events
client.utils.trackBatch([
  { eventKey: 'page_viewed', payload: { value: 1 } },
  { eventKey: 'product_clicked', payload: { properties: { sku: 'A' } } },
])

// Deduplicated tracking (event fires at most once per dedupKey per session)
client.utils.trackOnce('onboarding_complete', 'user-onboarding')

// Transaction helper (tracks items + total automatically)
const tx = new Transaction('purchase')
tx.add(itemOf('PRO_ANNUAL', 199.0, { currency: 'USD' }))
tx.add(itemOf('SUPPORT_ADDON', 49.0))
tx.track(client)  // fires 'purchase' with value=248.0

17. Cleanup registry

import { createRegistry } from '@avsbhq/utils'

const registry = createRegistry()

const timer = setInterval(pollMetrics, 5000)
registry.add(() => clearInterval(timer))

const sub = client.on('flagChange', handleChange)
registry.add(sub)  // sub() is the unsubscribe function

// On shutdown:
registry.drain()  // calls all registered teardown functions in reverse order

18. Migration

From LaunchDarkly client utilities

| LaunchDarkly | @avsbhq/utils | |---|---| | LDClient.on('change', cb) | client.utils.onFlagChange(key, cb) | | LDClient.allFlagsState(ctx) | client.utils.snapshot() | | Custom OpenFeature provider | AvsbProvider from @avsbhq/utils/openfeature | | LDRedisFeatureStore | createRedisAdapter(...) (storage adapter) |

From Statsig diagnostics / utils

| Statsig | @avsbhq/utils | |---|---| | StatsigLocalOverrides | createMockClient from @avsbhq/test | | EvaluationsDataAdapter | UnifiedStorageAdapter from @avsbhq/utils | | StatsigUser extension | defineContextSchema(...) with Zod schema | | Statsig.flush() | client.flush() / recorder.flush() |