{"_id":"@avsbhq/svelte","_rev":"5-024cbd6843952b8cf0710f0b7d401c9d","name":"@avsbhq/svelte","dist-tags":{"latest":"1.1.1"},"versions":{"1.0.0":{"name":"@avsbhq/svelte","version":"1.0.0","_id":"@avsbhq/svelte@1.0.0","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/main1479/a-vs-b/tree/main/packages/avsb-svelte#readme","bugs":{"url":"https://github.com/main1479/a-vs-b/issues"},"dist":{"shasum":"56259b9df9b6e466faf2c9c97e24567116834e82","tarball":"https://registry.npmjs.org/@avsbhq/svelte/-/svelte-1.0.0.tgz","fileCount":11,"integrity":"sha512-7ajzSgqzVyMfldS2stRw41+kP/y+UjCy2J5AL/7ZCTiK1jnwDUFeAtLvEfEAV3NfpPRqWPyK0xDN4JVuSFW8Aw==","signatures":[{"sig":"MEYCIQCjJhxLK8CAy23zYcSt4EvEqNmCCbdc2XSyM6KOBcF/gwIhANMrP4uVHkXJK/T0oehAuOYM2wchtjNe2i0tM7mehKdX","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":67734},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","svelte":"./src/AvsbProvider.svelte","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./sveltekit":{"types":"./dist/sveltekit.d.ts","import":"./dist/sveltekit.js","require":"./dist/sveltekit.cjs"},"./AvsbProvider.svelte":"./src/AvsbProvider.svelte"},"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-svelte"},"_npmVersion":"10.9.2","description":"Svelte 5 SDK adapter for A vs B. Provides reactive Svelte stores and a context provider for feature flags and A/B experiments. Full SvelteKit support including SSR bootstrap via server hooks.","directories":{},"_nodeVersion":"22.14.0","dependencies":{"@avsbhq/core":"1.0.0","@avsbhq/browser":"1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","svelte":"^5.0.0","vitest":"^1.6.0","happy-dom":"^14.0.0","typescript":"^5.5.0","@sveltejs/kit":"^2.0.0"},"peerDependencies":{"svelte":">=5.0.0","@sveltejs/kit":">=2.0.0"},"peerDependenciesMeta":{"@sveltejs/kit":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/svelte_1.0.0_1780034367259_0.6412482826944712","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@avsbhq/svelte","version":"1.0.1","_id":"@avsbhq/svelte@1.0.1","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/main1479/a-vs-b/tree/main/packages/avsb-svelte#readme","bugs":{"url":"https://github.com/main1479/a-vs-b/issues"},"dist":{"shasum":"132f4bb77a5b2610614ba8e9cc6c4f93088357d6","tarball":"https://registry.npmjs.org/@avsbhq/svelte/-/svelte-1.0.1.tgz","fileCount":11,"integrity":"sha512-raZBjBaPQjRH/wH2q3kQb+VheBLSTnet+U3GbZIbjfgWaTr0jUYAYhImzLTCFgZ0BHssTcf8y3aYkPiZ81PdsQ==","signatures":[{"sig":"MEQCICWon9BG473lanBNhAnQ6gpF1zcR+IVydyo5FIlQ/BiAAiBMRAaQkzvWrkGJw4+LB4ROISMeX2+lHquOn2tFsTK/vg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":67597},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","svelte":"./src/AvsbProvider.svelte","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./sveltekit":{"types":"./dist/sveltekit.d.ts","import":"./dist/sveltekit.js","require":"./dist/sveltekit.cjs"},"./AvsbProvider.svelte":"./src/AvsbProvider.svelte"},"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-svelte"},"_npmVersion":"10.9.8","description":"Svelte 5 SDK adapter for A vs B. Provides reactive Svelte stores and a context provider for feature flags and A/B experiments. Full SvelteKit support including SSR bootstrap via server hooks.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@avsbhq/core":"1.4.0","@avsbhq/browser":"1.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","svelte":"^5.0.0","vitest":"^1.6.0","happy-dom":"^14.0.0","typescript":"^5.5.0","@sveltejs/kit":"^2.0.0"},"peerDependencies":{"svelte":">=5.0.0","@sveltejs/kit":">=2.0.0"},"peerDependenciesMeta":{"@sveltejs/kit":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/svelte_1.0.1_1785346476161_0.9307948468757177","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@avsbhq/svelte","version":"1.0.2","_id":"@avsbhq/svelte@1.0.2","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/main1479/a-vs-b/tree/main/packages/avsb-svelte#readme","bugs":{"url":"https://github.com/main1479/a-vs-b/issues"},"dist":{"shasum":"163e5203d4976f5fee82fe14cd24707244a3d696","tarball":"https://registry.npmjs.org/@avsbhq/svelte/-/svelte-1.0.2.tgz","fileCount":11,"integrity":"sha512-7+mPY4i+48Fucn28nZ0ZAAvHwwKIQIfuUJ7Db74Oqm6LSWoot9ENr9WATrWBVLDTXaoRgJCVvlm1sKPQeVENyQ==","signatures":[{"sig":"MEYCIQD+nexieNEu4F9yiwvf7glMXuW4JqPaFHxZ30oe4ffY3gIhAOmxWMOwSJ5Agnc6XyXM/45Pd7eJmfpV2kVmLe+0Iwnf","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":67597},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","svelte":"./src/AvsbProvider.svelte","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./sveltekit":{"types":"./dist/sveltekit.d.ts","import":"./dist/sveltekit.js","require":"./dist/sveltekit.cjs"},"./AvsbProvider.svelte":"./src/AvsbProvider.svelte"},"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-svelte"},"_npmVersion":"10.9.8","description":"Svelte 5 SDK adapter for A vs B. Provides reactive Svelte stores and a context provider for feature flags and A/B experiments. Full SvelteKit support including SSR bootstrap via server hooks.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@avsbhq/core":"1.4.0","@avsbhq/browser":"1.3.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","svelte":"^5.0.0","vitest":"^1.6.0","happy-dom":"^14.0.0","typescript":"^5.5.0","@sveltejs/kit":"^2.0.0"},"peerDependencies":{"svelte":">=5.0.0","@sveltejs/kit":">=2.0.0"},"peerDependenciesMeta":{"@sveltejs/kit":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/svelte_1.0.2_1785347989503_0.48558212312630555","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@avsbhq/svelte","version":"1.1.0","keywords":["avsb","feature-flags","ab-testing","experiments","svelte","sveltekit","runes","sdk"],"license":"MIT","_id":"@avsbhq/svelte@1.1.0","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-svelte#readme","bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"dist":{"shasum":"e8dbf6899f506e78bfc44ec599ae71b426a73f28","tarball":"https://registry.npmjs.org/@avsbhq/svelte/-/svelte-1.1.0.tgz","fileCount":35,"integrity":"sha512-30VRhNWRgiWuHxrAjdtfOa5U6YANwMgHf1u4VSF9alJ5Ubky2ENNg5OWzdC/yng0CjPDXdEs0Gqi35IQfQ6Jrw==","signatures":[{"sig":"MEYCIQDdYjzeK2fH8yxsWobx44QOek5A/M1O2HQ92O45ZtfvagIhAKaTH9Fv2NC5ZbvRAFTXbzzy2jA9cpHLCvECk0ZeGXOJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":161956},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","svelte":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"svelte":"./dist/index.js","require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./runes":{"types":"./src/runes/flags.svelte.ts","svelte":"./src/runes/flags.svelte.ts","default":"./src/runes/flags.svelte.ts"},"./testing":{"import":{"types":"./dist/testing.d.ts","default":"./dist/testing.js"},"svelte":"./dist/testing.js","require":{"types":"./dist/testing.d.cts","default":"./dist/testing.cjs"}},"./sveltekit":{"import":{"types":"./dist/sveltekit.d.ts","default":"./dist/sveltekit.js"},"require":{"types":"./dist/sveltekit.d.cts","default":"./dist/sveltekit.cjs"}},"./package.json":"./package.json","./AvsbProvider.svelte":"./src/AvsbProvider.svelte"},"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-svelte"},"_npmVersion":"10.9.8","description":"Svelte SDK for A vs B feature flags and experiments: stores, a runes accessor module, a provider, SvelteKit helpers, 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":{"tsup":"^8.0.0","svelte":"^5.0.0","vitest":"^4.1.4","happy-dom":"^14.0.0","typescript":"^5.5.0","@avsbhq/test":"^1.0.1","@sveltejs/kit":"^2.0.0"},"peerDependencies":{"svelte":">=5.0.0","@sveltejs/kit":">=2.0.0"},"peerDependenciesMeta":{"@sveltejs/kit":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/svelte_1.1.0_1785971484332_0.2632592685440389","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@avsbhq/svelte","version":"1.1.1","description":"Svelte SDK for A vs B feature flags and experiments: stores, a runes accessor module, a provider, SvelteKit helpers, and test helpers.","keywords":["avsb","feature-flags","ab-testing","experiments","svelte","sveltekit","runes","sdk"],"license":"MIT","type":"module","sideEffects":false,"main":"dist/index.cjs","module":"dist/index.js","types":"dist/index.d.ts","svelte":"./dist/index.js","exports":{".":{"svelte":"./dist/index.js","import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./sveltekit":{"import":{"types":"./dist/sveltekit.d.ts","default":"./dist/sveltekit.js"},"require":{"types":"./dist/sveltekit.d.cts","default":"./dist/sveltekit.cjs"}},"./testing":{"svelte":"./dist/testing.js","import":{"types":"./dist/testing.d.ts","default":"./dist/testing.js"},"require":{"types":"./dist/testing.d.cts","default":"./dist/testing.cjs"}},"./runes":{"svelte":"./src/runes/flags.svelte.ts","types":"./src/runes/flags.svelte.ts","default":"./src/runes/flags.svelte.ts"},"./AvsbProvider.svelte":"./src/AvsbProvider.svelte","./package.json":"./package.json"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/avsbhq/a-vs-b.git","directory":"packages/avsb-svelte"},"homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-svelte#readme","bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","typecheck":"tsc --noEmit"},"peerDependencies":{"@sveltejs/kit":">=2.0.0","svelte":">=5.0.0"},"peerDependenciesMeta":{"@sveltejs/kit":{"optional":true}},"dependencies":{"@avsbhq/browser":"^1.3.1","@avsbhq/core":"^1.4.0"},"devDependencies":{"@avsbhq/test":"^1.0.1","@sveltejs/kit":"^2.0.0","happy-dom":"^14.0.0","svelte":"^5.0.0","tsup":"^8.0.0","typescript":"^5.5.0","vitest":"^4.1.4"},"_id":"@avsbhq/svelte@1.1.1","gitHead":"d31d88fbf202f40041b4cb47f77882f454fa5ef8","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-9kMxC5/2bk31rkR46558xiW12bh/6LJ4t2/7zzZ9sdYnfC0InkacyA6srstwWSUWKyVNDuCqlUXm4DqwJ+MP+Q==","shasum":"8e8e9dc83b0eaba730c35d35fa1b9f46fdbb360d","tarball":"https://registry.npmjs.org/@avsbhq/svelte/-/svelte-1.1.1.tgz","fileCount":35,"unpackedSize":162020,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICQZd331pBfPilz6XyTSCDIes1unlgyWTuK8KsNFZawZAiEAkmR9nK/J4ojzYW4ozJlALT/iKCVK9M8/svPHLcNShYI="}]},"_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/svelte_1.1.1_1786795869612_0.4827324928808532"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-29T05:59:27.094Z","modified":"2026-08-15T12:11:09.952Z","1.0.0":"2026-05-29T05:59:27.396Z","1.0.1":"2026-07-29T17:34:36.309Z","1.0.2":"2026-07-29T17:59:49.652Z","1.1.0":"2026-08-05T23:11:24.495Z","1.1.1":"2026-08-15T12:11:09.772Z"},"bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"license":"MIT","homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-svelte#readme","keywords":["avsb","feature-flags","ab-testing","experiments","svelte","sveltekit","runes","sdk"],"repository":{"type":"git","url":"git+https://github.com/avsbhq/a-vs-b.git","directory":"packages/avsb-svelte"},"description":"Svelte SDK for A vs B feature flags and experiments: stores, a runes accessor module, a provider, SvelteKit helpers, and test helpers.","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"readme":"# @avsbhq/svelte\n\nSvelte 5 and SvelteKit SDK for the [A vs B](https://app.avsb.cloud) platform.\n\nMount one `<AvsbProvider>`, then read feature flags with stores (`$flag`) or with runes (`flag.current`). Built on [`@avsbhq/browser`](https://www.npmjs.com/package/@avsbhq/browser): components update when a flag's value changes, and only the ones reading that flag.\n\n---\n\n## 1. Install\n\n```bash\nnpm install @avsbhq/svelte\n```\n\nSvelte 5 or later is required as a peer dependency. `@sveltejs/kit` is an optional peer: install it if you use the server helpers.\n\n---\n\n## 2. Quickstart\n\n```svelte\n<!-- src/routes/+layout.svelte -->\n<script lang=\"ts\">\n  import AvsbProvider from '@avsbhq/svelte/AvsbProvider.svelte'\n  import { PUBLIC_AVSB_SDK_KEY } from '$env/static/public'\n\n  let { data, children } = $props()\n</script>\n\n<AvsbProvider\n  sdkKey={PUBLIC_AVSB_SDK_KEY}\n  context={{ kind: 'user', key: data.userId }}\n  bootstrap={data.avsbBootstrap}\n>\n  {@render children()}\n</AvsbProvider>\n```\n\n```svelte\n<!-- CheckoutButton.svelte -->\n<script lang=\"ts\">\n  import { boolFlag, getTrack, getExposure } from '@avsbhq/svelte'\n  import { onMount } from 'svelte'\n\n  const checkout = boolFlag('new-checkout-flow', false)\n  const track = getTrack()\n  const expose = getExposure()\n\n  // The visitor is about to see this decision, so record it once.\n  onMount(() => expose('new-checkout-flow'))\n</script>\n\n<button onclick={() => track('checkout_started', { value: 99 })}>\n  {$checkout.value ? 'Start checkout (new)' : 'Buy now'}\n</button>\n```\n\nBoth provider 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\nThat is why the SvelteKit example above reads it from `$env/static/public`.\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. Every store factory takes an optional `ctx` as its last argument: omit it inside a component, pass it anywhere else.\n\nThe signatures name the package's own types, and Svelte's store type. Both come from here:\n\n```ts\nimport type {\n  AvsbClientOptions,\n  AvsbContextValue,\n  AvsbProviderProps,\n  AvsbStatusValue,\n  EvalContext,\n  Flag,\n  FlagDatafile,\n  SvelteAvsbClient,\n  TrackPayload,\n} from '@avsbhq/svelte';\nimport type { Readable } from 'svelte/store';\n```\n\n```ts\n// Provider\nfunction createAvsbContext(options: AvsbProviderProps): CreateAvsbContextResult;\nfunction buildAvsbContext(options: AvsbProviderProps): CreateAvsbContextResult;\n\ninterface CreateAvsbContextResult {\n  contextValue: AvsbContextValue;\n  client: SvelteAvsbClient;\n  cleanup: () => void;\n}\n\n// Reading flags\nfunction flag<T>(flagKey: string, defaultValue: T, ctx?: AvsbContextValue): Readable<Flag<T>>;\nfunction flagValue<T>(flagKey: string, defaultValue: T, ctx?: AvsbContextValue): Readable<T>;\nfunction boolFlag(\n  flagKey: string,\n  defaultValue: boolean,\n  ctx?: AvsbContextValue,\n): Readable<Flag<boolean>>;\nfunction stringFlag(\n  flagKey: string,\n  defaultValue: string,\n  ctx?: AvsbContextValue,\n): Readable<Flag<string>>;\nfunction numberFlag(\n  flagKey: string,\n  defaultValue: number,\n  ctx?: AvsbContextValue,\n): Readable<Flag<number>>;\nfunction jsonFlag<T>(flagKey: string, defaultValue: T, ctx?: AvsbContextValue): Readable<Flag<T>>;\nfunction allFlags(ctx?: AvsbContextValue): Readable<Record<string, Flag>>;\n\n// Readiness and errors\nfunction flagReady(ctx?: AvsbContextValue): Readable<boolean>;\nfunction avsbStatus(ctx?: AvsbContextValue): Readable<AvsbStatusValue>;\n\n// Client, identity, events\nfunction getAvsbClient(ctx?: AvsbContextValue): SvelteAvsbClient;\nfunction getTrack(ctx?: AvsbContextValue): (eventKey: string, payload?: TrackPayload) => void;\nfunction getIdentify(ctx?: AvsbContextValue): (context: EvalContext) => void;\nfunction getAlias(ctx?: AvsbContextValue): (previous: EvalContext, next: EvalContext) => void;\nfunction getReset(ctx?: AvsbContextValue): () => void;\nfunction getExposure(ctx?: AvsbContextValue): (flagKey: string) => () => void;\nfunction subscribeFlag(\n  flagKey: string,\n  callback: (flag: Flag) => void,\n  ctx?: AvsbContextValue,\n): () => void;\n\n// Context\nconst AVSB_CONTEXT_KEY: unique symbol;\nfunction useAvsbContext(): AvsbContextValue;\nfunction setAvsbContext(value: AvsbContextValue): void;\nfunction hasAvsbContext(): boolean;\n```\n\nThe `get*` prefix is not decoration: Svelte context can only be read while a component is initialising, so these are called at the top of `<script>` and return a function you can use later, in a handler or in `onMount`.\n\n### Provider props\n\n```ts\ntype AvsbProviderProps = (AvsbClientOptions & { client?: never }) | { client: SvelteAvsbClient };\n```\n\n**Mode A** (an `sdkKey`, plus any other `AvsbClientOptions` such as `context` or `bootstrap`) means the provider builds the client and closes it when the component is destroyed. **Mode B** (a `client`) means you own its lifetime and the provider never closes it. Passing neither throws during initialisation with a message naming both fixes, rather than rendering a tree where every flag is silently the default.\n\n### `AvsbContextValue`\n\n```ts\ninterface AvsbContextValue {\n  client: SvelteAvsbClient;\n  readonly status: 'loading' | 'ready' | 'error';\n  readonly error: Error | undefined;\n  readonly degraded: boolean;\n  onStatusChange(listener: () => void): () => void;\n}\n```\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  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  readonly evaluatedAt: number;\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 `$flagReady`.                        | false           | false      |\n\n\"value-dependent\" means `isEnabled()` is `Boolean(flag.value)`.\n\n### `AvsbStatusValue`\n\n```ts\ninterface AvsbStatusValue {\n  status: 'loading' | 'ready' | 'error';\n  error: Error | undefined;\n  degraded: boolean;\n}\n```\n\n### Typed flag keys\n\nEvery `flagKey` above is `string` until you generate your keys. Generate them\nand it becomes the union of this project's real flag keys, so a typo is a\ncompile 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 store, rune and action in this package which keys exist. Its\nimportant 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```svelte\n<script lang=\"ts\">\n  import { boolFlag, jsonFlag } from '@avsbhq/svelte'\n  import type { FlagValues } from './generated/flags'\n\n  const checkout = boolFlag('checkout-v2', false)\n\n  // A JSON flag types its payload from the generated table:\n  const theme = jsonFlag<FlagValues['theme']>('theme', { primary: '#111' })\n\n  // Once the generated file exists, this line stops compiling:\n  // 'chekcout-v2' is not a flag key.\n  const typo = boolFlag('chekcout-v2', false)\n</script>\n```\n\nThe runes layer (`flagState`, `boolFlagState` and friends) and the SvelteKit\nserver client narrow their keys the same way.\n\nNothing changes for a project that never runs `codegen`: with no generated file\nthe table is empty, every `flagKey` is exactly `string`, and every call you have\nalready written compiles unchanged. For a key computed at runtime, widen\ndeliberately with `key as AvsbFlagKey` (that type is exported from\n`@avsbhq/core`).\n\n---\n\n## 5. Runes, if you prefer them\n\n```svelte\n<script lang=\"ts\">\n  import { boolFlagState } from '@avsbhq/svelte/runes'\n\n  const checkout = boolFlagState('new-checkout-flow', false)\n</script>\n\n{#if checkout.current.value}\n  <NewCheckout />\n{/if}\n```\n\n```ts\nfunction flagState<T>(flagKey: string, defaultValue: T, ctx?: AvsbContextValue): FlagState<T>;\nfunction boolFlagState(\n  flagKey: string,\n  defaultValue: boolean,\n  ctx?: AvsbContextValue,\n): FlagState<boolean>;\nfunction stringFlagState(\n  flagKey: string,\n  defaultValue: string,\n  ctx?: AvsbContextValue,\n): FlagState<string>;\nfunction numberFlagState(\n  flagKey: string,\n  defaultValue: number,\n  ctx?: AvsbContextValue,\n): FlagState<number>;\nfunction jsonFlagState<T>(flagKey: string, defaultValue: T, ctx?: AvsbContextValue): FlagState<T>;\nfunction allFlagsState(ctx?: AvsbContextValue): { readonly current: Record<string, Flag> };\nfunction avsbStatusState(ctx?: AvsbContextValue): { readonly current: AvsbStatusValue };\n\ninterface FlagState<T> {\n  readonly current: Flag<T>;\n}\n```\n\nThe two layers are the same subscription underneath, so they can never report different values. Pick per file: stores if your codebase is store-shaped, runes if it is runes-shaped.\n\n**Where they can be called.** Anywhere an effect can be created: a component's `<script>`, or a `.svelte.ts` module or class that a component instantiates. Called from a plain module at import time you get the first value and no updates, because `$effect` needs an owner. For that case, own it yourself:\n\n```ts\ndeclare const ctx: AvsbContextValue;\ndeclare function report(isEnabled: boolean): void;\n\nconst stop = $effect.root(() => {\n  const checkout = boolFlagState('new-checkout-flow', false, ctx);\n  $effect(() => report(checkout.current.value));\n});\n// later\nstop();\n```\n\nPassing `ctx` covers the provider context, not the effect owner: those are two separate requirements, and only the stores are free of the second one.\n\n`@avsbhq/svelte/runes` ships as TypeScript source rather than compiled output, because runes have to be compiled by your app's Svelte compiler. That is why it is a separate entry point: importing the main barrel never pulls uncompiled runes into a non-Svelte build.\n\n---\n\n## 6. Identity\n\n```svelte\n<script lang=\"ts\">\n  import { getAvsbClient, getIdentify, getAlias, getReset } from '@avsbhq/svelte'\n\n  const client = getAvsbClient()\n  const identify = getIdentify()\n  const alias = getAlias()\n  const reset = getReset()\n\n  function onSignIn(user: { id: string; email: string; plan: string }): void {\n    const context = client.getContext()\n    const anonymousKey = '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    identify({ kind: 'user', key: user.id, email: user.email, plan: user.plan })\n  }\n</script>\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`alias` is synchronous: it queues one event and returns. There is nothing to await, and it does not rewrite assignments already made. `reset()` rotates to a new anonymous identity and clears runtime overrides; call it on sign-out.\n\n### Multi-context\n\n```ts\nimport { getIdentify } from '@avsbhq/svelte';\n\n// At the top of a component's <script>, as above.\nconst identify = getIdentify();\n\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```svelte\n<script lang=\"ts\">\n  import { boolFlag, jsonFlag, flagValue } from '@avsbhq/svelte'\n\n  interface ApiConfig {\n    timeout: number\n    retries: number\n  }\n\n  const darkMode = boolFlag('dark-mode', false)\n  const config = jsonFlag<ApiConfig>('api-config', { timeout: 5000, retries: 3 })\n  const banner = flagValue('promo-banner', 'grey')\n</script>\n\n<Theme dark={$darkMode.value} timeout={$config.value.timeout} colour={$banner} />\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'`. `jsonFlag<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`allFlags()` gives you everything at once for debug panels; prefer the single-flag stores in normal components, since `allFlags` re-emits on any change.\n\n---\n\n## 8. Exposures: what lands in your results\n\nReading a flag never records an exposure. That is deliberate: an exposure means \"this visitor saw this decision\", and a component can re-render for reasons that have nothing to do with the visitor.\n\n```svelte\n<script lang=\"ts\">\n  import { getExposure } from '@avsbhq/svelte'\n  import { onMount } from 'svelte'\n\n  const expose = getExposure()\n  onMount(() => expose('new-checkout-flow'))\n</script>\n```\n\nIf the SDK is not ready yet the exposure is deferred to the moment it is, rather than dropped. Only decisions that belong in an experiment (A/B rules, holdouts, bandits) produce an event; a plain rollout has nothing to record.\n\n`expose(key)` returns a cancel function for that deferred case, and `onMount` hands whatever you return to `onDestroy`, so the one-liner above already cancels itself: a component destroyed before the SDK was ready does not record an exposure for a decision nobody is looking at any more.\n\nThis is also how a server-rendered variant becomes visible in results: evaluate on the server, render the value, and call `expose(key)` in the component that shows it. On the server side, `locals.avsb?.client.manualExposure(key)` does the same job when the page is rendered without hydration.\n\nFor side effects that are not rendering:\n\n```ts\nimport { onDestroy } from 'svelte';\n\nonDestroy(\n  subscribeFlag('new-checkout-flow', (f) => {\n    analytics.track('checkout_variant_assigned', { variationKey: f.variationKey });\n  }),\n);\n```\n\n---\n\n## 9. Readiness and failure\n\n```svelte\n<script lang=\"ts\">\n  import { avsbStatus } from '@avsbhq/svelte'\n  const status = avsbStatus()\n</script>\n\n{#if $status.status === 'loading'}\n  <Skeleton />\n{:else}\n  {#if $status.degraded}\n    <StaleDataNotice message={$status.error?.message} />\n  {/if}\n  <App />\n{/if}\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. Pass `logLevel=\"silent\"` (or any other `AvsbClientOptions` field) to the provider to change that.\n\n---\n\n## 10. SvelteKit\n\n```ts\n// src/hooks.server.ts\nimport { AvsbServer } from '@avsbhq/node';\nimport { withAvsbHooks } from '@avsbhq/svelte/sveltekit';\nimport { AVSB_SDK_KEY } from '$env/static/private';\nimport type { Handle } from '@sveltejs/kit';\n\nconst avsb = new AvsbServer({ sdkKey: AVSB_SDK_KEY });\n\nexport const handle: Handle = withAvsbHooks(({ event, resolve }) => resolve(event), {\n  serverClient: avsb,\n  resolveContext: (event) => ({\n    kind: 'user',\n    key: event.cookies.get('uid') ?? 'anonymous',\n  }),\n});\n```\n\n`withAvsbHooks` takes and returns SvelteKit's own `Handle`, so it drops straight into `hooks.server.ts` with no cast and composes with `sequence()` in either order.\n\n```ts\n// src/app.d.ts\ndeclare global {\n  namespace App {\n    interface Locals {\n      // Optional: `withAvsbHooks` sets no locals on a request whose\n      // `resolveContext` returned null. Make it required only if yours\n      // never does.\n      avsb?: import('@avsbhq/svelte/sveltekit').AvsbLocals;\n    }\n  }\n}\nexport {};\n```\n\n```ts\n// src/routes/+layout.server.ts\nimport type { LayoutServerLoad } from './$types';\n\nexport const load: LayoutServerLoad = ({ locals }: { locals: App.Locals }) => ({\n  // The datafile: this is what <AvsbProvider bootstrap={...}> takes.\n  avsbBootstrap: locals.avsb?.bootstrap ?? null,\n  // Server-evaluated values, if you want the markup decided on the server.\n  newCheckout: locals.avsb?.client.getBoolFlag('new-checkout-flow', false).value ?? false,\n});\n```\n\nThen pass `data.avsbBootstrap` to the provider, as in the quickstart. With a bootstrap the browser client is ready on the first render: `$flagReady` is already `true`, no loading state renders, and the values match what the server rendered.\n\n```ts\nimport type { AvsbBoundClient, RequestEvent } from '@avsbhq/svelte/sveltekit';\n\ninterface AvsbLocals {\n  evalContext: EvalContext;\n  client: AvsbBoundClient;\n  bootstrap: FlagDatafile | null;\n}\n\ninterface AvsbServerClient {\n  forUser(context: EvalContext): AvsbBoundClient;\n  getDatafile(): FlagDatafile | null;\n}\n\ninterface WithAvsbHooksOptions {\n  serverClient: AvsbServerClient;\n  /** Return null to skip A vs B for this request; no locals are set. */\n  resolveContext: (event: RequestEvent) => EvalContext | null;\n}\n\n// `Handle` and `RequestEvent` are SvelteKit's own types, re-exported here.\nfunction withAvsbHooks(handle: Handle, options: WithAvsbHooksOptions): Handle;\nfunction getAvsbBootstrap(serverClient: AvsbServerClient): FlagDatafile | null;\nfunction bindAvsbClient(serverClient: AvsbServerClient, context: EvalContext): AvsbBoundClient;\n```\n\n`AvsbServer` from `@avsbhq/node` satisfies `AvsbServerClient`, and the client its `forUser(context)` returns satisfies `AvsbBoundClient`, so nothing needs casting.\n\n**What changed and why**: the previous helper serialised a map of evaluated flags and told you to pass it as `bootstrap`. The browser client's `bootstrap` is a datafile and it iterates `datafile.flags`, so following that recipe crashed the client at construction. `bootstrap` is now the datafile, which is JSON-safe by construction because it is the document the CDN serves. Handing the browser server-evaluated VALUES instead is a different handoff that the browser SDK does not expose publicly yet.\n\n---\n\n## 11. 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## 12. Graceful shutdown\n\nMode A closes the client when the provider is destroyed, which flushes queued events. In Mode B you own it:\n\n```ts\nimport { onDestroy } from 'svelte';\nimport { getAvsbClient } from '@avsbhq/svelte';\n\nconst client = getAvsbClient();\n\nonDestroy(async () => {\n  await client.close(); // flushes internally\n});\n```\n\n---\n\n## 13. Testing\n\n```ts\nimport {\n  createTestContextValue,\n  createTestClient,\n  createTestDatafile,\n} from '@avsbhq/svelte/testing';\nimport { boolFlag } from '@avsbhq/svelte';\nimport { get } from 'svelte/store';\n\nconst { contextValue, client } = createTestContextValue({\n  flags: { 'checkout-v2': true },\n});\n\nexpect(get(boolFlag('checkout-v2', false, contextValue)).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\nInside a component test, set the real context instead:\n\n```ts\nimport { createAvsbTestContext } from '@avsbhq/svelte/testing';\n\n// during component initialisation\ncreateAvsbTestContext({ flags: { 'checkout-v2': true } });\n```\n\n```ts\nimport type { AvsbClient } from '@avsbhq/browser';\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 createTestContextValue(options?: TestClientOptions): CreateAvsbContextResult;\nfunction createAvsbTestContext(options?: TestClientOptions): CreateAvsbContextResult;\nfunction createTestDatafile(flags?: TestFlags, options?: { publishedAt?: string }): FlagDatafile;\n```\n\nThe client is a real `AvsbClient` whose datafile is built from your flags map, with the network, the cache, and logging switched off, so tests exercise the same evaluator and the same exposure rules as production. Each entry in `flags` becomes a fully rolled out flag, so `isEnabled()` is true for truthy values and reads record exposures exactly as an A/B rule would.\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## 14. Migration from LaunchDarkly\n\n| LaunchDarkly JS                      | `@avsbhq/svelte`                                   |\n| ------------------------------------ | -------------------------------------------------- |\n| `initialize(clientId, context)`      | `<AvsbProvider sdkKey context>`                    |\n| `client.waitForInitialization()`     | `$flagReady` or `$avsbStatus`                      |\n| `client.allFlags()`                  | `$allFlags`                                        |\n| `client.variation('key', false)`     | `$boolFlag('key', false).value`                    |\n| `client.variationDetail('key', d)`   | `$flag('key', d)`                                  |\n| `client.identify(context)`           | `getIdentify()(context)`                           |\n| `client.track('event', data, value)` | `getTrack()('event', { value, properties: data })` |\n| `client.on('change:key', cb)`        | `subscribeFlag('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 record no exposure. Call `getExposure()(key)` where the visitor actually sees the decision.\n\n---\n\n## 15. 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 Svelte 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 Svelte, writes the SDK key into .env as PUBLIC_AVSB_SDK_KEY for\n   SvelteKit (VITE_AVSB_SDK_KEY for plain Vite), and writes an example component.\n2. Install @avsbhq/svelte with this project's package manager.\n3. Mount AvsbProvider once in the root layout with that key and a context of\n   { kind: 'user', key: <your user id> }, then read the flag the example names.\n4. Flag stores hold a Flag object, so read $flag.value or call $flag.isEnabled(),\n   and always pass a fallback. Reading a flag records nothing: call\n   getExposure()(key) where the variation is 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"}