Cloudflare Workers integration
Cloudflare Workers run in V8 isolates distributed across Cloudflare's global network. This guide shows how to evaluate feature flags with zero added latency by caching the A vs B datafile in Workers KV. By the end your Worker will read flags, track events, and flush event queues without holding up the response.
Install
1npm install @avsbhq/edgeObtain your SDK key
Open your A vs B project, go to Settings → Environments, and copy the Server SDK key. Add it to your Worker as an environment secret:
1npx wrangler secret put AVSB_SDK_KEYDeclare the KV namespace in wrangler.toml
Create a KV namespace to cache the A vs B datafile so each Worker invocation reads from local KV rather than making an outbound HTTPS request.
1name = "my-worker"2main = "src/worker.ts"3compatibility_date = "2024-09-23"4
5[[kv_namespaces]]6binding = "AVSB_DATAFILE_CACHE"7id = "<your-kv-namespace-id>"8preview_id = "<your-preview-kv-namespace-id>"Bootstrap the edge client
Cloudflare Workers have no persistent background timers between requests, so the AvsbEdgeClient reads from KV on each cold start. The@avsbhq/edge/cloudflare subpath exports a createCloudflareHandler factory that wires KV caching, client init, and ctx.waitUntil-aware event flushing in one call.
1import { createCloudflareHandler } from '@avsbhq/edge/cloudflare'2
3interface Env {4 AVSB_SDK_KEY: string5 AVSB_DATAFILE_CACHE: KVNamespace6}7
8export default {9 fetch: createCloudflareHandler({10 sdkKey: globalThis.__ENV?.AVSB_SDK_KEY ?? '',11 kv: globalThis.__ENV?.AVSB_DATAFILE_CACHE,12 contextFrom: (req) => ({13 kind: 'user',14 key: req.headers.get('cf-visitor-id') ?? 'anon',15 }),16 handler: async (req, client) => {17 const flag = client.getFlag('new-homepage', false, {18 kind: 'user',19 key: req.headers.get('cf-visitor-id') ?? 'anon',20 country: (req.cf?.country as string | undefined) ?? 'unknown',21 })22 return Response.json({ value: flag.value })23 },24 }),25} satisfies ExportedHandler<Env>KVNamespace binding for datafile caching with a 5-minute TTL, builds the per-request EvalContext via your contextFrom callback, and calls ctx.waitUntil(client.flushEvents()) automatically after your handler returns. If you need lower-level control, instantiate new AvsbEdgeClient(opts) from @avsbhq/edge directly.Keep the datafile fresh
Register a datafile.published webhook in Settings → Webhooks. Your webhook receiver should write the new datafile to KV so all Workers pick up the change on their next cold start without waiting for the TTL to expire.
1// Minimal example — add auth validation in production2export default {3 async fetch(request: Request, env: Env): Promise<Response> {4 const body = await request.json<{ datafile: string }>()5 await env.AVSB_DATAFILE_CACHE.put('avsb-datafile', body.datafile, {6 expirationTtl: 3600,7 })8 return new Response('ok')9 },10}Read a flag
1const flag = scoped.getFlag('new-homepage', false)2// T is inferred as boolean from the defaultValue3// flag.value — the result4// flag.source — 'rule' | 'default' | 'not_found'5// flag.variationKey — which variation (null if default)Track an event
1scoped.track('page_view', {2 properties: { path: new URL(request.url).pathname },3})4// Always pair track() calls with ctx.waitUntil(avsb.flush())Identify a user
Build the EvalContext from whatever identity signal is available in the request — a cookie, a JWT claim, or a Cloudflare Access header.
1const jwt = request.headers.get('cf-access-jwt-assertion')2const claims = jwt ? parseJwt(jwt) : null3
4const scoped = avsb.forUser({5 kind: 'user',6 key: claims?.sub ?? crypto.randomUUID(),7 plan: claims?.plan ?? 'free',8 country: (request.cf?.country as string | undefined) ?? 'unknown',9})Graceful shutdown
Workers do not have a shutdown lifecycle — isolates are discarded silently. Always use ctx.waitUntil(avsb.flush()) to ensure queued exposure and tracking events are sent before the isolate is frozen. The flush() call is non-blocking from the perspective of the response.
Testing
1import { TestData, createMockClient } from '@avsbhq/test'2import { describe, it, expect } from 'vitest'3
4const td = TestData.flag('new-homepage')5 .booleanFlag()6 .variationForUser('u_1', true)7 .fallthroughVariation(false)8
9const mock = createMockClient({ flags: [td.build()] })10
11describe('Worker flag evaluation', () => {12 it('returns true for u_1', () => {13 const flag = mock.forUser({ kind: 'user', key: 'u_1' }).getFlag('new-homepage', false)14 expect(flag.value).toBe(true)15 })16
17 it('returns false as fallthrough', () => {18 const flag = mock.forUser({ kind: 'user', key: 'other' }).getFlag('new-homepage', false)19 expect(flag.value).toBe(false)20 })21})What's next
- Edge SDK reference — full
AvsbEdgeClientAPI surface including streaming updates. - Deno Deploy integration— the same pattern on Deno's edge runtime.
- Multi-context targeting — combine user, organisation, and device contexts in a single evaluation.