@avsbhq/vue
v1.0.0
Published
Vue 3 SDK for the [A vs B](https://app.avsb.cloud) platform.
Readme
@avsbhq/vue
Vue 3 SDK for the A vs B platform.
Drop an <AvsbProvider> at the top of your component tree (or call app.use(AvsbPlugin)) and read feature flags from anywhere using composables. Built on @avsbhq/browser; components re-render automatically when flag values change.
1. Install
npm install @avsbhq/vueVue 3.4 or later is required as a peer dependency. Pinia integration is optional — install pinia only if you use useAvsbStore.
2. Quickstart
<!-- App.vue -->
<script setup lang="ts">
import { AvsbProvider } from '@avsbhq/vue'
</script>
<template>
<AvsbProvider :sdk-key="sdkKey" :context="{ kind: 'user', key: userId }">
<RouterView />
</AvsbProvider>
</template><!-- CheckoutButton.vue -->
<script setup lang="ts">
import { useFlag, useTrack } from '@avsbhq/vue'
const newCheckout = useFlag('new-checkout-flow', false)
const track = useTrack()
</script>
<template>
<button @click="track('checkout_clicked')">
{{ newCheckout.value ? 'Start checkout (new)' : 'Buy now' }}
</button>
</template>3. SDK keys
Get a client SDK key from your A vs B project's Environments page.
Client SDK keys are safe to embed in browser bundles — store them in your framework's public env variable:
VITE_AVSB_SDK_KEY=sdk-...// main.ts
const key = import.meta.env.VITE_AVSB_SDK_KEYServer SDK keys (used by @avsbhq/node) must never be exposed to the browser.
4. Identity — identify, updateAttributes, alias, reset
Provider-level context
Pass a context object to <AvsbProvider> at bootstrap. This is the evaluation context used for all flag reads until you call identify.
<AvsbProvider :sdk-key="key" :context="{ kind: 'user', key: userId, plan: 'pro' }">useIdentify
Replace the current context at runtime (e.g. after sign-in):
import { useIdentify } from '@avsbhq/vue'
const identify = useIdentify()
function onSignIn(user: User) {
identify({ kind: 'user', key: user.id, email: user.email, plan: user.plan })
}The function is stable — it will not change reference between renders.
useAlias
Record that an anonymous visitor and an identified user are the same person. Call this once at sign-in:
import { useAlias } from '@avsbhq/vue'
const alias = useAlias()
async function onSignIn(anonKey: string, userId: string) {
await alias(
{ kind: 'user', key: anonKey },
{ kind: 'user', key: userId }
)
}reset
Call client.reset() via useAvsbClient() to clear the current identity and return to the anonymous state.
5. Multi-context
A vs B supports multi-context evaluation — evaluating a flag for both a user and an organisation simultaneously:
identify({
kind: 'multi',
user: { kind: 'user', key: 'u_123', plan: 'pro' },
organization: { kind: 'organization', key: 'org_456', tier: 'enterprise' },
})6. Reading flags
useFlag — full Flag object
import { useFlag } from '@avsbhq/vue'
const checkoutFlag = useFlag('new-checkout-flow', false)
// checkoutFlag is ComputedRef<Flag<boolean>>
// In template:
// checkoutFlag.value.value → the boolean value
// checkoutFlag.value.isEnabled() → true if rule-served AND truthy
// checkoutFlag.value.variationKey → 'on' | 'off' | ...
// checkoutFlag.value.source → 'rule' | 'override' | 'not_found' | ...A default value is always required. It is returned until the SDK is ready and whenever the flag is not found.
useFlagValue — raw value shortcut
import { useFlagValue } from '@avsbhq/vue'
const showBanner = useFlagValue('promo-banner', false)
// showBanner is ComputedRef<boolean>Typed variants
Use typed composables when you want a runtime type-safety check against the datafile's declared flag type:
import { useBoolFlag, useStringFlag, useNumberFlag, useJsonFlag } from '@avsbhq/vue'
const darkMode = useBoolFlag('dark-mode', false)
const theme = useStringFlag('theme', 'light')
const maxItems = useNumberFlag('max-items', 10)
const config = useJsonFlag<{ timeout: number }>('api-config', { timeout: 5000 })All return ComputedRef<Flag<T>>.
Reactive flag key
useFlag (and useFlagValue) accept a Ref<string> or ComputedRef<string> as the key — the subscription updates automatically when the key changes:
const selectedFlag = ref('feature-a')
const flag = useFlag(selectedFlag, false)useAllFlags — all evaluated flags
import { useAllFlags } from '@avsbhq/vue'
const flags = useAllFlags()
// flags is ComputedRef<Record<string, Flag>>
// Re-computes on any flagChange event.Prefer individual useFlag composables in most components — useAllFlags triggers on any flag change and is best suited for debug panels.
7. Tracking events
import { useTrack } from '@avsbhq/vue'
const track = useTrack()
function onClick() {
track('button_clicked', { location: 'header', variant: 'A' })
}The function is defined once in setup — it is safe to use as an event handler without wrapping in a closure.
8. Error handling
Provider-level error state
<AvsbProvider> exposes a status that transitions to 'error' if onReady() rejects or if the client emits an 'error' event. Use useFlagReady to gate rendering:
<script setup lang="ts">
import { useFlagReady } from '@avsbhq/vue'
const ready = useFlagReady()
</script>
<template>
<div v-if="!ready">Loading flags...</div>
<MyApp v-else />
</template>Custom logger
Pass a logger to suppress or redirect SDK output:
app.use(AvsbPlugin, {
sdkKey: '...',
logger: {
debug: (msg, ...args) => myLogger.debug(msg, ...args),
warn: (msg, ...args) => myLogger.warn(msg, ...args),
error: (msg, ...args) => myLogger.error(msg, ...args),
},
})9. SSR / Nuxt
For SSR, pre-fetch the datafile server-side and pass it as the bootstrap option to skip the initial network round-trip on the client:
// server.ts
import { AvsbClient } from '@avsbhq/browser'
const client = new AvsbClient({ sdkKey: '...', bootstrap: serverDatafile })<!-- App.vue — client side -->
<AvsbProvider :sdk-key="sdkKey" :bootstrap="bootstrapDatafile">
<NuxtPage />
</AvsbProvider>Flag composables return a not_found sentinel during the hydration window so SSR output matches the client's initial render.
10. Graceful shutdown
When using <AvsbProvider> in Mode A (sdkKey prop), the client is closed automatically on component unmount. This flushes any queued events.
When using Mode B (passing a pre-built client prop), you own the lifecycle:
onUnmounted(async () => {
await client.flush()
await client.close()
})The AvsbPlugin (Mode A) proxies app.unmount to close the client when the Vue app tears down.
11. Testing
Inject a mock context directly via provide — no need to mount the full provider:
import { defineComponent, h, provide } from 'vue'
import { mount } from '@vue/test-utils'
import { vi } from 'vitest'
import { AvsbInjectionKey } from '@avsbhq/vue'
import { notFoundFlag, createFlag } from '@avsbhq/core'
function makeMockClient() {
return {
subscribe: vi.fn((_key, cb) => { /* store cb */ return () => {} }),
getSnapshot: vi.fn((key, def) => notFoundFlag(def, 'no data')),
on: vi.fn(() => () => {}),
onReady: vi.fn(() => Promise.resolve()),
close: vi.fn(() => Promise.resolve()),
track: vi.fn(),
identify: vi.fn(),
alias: vi.fn(() => Promise.resolve()),
reset: vi.fn(),
}
}
const Wrapper = defineComponent({
setup() {
provide(AvsbInjectionKey, {
client: makeMockClient(),
status: 'ready',
})
},
render() {
return h(MyComponent)
},
})
const wrapper = mount(Wrapper)Use createFlag() from @avsbhq/core to build typed flag fixtures:
import { createFlag } from '@avsbhq/core'
const FLAG_ON = createFlag<boolean>({
value: true,
variationKey: 'on',
source: 'rule',
ruleId: 'r1',
ruleType: 'ab_test',
reasons: ['matched rule'],
})12. Migration from LaunchDarkly
| LaunchDarkly (React SDK) | @avsbhq/vue equivalent |
|---------------------------------------|----------------------------------------------------|
| <LDProvider clientSideID="..."> | <AvsbProvider sdkKey="..."> |
| useLDClient() | useAvsbClient() |
| useFlags() | useAllFlags() |
| useLDFlag('key', default) | useFlag('key', default).value |
| ldClient.identify(context) | useIdentify()(context) |
| ldClient.track('event') | useTrack()('event') |
| ldClient.variation('key', default) | useAvsbClient()?.getFlag('key', default) |
Key differences:
useFlagreturns aFlag<T>object, not a rawT. Access.valuefor the primitive or.isEnabled()for a boolean gate. This gives you the evaluation metadata (source, variationKey, reasons) without a second call.- All
getFlagvariants require an explicitdefaultValue. There is no untyped.variation(). - Multi-context is a first-class concept — no adapter required.
