{"_id":"@avsbhq/vue","_rev":"5-c84dc02b78ec17e35805a1af27b2c236","name":"@avsbhq/vue","dist-tags":{"latest":"1.1.1"},"versions":{"1.0.0":{"name":"@avsbhq/vue","version":"1.0.0","_id":"@avsbhq/vue@1.0.0","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/main1479/a-vs-b/tree/main/packages/avsb-vue#readme","bugs":{"url":"https://github.com/main1479/a-vs-b/issues"},"dist":{"shasum":"a61fe9c6610acb72f3e1edf469e73580daa13218","tarball":"https://registry.npmjs.org/@avsbhq/vue/-/vue-1.0.0.tgz","fileCount":6,"integrity":"sha512-zPsvNF9lvUsTfl5fmnNClJQqcfs1GRa4J9Xzvc42AmSgOgagpZHWK+7YV+5VRxmYwPlEfNeCiyYLbVgIQUIVbA==","signatures":[{"sig":"MEYCIQDhSSMm3/hrQ91sA11GTlNDjJE72EGnC51gkbHEb3c9TAIhAPYCzGDCfGwrIFhXQlRg0aKeJj56dyNL4VCKoZN7ivWT","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":64095},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"8cd6226e120fe67c198b2f1db0087cd38b4a4060","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"repository":{"url":"git+https://github.com/main1479/a-vs-b.git","type":"git","directory":"packages/avsb-vue"},"_npmVersion":"10.9.2","description":"Vue 3 SDK for the [A vs B](https://app.avsb.cloud) platform.","directories":{},"_nodeVersion":"22.14.0","dependencies":{"@avsbhq/core":"1.0.0","@avsbhq/browser":"1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.4.0","tsup":"^8.0.0","pinia":"^2.1.0","vitest":"^1.6.0","happy-dom":"^14.0.0","typescript":"^5.5.0","@vue/test-utils":"^2.4.0","@vitejs/plugin-vue":"^5.0.0"},"peerDependencies":{"vue":">=3.4.0"},"peerDependenciesMeta":{"pinia":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/vue_1.0.0_1780034365231_0.7896336497970258","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@avsbhq/vue","version":"1.0.1","_id":"@avsbhq/vue@1.0.1","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/main1479/a-vs-b/tree/main/packages/avsb-vue#readme","bugs":{"url":"https://github.com/main1479/a-vs-b/issues"},"dist":{"shasum":"44f075c56bc6cb4ac57b25f47d769da8f511ad9f","tarball":"https://registry.npmjs.org/@avsbhq/vue/-/vue-1.0.1.tgz","fileCount":6,"integrity":"sha512-OKbbHu2UohdhQzLSTAxgPt5gfG4UcfwDo8zSDatlVF7gUXYBk0PlQdSENnSwUmsCeLiaSdnh275l+dYCgU997g==","signatures":[{"sig":"MEUCIAJjIMj7dvHNlbvuIdLMwgOucxMEHj8npaapynbzheg1AiEAlgV8r++tdXjLQD+CtbErgPNPWjRKA/lJR8/MDrJcWmc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63991},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"repository":{"url":"git+https://github.com/main1479/a-vs-b.git","type":"git","directory":"packages/avsb-vue"},"_npmVersion":"10.9.8","description":"Vue 3 SDK for the [A vs B](https://app.avsb.cloud) platform.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@avsbhq/core":"1.4.0","@avsbhq/browser":"1.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.4.0","tsup":"^8.0.0","pinia":"^2.1.0","vitest":"^1.6.0","happy-dom":"^14.0.0","typescript":"^5.5.0","@vue/test-utils":"^2.4.0","@vitejs/plugin-vue":"^5.0.0"},"peerDependencies":{"vue":">=3.4.0"},"peerDependenciesMeta":{"pinia":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/vue_1.0.1_1785346473010_0.9656643587731195","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@avsbhq/vue","version":"1.0.2","_id":"@avsbhq/vue@1.0.2","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/main1479/a-vs-b/tree/main/packages/avsb-vue#readme","bugs":{"url":"https://github.com/main1479/a-vs-b/issues"},"dist":{"shasum":"a6e7b1108cb04165234f83c7a15c4e45ac331576","tarball":"https://registry.npmjs.org/@avsbhq/vue/-/vue-1.0.2.tgz","fileCount":6,"integrity":"sha512-F0yrdDtH8JTx5Ri6WJgZnGUjpaJcdFumhYAjb5ZjE71PvUXa+wklLDfsAGr5Eclzh5iu8rAgE1YCBDfChAK+Vg==","signatures":[{"sig":"MEUCIHT/td3PcYvirHPsMEbJhlbytYh9KtfmKCQuQp1y54WgAiEAnUSzVF0GQ/COY7oRXFu9jGqUbyxOyyXK3B80z85F4AA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63991},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"repository":{"url":"git+https://github.com/main1479/a-vs-b.git","type":"git","directory":"packages/avsb-vue"},"_npmVersion":"10.9.8","description":"Vue 3 SDK for the [A vs B](https://app.avsb.cloud) platform.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@avsbhq/core":"1.4.0","@avsbhq/browser":"1.3.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.4.0","tsup":"^8.0.0","pinia":"^2.1.0","vitest":"^1.6.0","happy-dom":"^14.0.0","typescript":"^5.5.0","@vue/test-utils":"^2.4.0","@vitejs/plugin-vue":"^5.0.0"},"peerDependencies":{"vue":">=3.4.0"},"peerDependenciesMeta":{"pinia":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/vue_1.0.2_1785347993200_0.5746387267541881","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@avsbhq/vue","version":"1.1.0","keywords":["avsb","feature-flags","ab-testing","experiments","vue","composables","pinia","sdk"],"license":"MIT","_id":"@avsbhq/vue@1.1.0","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-vue#readme","bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"dist":{"shasum":"b2044fb569d83de1b291ac8bf7909e9b8123c6c7","tarball":"https://registry.npmjs.org/@avsbhq/vue/-/vue-1.1.0.tgz","fileCount":21,"integrity":"sha512-JUUJ4/MMpG/bKUpEn9kCNKG9Ku1C1x0O5lf78/OZlcmTtCVPjInOk36iC2LprvEjpDRxxgZdeH2k+lP4L+rt6w==","signatures":[{"sig":"MEQCICoeVB6WvWTXC8guspTaNrGy2EdltPX4GnbJalozfh/YAiB2ULxX/TG9YQ6yuP0n1NUlbTEVrjXk25sk6TSZQ3AxtA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":110218},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./pinia":{"import":{"types":"./dist/pinia.d.ts","default":"./dist/pinia.js"},"require":{"types":"./dist/pinia.d.cts","default":"./dist/pinia.cjs"}},"./testing":{"import":{"types":"./dist/testing.d.ts","default":"./dist/testing.js"},"require":{"types":"./dist/testing.d.cts","default":"./dist/testing.cjs"}},"./package.json":"./package.json"},"gitHead":"5eed46d5944b522ba0aa34e82850c98bb91667fe","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"repository":{"url":"git+https://github.com/avsbhq/a-vs-b.git","type":"git","directory":"packages/avsb-vue"},"_npmVersion":"10.9.8","description":"Vue SDK for A vs B feature flags and experiments: composables, a provider component, an optional Pinia store, and test helpers.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"@avsbhq/core":"^1.4.0","@avsbhq/browser":"^1.3.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.4.0","tsup":"^8.0.0","pinia":"^2.1.0","vitest":"^4.1.4","happy-dom":"^14.0.0","typescript":"^5.5.0","@avsbhq/test":"^1.0.1","@vue/test-utils":"^2.4.0","@vitejs/plugin-vue":"^5.0.0"},"peerDependencies":{"vue":">=3.4.0","pinia":">=2.1.0"},"peerDependenciesMeta":{"pinia":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/vue_1.1.0_1785971481768_0.5399509871474029","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@avsbhq/vue","version":"1.1.1","description":"Vue SDK for A vs B feature flags and experiments: composables, a provider component, an optional Pinia store, and test helpers.","keywords":["avsb","feature-flags","ab-testing","experiments","vue","composables","pinia","sdk"],"license":"MIT","type":"module","sideEffects":false,"main":"dist/index.cjs","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./pinia":{"import":{"types":"./dist/pinia.d.ts","default":"./dist/pinia.js"},"require":{"types":"./dist/pinia.d.cts","default":"./dist/pinia.cjs"}},"./testing":{"import":{"types":"./dist/testing.d.ts","default":"./dist/testing.js"},"require":{"types":"./dist/testing.d.cts","default":"./dist/testing.cjs"}},"./package.json":"./package.json"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/avsbhq/a-vs-b.git","directory":"packages/avsb-vue"},"homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-vue#readme","bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","typecheck":"tsc --noEmit"},"peerDependencies":{"pinia":">=2.1.0","vue":">=3.4.0"},"peerDependenciesMeta":{"pinia":{"optional":true}},"dependencies":{"@avsbhq/browser":"^1.3.1","@avsbhq/core":"^1.4.0"},"devDependencies":{"@avsbhq/test":"^1.0.1","@vitejs/plugin-vue":"^5.0.0","@vue/test-utils":"^2.4.0","happy-dom":"^14.0.0","pinia":"^2.1.0","tsup":"^8.0.0","typescript":"^5.5.0","vitest":"^4.1.4","vue":"^3.4.0"},"_id":"@avsbhq/vue@1.1.1","gitHead":"d31d88fbf202f40041b4cb47f77882f454fa5ef8","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-TCjy73YIP4Disd6xGYjFU6r02AzpH0uW9Wwsxs5JQrhV33nTRNqhx154WOl4ZpPUr/nuIhTIM9ud26rJIR/sZw==","shasum":"aaf3e547c486b2cdc6fd442df5b9bdabfd7ac664","tarball":"https://registry.npmjs.org/@avsbhq/vue/-/vue-1.1.1.tgz","fileCount":21,"unpackedSize":110266,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD5ijVg4uFTt3AHYRX/dtj1QqNtv+mwZraUhb/wlhwjYgIgNybW440O/yUWW2KcfX5CF92YY4BV4x9YvWPVzzRtlu8="}]},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"directories":{},"maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vue_1.1.1_1786795862317_0.25217274618062824"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-29T05:59:25.029Z","modified":"2026-08-15T12:11:02.604Z","1.0.0":"2026-05-29T05:59:25.375Z","1.0.1":"2026-07-29T17:34:33.174Z","1.0.2":"2026-07-29T17:59:53.368Z","1.1.0":"2026-08-05T23:11:21.911Z","1.1.1":"2026-08-15T12:11:02.464Z"},"bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"license":"MIT","homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-vue#readme","keywords":["avsb","feature-flags","ab-testing","experiments","vue","composables","pinia","sdk"],"repository":{"type":"git","url":"git+https://github.com/avsbhq/a-vs-b.git","directory":"packages/avsb-vue"},"description":"Vue SDK for A vs B feature flags and experiments: composables, a provider component, an optional Pinia store, and test helpers.","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"readme":"# @avsbhq/vue\n\nVue 3 SDK for the [A vs B](https://app.avsb.cloud) platform.\n\nMount one `<AvsbProvider>` (or install `AvsbPlugin`), then read feature flags anywhere with composables. Built on [`@avsbhq/browser`](https://www.npmjs.com/package/@avsbhq/browser): components update when a flag's value changes, and only the components reading that flag.\n\n---\n\n## 1. Install\n\n```bash\nnpm install @avsbhq/vue\n```\n\nVue 3.4 or later is required as a peer dependency. `pinia` is an optional peer: install it only if you import `@avsbhq/vue/pinia`.\n\n---\n\n## 2. Quickstart\n\n```vue\n<!-- App.vue -->\n<script setup lang=\"ts\">\nimport { AvsbProvider } from '@avsbhq/vue';\n\nconst sdkKey: string = import.meta.env['VITE_AVSB_SDK_KEY'] as string;\nconst userId = 'u_123';\n</script>\n\n<template>\n  <AvsbProvider :sdk-key=\"sdkKey\" :context=\"{ kind: 'user', key: userId, plan: 'pro' }\">\n    <RouterView />\n  </AvsbProvider>\n</template>\n```\n\n```vue\n<!-- CheckoutButton.vue -->\n<script setup lang=\"ts\">\nimport { useBoolFlag, useExposure, useTrack } from '@avsbhq/vue';\nimport type { ComputedRef } from 'vue';\nimport type { Flag } from '@avsbhq/vue';\n\n// Keep the ref: reading `.value` here instead would freeze the first value.\nconst newCheckout: ComputedRef<Flag<boolean>> = useBoolFlag('new-checkout-flow', false);\nconst track = useTrack();\n\n// The visitor is about to see this decision, so record it once.\nuseExposure('new-checkout-flow');\n</script>\n\n<template>\n  <button @click=\"track('checkout_started', { value: 99 })\">\n    {{ newCheckout.value ? 'Start checkout (new)' : 'Buy now' }}\n  </button>\n</template>\n```\n\nBoth props matter: without `context` the visitor gets a persisted anonymous id, which is fine for anonymous experiments but cannot target logged-in attributes.\n\n---\n\n## 3. SDK keys\n\nGet the SDK key for the environment you want from your project: **app.avsb.cloud**, then **Settings**, then **Environments**. The format is `sdk_<environment>_<id>`, for example `sdk_production_ttqm0eaj4vth1krcb2xn`.\n\nThere is one SDK key per environment, and \"SDK key\" is its only name. There is no separate client key and server key to choose between.\n\nYour SDK key is a public identifier, not a secret: it is safe to ship in browser and mobile bundles, it can only fetch that environment's flag configuration and send events, and it can never read or change anything in your dashboard.\n\n```\nVITE_AVSB_SDK_KEY=sdk_production_...\n```\n\nThe client checks the shape of the key when it is constructed. A key that does not look like an SDK key (a pasted dashboard URL, a truncated copy, a personal access token or a service token) logs one actionable error naming what it got and where the real key lives.\n\n---\n\n## 4. The whole type surface\n\nEvery export, with its real signature. The signatures name the package's own types, and Vue's. Both come from here:\n\n```ts\nimport type {\n  AvsbClientOptions,\n  AvsbContextValue,\n  AvsbPluginOptions,\n  AvsbProviderProps,\n  EvalContext,\n  Flag,\n  FlagDatafile,\n  FlagKeyInput,\n  TrackPayload,\n  UseAvsbStatusResult,\n  VueAvsbClient,\n} from '@avsbhq/vue';\nimport type { ComputedRef, DefineComponent, InjectionKey, Plugin } from 'vue';\n```\n\n```ts\n// Provider and plugin\nconst AvsbProvider: DefineComponent<AvsbProviderProps>;\nconst AvsbPlugin: Plugin<AvsbPluginOptions>;\n\n// Reading flags. `FlagKeyInput = string | Ref<string> | ComputedRef<string>`\nfunction useFlag<T>(flagKey: FlagKeyInput, defaultValue: T): ComputedRef<Flag<T>>;\nfunction useFlagValue<T>(flagKey: FlagKeyInput, defaultValue: T): ComputedRef<T>;\nfunction useBoolFlag(flagKey: FlagKeyInput, defaultValue: boolean): ComputedRef<Flag<boolean>>;\nfunction useStringFlag(flagKey: FlagKeyInput, defaultValue: string): ComputedRef<Flag<string>>;\nfunction useNumberFlag(flagKey: FlagKeyInput, defaultValue: number): ComputedRef<Flag<number>>;\nfunction useJsonFlag<T>(flagKey: FlagKeyInput, defaultValue: T): ComputedRef<Flag<T>>;\nfunction useAllFlags(): ComputedRef<Record<string, Flag>>;\n\n// Readiness and errors\nfunction useFlagReady(): ComputedRef<boolean>;\nfunction useAvsbStatus(): UseAvsbStatusResult;\n\n// Identity\nfunction useIdentify(): (context: EvalContext) => void;\nfunction useAlias(): (previousContext: EvalContext, newContext: EvalContext) => void;\nfunction useReset(): () => void;\n\n// Events\nfunction useTrack(): (eventKey: string, payload?: TrackPayload) => void;\nfunction useExposure(flagKey: FlagKeyInput): void;\nfunction useFlagSubscription(flagKey: FlagKeyInput, callback: (flag: Flag) => void): void;\n\n// The client itself\nfunction useAvsbClient(): VueAvsbClient;\n\n// Context, for custom providers\nconst AvsbInjectionKey: InjectionKey<AvsbContextValue>;\n```\n\n### Provider props\n\n```ts\ntype AvsbProviderProps = AvsbProviderModeAProps | AvsbProviderModeBProps;\n\ninterface AvsbProviderModeAProps {\n  /** SDK key for the environment you are targeting. */\n  sdkKey: string;\n  /** Evaluation context. Omit for an anonymous, persisted visitor id. */\n  context?: EvalContext;\n  /** A datafile fetched on the server, so the first paint has real values. */\n  bootstrap?: FlagDatafile;\n  /** Everything else AvsbClient accepts (polling, logging, caching). */\n  options?: Omit<AvsbClientOptions, 'sdkKey' | 'context' | 'bootstrap'>;\n  client?: never;\n}\n\ninterface AvsbProviderModeBProps {\n  /** A client you built and own. The provider will not close it. */\n  client: VueAvsbClient;\n  sdkKey?: never;\n}\n```\n\nIn templates the props are kebab-case and bound: `sdk-key`, `:context`, `:bootstrap`, `:options`, `:client`.\n\n**Mode A** (an `sdk-key`) means the provider builds the client and closes it when it unmounts. **Mode B** (a `client`) means you own its lifetime and the provider never closes it. Passing neither throws at setup with a message naming both fixes, rather than rendering a tree where every flag is silently the default.\n\n### `Flag<T>`, the return type of every read\n\n```ts\ninterface Flag<T = unknown> {\n  /** The variation value, typed against the default you passed. */\n  readonly value: T;\n  /** Variation key, null when the default was served or the flag is unknown. */\n  readonly variationKey: string | null;\n  /** Why this value was produced. */\n  readonly source: EvaluationSource;\n  /** Rule that matched, null when none did. */\n  readonly ruleId: string | null;\n  readonly ruleType: RuleType | null;\n  /** Structured reasons for this decision. Never null; may be empty. */\n  readonly reasons: string[];\n  /** ms-epoch when this was evaluated. */\n  readonly evaluatedAt: number;\n  /** Microseconds spent in the evaluator. */\n  readonly durationMicros: number;\n  /** A real decision produced a truthy value. */\n  isEnabled(): boolean;\n  /** False for 'not_found' and for 'not_ready'. */\n  exists(): boolean;\n}\n\ntype RuleType = 'targeted_delivery' | 'ab_test' | 'holdout' | 'bandit';\n\ntype EvaluationSource =\n  | 'datafileOverride'\n  | 'runtimeOverride'\n  | 'sticky'\n  | 'rule'\n  | 'holdout'\n  | 'bandit'\n  | 'default'\n  | 'not_found'\n  | 'disabled'\n  | 'not_ready';\n```\n\n| `source`           | Meaning                                                       | `isEnabled()`   | `exists()` |\n| ------------------ | ------------------------------------------------------------- | --------------- | ---------- |\n| `datafileOverride` | A per-user override in the dashboard matched.                 | value-dependent | true       |\n| `runtimeOverride`  | `setOverrideForUser` or `setGlobalOverride` matched.          | value-dependent | true       |\n| `sticky`           | A previously stored assignment was reused.                    | value-dependent | true       |\n| `rule`             | A targeting rule or A/B rule matched.                         | value-dependent | true       |\n| `holdout`          | The visitor is held out, so the holdout variation was served. | value-dependent | true       |\n| `bandit`           | A bandit rule picked the variation.                           | value-dependent | true       |\n| `default`          | The flag exists, nothing matched, default variation served.   | false           | true       |\n| `disabled`         | The flag exists but is off in this environment.               | false           | true       |\n| `not_found`        | The datafile loaded and has no such key. Check the name.      | false           | false      |\n| `not_ready`        | No datafile yet. Gate on `useFlagReady()`.                    | false           | false      |\n\n\"value-dependent\" means `isEnabled()` is `Boolean(flag.value)`.\n\n### `UseAvsbStatusResult`\n\n```ts\ninterface UseAvsbStatusResult {\n  status: ComputedRef<'loading' | 'ready' | 'error'>;\n  error: ComputedRef<Error | undefined>;\n  degraded: ComputedRef<boolean>;\n}\n```\n\n### Typed flag keys\n\n`FlagKeyInput` is built from `string` until you generate your keys. Generate\nthem and it is built from the union of this project's real flag keys, so a typo\nis a compile error and your editor completes the list:\n\n```bash\nnpx avsb codegen --output src/generated/flags.ts\n```\n\nThe generated file declares your flags twice on purpose: an importable\n`FlagValues` interface for payload types, and a `declare global` block that\nteaches every composable in this package which keys exist. Its important parts:\n\n```text\n// AUTO-GENERATED by @avsbhq/cli codegen. Do not edit by hand.\n\nexport interface FlagValues {\n  'checkout-v2': boolean\n  'hero-copy': 'control' | 'variant-a'\n  'theme': { primary: string }\n}\n\ndeclare global {\n  interface AvsbFlags {\n    'checkout-v2': boolean\n    'hero-copy': 'control' | 'variant-a'\n    'theme': { primary: string }\n  }\n}\n```\n\n```ts\nimport { useBoolFlag, useJsonFlag } from '@avsbhq/vue';\nimport type { FlagValues } from './generated/flags';\n\nconst checkout = useBoolFlag('checkout-v2', false);\n\n// A JSON flag types its payload from the generated table:\nconst theme = useJsonFlag<FlagValues['theme']>('theme', { primary: '#111' });\n\n// Once the generated file exists, this line stops compiling:\n// 'chekcout-v2' is not a flag key.\nconst typo = useBoolFlag('chekcout-v2', false);\n```\n\nOne real consequence for reactive keys: a `Ref<string>` no longer satisfies\n`FlagKeyInput` once your keys are generated, because that ref could hold any\nstring. Declare it as `Ref<AvsbFlagKey>` (or `ComputedRef<AvsbFlagKey>`) and it\nfits again.\n\nNothing changes for a project that never runs `codegen`: with no generated file\nthe table is empty, so `FlagKeyInput` accepts any string exactly as it does\ntoday, and every call you have already written compiles unchanged. For a key\ncomputed at runtime, widen deliberately with `key as AvsbFlagKey` (that type is\nexported from `@avsbhq/core`).\n\n---\n\n## 5. Why `.value.value`\n\n`useFlag` returns a `ComputedRef<Flag<T>>`. Vue refs are unwrapped with `.value`, and the flag's own field is also called `value`, so in `<script setup>` you write:\n\n```ts\nconst checkout = useFlag('new-checkout-flow', false);\n\ncheckout.value.value; // boolean  (ref unwrap, then Flag.value)\ncheckout.value.isEnabled(); // boolean\ncheckout.value.variationKey; // string | null\n```\n\nIn a template Vue unwraps the ref for you, so it is one `.value`:\n\n```vue\n<template>\n  <NewCheckout v-if=\"checkout.value\" />\n</template>\n```\n\nThis is a real papercut and we are not going to hide it behind a rename: `Flag.value` is the same field in seven SDKs, and renaming it here would make the Vue docs disagree with every other page and every code sample.\n\nTwo ways to make it read better:\n\n```ts\nimport { computed } from 'vue';\n\n// 1. Ask for the value instead of the Flag\nconst showNewCheckout = useFlagValue('new-checkout-flow', false);\nshowNewCheckout.value; // boolean, one .value everywhere\n\n// 2. Destructure with toRefs-style naming at the top of setup\nconst checkoutFlag = useFlag('new-checkout-flow', false);\nconst isNewCheckout = computed(() => checkoutFlag.value.isEnabled());\n```\n\n---\n\n## 6. Identity\n\n```vue\n<AvsbProvider :sdk-key=\"sdkKey\" :context=\"{ kind: 'user', key: userId, plan: 'pro' }\">\n```\n\nAnonymous visitors get a persisted id (localStorage, with a cookie fallback), so a returning visitor buckets into the same variation instead of being re-randomised on every load. If the A vs B web snippet is on the same page, the SDK adopts its visitor id so flag exposures and web experiment exposures join to one visitor.\n\n```ts\nimport { useAvsbClient, useIdentify, useAlias, useReset } from '@avsbhq/vue';\n\nconst client = useAvsbClient();\nconst identify = useIdentify();\nconst alias = useAlias();\nconst reset = useReset();\n\nfunction onSignIn(user: { id: string; email: string; plan: string }): void {\n  const context = client.getContext();\n  const anonymousKey: string = 'key' in context ? String(context['key']) : '';\n\n  // Same person, two sessions: this is what lets results stitch them.\n  alias({ kind: 'user', key: anonymousKey }, { kind: 'user', key: user.id });\n\n  // From here on, flags evaluate for the signed-in user.\n  identify({ kind: 'user', key: user.id, email: user.email, plan: user.plan });\n}\n\nfunction onSignOut(): void {\n  reset(); // new anonymous identity, runtime overrides cleared\n}\n```\n\n`alias` is synchronous: it queues one event and returns. There is nothing to await, and it does not rewrite assignments already made. To carry bucketing across a login, evaluate with the same context key on both sides.\n\n### Multi-context\n\n```ts\nidentify({\n  kind: 'multi',\n  user: { kind: 'user', key: 'u_123', plan: 'pro' },\n  organization: { kind: 'organization', key: 'org_456', tier: 'enterprise' },\n});\n```\n\nA rule can bucket on `user.key` while matching an audience condition on `organization.tier`.\n\n---\n\n## 7. Reading flags\n\n```ts\nimport { useFlag, useBoolFlag, useJsonFlag } from '@avsbhq/vue';\nimport type { Flag } from '@avsbhq/vue';\n\nconst darkMode = useBoolFlag('dark-mode', false);\n\ninterface ApiConfig {\n  timeout: number;\n  retries: number;\n}\nconst config = useJsonFlag<ApiConfig>('api-config', { timeout: 5000, retries: 3 });\nconst timeout: number = config.value.value.timeout;\n```\n\nThe typed reads check the value against the type the platform declared for that flag. **On a mismatch nothing throws**: the SDK logs one warning naming the flag and the getter, then returns your `defaultValue` with `source: 'not_found'`. `useJsonFlag<T>` does not validate the shape of `T` at runtime, only that the flag is declared as JSON; validate the payload yourself if it crosses a trust boundary.\n\nA default value is always required, and it is what you get before the SDK is ready or when the key is unknown.\n\n### Reactive keys\n\n```ts\nimport { ref } from 'vue';\n\nconst selected = ref('feature-a');\nconst flag = useFlag(selected, false); // re-subscribes when `selected` changes\n```\n\n### Every flag at once\n\n```ts\nconst flags = useAllFlags(); // ComputedRef<Record<string, Flag>>\n```\n\nRe-computes on any flag change, so prefer `useFlag` in normal components. Exposures are suppressed and the SDK returns the same object between changes, so this is cheap to read on every render.\n\n---\n\n## 8. Exposures: what lands in your results\n\nReads during render never record an exposure. That is deliberate: an exposure means \"this visitor saw this decision\", and Vue can re-render a component any number of times for reasons that have nothing to do with the visitor.\n\n```ts\nimport { useExposure } from '@avsbhq/vue';\n\n// In the component that actually shows the variation:\nuseExposure('new-checkout-flow');\n```\n\n`useExposure` fires on mount, once, or as soon as the SDK becomes ready if it is not yet. It is also the piece that makes server-rendered variants visible in results: if a value arrived as a prop from the server, call `useExposure` where it is rendered.\n\nOnly decisions that belong in an experiment (A/B rules, holdouts, bandits) produce an exposure event; a plain rollout has nothing to record.\n\nFor side effects that are not rendering:\n\n```ts\nuseFlagSubscription('new-checkout-flow', (flag) => {\n  analytics.track('checkout_variant_assigned', { variationKey: flag.variationKey });\n});\n```\n\n---\n\n## 9. Readiness and failure\n\n```vue\n<script setup lang=\"ts\">\nimport { useAvsbStatus } from '@avsbhq/vue';\n\nconst { status, error, degraded } = useAvsbStatus();\n</script>\n\n<template>\n  <SkeletonApp v-if=\"status === 'loading'\" />\n  <template v-else>\n    <StaleDataNotice v-if=\"degraded\" :message=\"error?.message\" />\n    <MyApp />\n  </template>\n</template>\n```\n\n- `'loading'`: no datafile yet and nothing cached.\n- `'ready'`: flags answer. A **degraded** client is ready: a refresh failed while a cached datafile is being served, so values work and may be stale. `error` says why.\n- `'error'`: nothing could be loaded. Every flag returns the default you passed, and `error.message` names the HTTP status, the URL tried, and the fix.\n\nThe SDK's default logger writes to the console at `warn` level in development and is silent in production. Override it per client:\n\n```ts\nconst options = { logLevel: 'silent' } as const;\n```\n\n```vue\n<AvsbProvider :sdk-key=\"sdkKey\" :options=\"{ logLevel: 'debug' }\">\n```\n\n---\n\n## 10. Server rendering\n\nFetch the datafile on the server and hand it to the provider, so the first render has real values instead of defaults:\n\n```ts\n// Server\nimport { fetchDatafile } from '@avsbhq/browser/server';\nimport type { FlagDatafile } from '@avsbhq/vue';\n\nconst sdkKey: string = process.env['AVSB_SDK_KEY'] ?? '';\n\nconst datafile: FlagDatafile | null = await fetchDatafile(sdkKey, { timeout: 2000 });\n```\n\n```vue\n<!-- Client -->\n<AvsbProvider :sdk-key=\"sdkKey\" :bootstrap=\"datafileFromServer\" :context=\"userContext\">\n  <App />\n</AvsbProvider>\n```\n\nWith a bootstrap the client is ready on the first pass: `useFlagReady()` is already `true`, no loading state renders, and the values match what the server rendered. Serialise the datafile into the page the way your framework escapes JSON.\n\nFor Nuxt apps, use the @avsbhq/nuxt module, which wires this plugin, SSR bootstrap, and auto-imports for you. Outside Nuxt, the pattern above is the whole SSR story for Vue, and it is honest: without a bootstrap the first render shows your defaults.\n\n---\n\n## 11. Pinia (optional)\n\n```ts\nimport { useAvsbStore } from '@avsbhq/vue/pinia';\n\nconst avsb = useAvsbStore();\n\navsb.status; // 'loading' | 'ready' | 'error'\navsb.degraded; // boolean\navsb.flags; // Record<string, Flag>, refreshed on any flag change\navsb.identify({ kind: 'user', key: 'u_1' });\navsb.track('checkout_started', { value: 99 });\navsb.reset();\n```\n\nThe store reflects the provider; it never creates a client. The first component that calls `useAvsbStore()` binds it, so that call has to happen somewhere under `<AvsbProvider>` (or in an app that installed `AvsbPlugin`).\n\nThis lives on its own entry point on purpose. Importing it from the main barrel used to pull `pinia` into every Vue app whether or not it used Pinia, which broke module resolution for everyone else.\n\n---\n\n## 12. Plugin form\n\n```ts\nimport { createApp } from 'vue';\nimport { AvsbPlugin } from '@avsbhq/vue';\nimport App from './App.vue';\n\ndeclare const userId: string;\n\nconst app = createApp(App);\n\napp.use(AvsbPlugin, {\n  sdkKey: import.meta.env['VITE_AVSB_SDK_KEY'] as string,\n  context: { kind: 'user', key: userId },\n});\n\napp.mount('#app');\n```\n\nSame semantics as the component, provided at app level. In Mode A the plugin closes the client when the app unmounts, which flushes whatever events are queued.\n\n### One provider, please\n\nNesting a second `<AvsbProvider>` inside another shadows the outer one for everything below it: two clients, two visitor ids, two event queues, and identify calls that only reach one of them. The provider logs a warning in development when it detects this. Mount one provider at the root unless you deliberately want an isolated subtree.\n\n---\n\n## 13. Graceful shutdown\n\nMode A closes the client for you on unmount, which flushes queued events. In Mode B you own it:\n\n```ts\nimport { onUnmounted } from 'vue';\n\nonUnmounted(async () => {\n  await client.close(); // flushes internally\n});\n```\n\n---\n\n## 14. Testing\n\n```ts\nimport { mount } from '@vue/test-utils';\nimport { AvsbTestProvider } from '@avsbhq/vue/testing';\nimport CheckoutButton from './CheckoutButton.vue';\n\nconst wrapper = mount(AvsbTestProvider, {\n  props: { flags: { 'checkout-v2': true } },\n  slots: { default: CheckoutButton },\n});\n```\n\n`AvsbTestProvider` is the real `<AvsbProvider>` in Mode B around a real `AvsbClient` whose datafile is built from your flags map, with the network, the cache, and logging switched off. Your components use the same composables, the same evaluator, and the same exposure rules they use in production.\n\nEach entry in `flags` becomes a flag that is fully rolled out, so `isEnabled()` is true for truthy values and reads record exposures exactly as an A/B rule would.\n\nDrive the client directly when that is simpler:\n\n```ts\nimport { createTestClient, createTestDatafile } from '@avsbhq/vue/testing';\n\nconst client = createTestClient({\n  flags: { 'checkout-v2': true },\n  context: { kind: 'user', key: 'u_1' },\n});\n\nexpect(client.getBoolFlag('checkout-v2', false).isEnabled()).toBe(true);\n\n// Publish a new datafile and watch subscribers wake up.\nclient.applyDatafileBootstrap(\n  createTestDatafile({ 'checkout-v2': false }, { publishedAt: '2024-06-01T00:00:00.000Z' }),\n);\n```\n\n```ts\nimport type { AvsbClient } from '@avsbhq/browser';\nimport type { FlagDatafileEntry } from '@avsbhq/vue';\n\n/** Flag key to the value it serves. */\ntype TestFlags = Record<string, boolean | string | number | object | null>;\n\ninterface TestClientOptions {\n  flags?: TestFlags;\n  context?: EvalContext;\n  /** A full datafile, for when a map of values is not expressive enough. */\n  datafile?: FlagDatafile;\n}\n\nfunction createTestClient(options?: TestClientOptions): AvsbClient;\nfunction createTestDatafile(\n  flags?: TestFlags,\n  options?: {\n    sdkKey?: string;\n    environmentKey?: string;\n    publishedAt?: string;\n    entries?: FlagDatafileEntry[];\n  },\n): FlagDatafile;\n```\n\nCall `client.close()` at the end of a test that tracked events, so the flush timer does not outlive it.\n\n---\n\n## 15. Migration from LaunchDarkly\n\n| LaunchDarkly                           | `@avsbhq/vue`                                      |\n| -------------------------------------- | -------------------------------------------------- |\n| `LDPlugin` / `asyncWithLDProvider`     | `<AvsbProvider sdk-key>` or `app.use(AvsbPlugin)`  |\n| `ldClient.waitForInitialization()`     | `useFlagReady()` or `useAvsbStatus()`              |\n| `useFlags()`                           | `useAllFlags()`                                    |\n| `ldClient.variation('key', false)`     | `useBoolFlag('key', false).value.value`            |\n| `ldClient.variationDetail('key', d)`   | `useFlag('key', d)`                                |\n| `ldClient.identify(context)`           | `useIdentify()(context)`                           |\n| `ldClient.track('event', data, value)` | `useTrack()('event', { value, properties: data })` |\n| `ldClient.on('change:key', cb)`        | `useFlagSubscription('key', cb)`                   |\n\nDifferences worth knowing:\n\n- Every read returns a `Flag<T>` object, not a raw value, so you get the decision metadata without a second call.\n- Every read requires an explicit default. There is no untyped variation call.\n- Multi-context is first class: one `EvalContext` with kinds, no wrapper type.\n- Reads during render record no exposure. Call `useExposure` where the visitor actually sees the decision.\n\n---\n\n## 16. Set this up with your AI assistant\n\nPaste this into Claude Code, Cursor, or any coding assistant:\n\n```text\nSet up A vs B feature flags in this Vue project.\n\n1. Run: npx @avsbhq/cli init\n   Use my saved CLI login: do not ask me for a token and do not put one in any file.\n   It detects Vue, writes the SDK key into the environment file this project's\n   bundler reads (VITE_AVSB_SDK_KEY for Vite), and writes an example component.\n2. Install @avsbhq/vue with this project's package manager.\n3. Mount <AvsbProvider> once at the root with that key and a context of\n   { kind: 'user', key: <your user id> }, then read the flag the example names.\n4. Every getter returns a ref to a Flag object, so keep the ref and read\n   flag.value.value or call flag.value.isEnabled(), and always pass a fallback.\n   Reading a flag records nothing: call useExposure(key) where the variation is\n   shown.\n5. Then run: npx @avsbhq/cli codegen\n\nDone looks like: the app starts, the flag reads without throwing, and avsb init\nprints the line confirming it saw the first check-in.\n```\n\n`avsb init` ends by waiting for your app's first check-in and printing what it\nsaw, so the terminal tells you it works rather than the dashboard.\n","readmeFilename":"README.md"}