/Docs

Deno Deploy integration

Deno Deploy runs JavaScript and TypeScript at the edge in Deno isolates. Because Deno natively resolves npm packages via the npm: specifier, you can use @avsbhq/edge without any build step. By the end of this guide your Deno Deploy function will evaluate feature flags from a KV-cached datafile and flush events after the response is sent.

1

Import the package

Deno resolves npm packages at runtime using the npm: prefix. No package.json or deno.json install step is required, though you can pin the version in your import map for reproducibility.

main.ts
typescript
1import { AvsbEdgeClient } from 'npm:@avsbhq/edge'

Or pin the version in deno.json:

deno.json
json
1{
2 "imports": {
3 "@avsbhq/edge": "npm:@avsbhq/edge@^1.0.0"
4 }
5}

Then import without the npm: prefix:

main.ts
typescript
1import { AvsbEdgeClient } from '@avsbhq/edge'
2

Obtain your SDK key

Open your A vs B project, navigate to Settings → Environments, and copy the Server SDK key. Add it to Deno Deploy as a project environment variable named AVSB_SDK_KEYin your project's settings on dash.deno.com.

3

Bootstrap the edge client

Deno Deploy isolates do not have persistent background timers between requests. The AvsbEdgeClient reads the datafile from Deno KV on startup and falls back to the A vs B CDN if KV is empty.

main.ts
typescript
1import { AvsbEdgeClient } from 'npm:@avsbhq/edge'
2
3// Deno KV — available in all Deno Deploy projects
4const kv = await Deno.openKv()
5
6function makeKvCache(kv: Deno.Kv) {
7 return {
8 async get(key: string): Promise<string | null> {
9 const result = await kv.get<string>(['avsb', key])
10 return result.value ?? null
11 },
12 async set(key: string, value: string): Promise<void> {
13 await kv.set(['avsb', key], value, { expireIn: 3_600_000 })
14 },
15 }
16}
17
18const avsb = new AvsbEdgeClient({
19 sdkKey: Deno.env.get('AVSB_SDK_KEY')!,
20 datafileCache: makeKvCache(kv),
21})
22
23Deno.serve(async (req: Request): Promise<Response> => {
24 await avsb.onReady()
25
26 const userId = req.headers.get('x-user-id') ?? 'anon'
27 const scoped = avsb.forUser({ kind: 'user', key: userId })
28
29 const flag = scoped.getFlag('checkout-v2', false)
30
31 // Flush events asynchronously after the response
32 // Deno Deploy supports EdgeRuntime.waitUntil in recent versions
33 EdgeRuntime.waitUntil(avsb.flush())
34
35 return Response.json({ value: flag.value })
36})
EdgeRuntime.waitUntil
EdgeRuntime.waitUntil is available on Deno Deploy. If you are running locally with deno run for development it may not exist; guard with typeof EdgeRuntime !== 'undefined' or await flush directly before returning the response during local development.
4

Read a flag

typescript
1// Boolean flag — T inferred from defaultValue
2const flag = scoped.getFlag('checkout-v2', false)
3if (flag.value) {
4 // serve new checkout experience
5}
6
7// String flag
8const theme = scoped.getFlag('ui-theme', 'default')
9
10// JSON flag
11const config = scoped.getFlag('pricing-config', { tier: 'standard' })
5

Track an event

typescript
1scoped.track('purchase', {
2 value: 49.99,
3 properties: { currency: 'usd', sku: 'PRO-ANNUAL' },
4})
6

Identify a user

Build the EvalContext from the request. Deno Deploy passes Cloudflare geolocation headers so you can include country-based targeting without extra work.

typescript
1const scoped = avsb.forUser({
2 kind: 'user',
3 key: req.headers.get('x-user-id') ?? crypto.randomUUID(),
4 plan: req.headers.get('x-user-plan') ?? 'free',
5 country: req.headers.get('x-country') ?? 'unknown',
6})

Keeping the datafile fresh

Register a datafile.published webhook in Settings → Webhooks. Your webhook endpoint should write the updated datafile JSON into Deno KV so all edge instances pick it up on their next request without waiting for the TTL to expire.

webhook.ts
typescript
1// Minimal — add HMAC signature validation in production
2Deno.serve(async (req: Request): Promise<Response> => {
3 if (req.method !== 'POST') return new Response('Method Not Allowed', { status: 405 })
4 const body = await req.json<{ datafile: string; sdkKey: string }>()
5 const kv = await Deno.openKv()
6 await kv.set(['avsb', body.sdkKey], body.datafile, { expireIn: 3_600_000 })
7 return new Response('ok')
8})

Graceful shutdown

Deno Deploy isolates are frozen rather than gracefully shut down. Always pass the flush call to EdgeRuntime.waitUntil so it completes after the response is delivered. For local development or when running under deno run with a standard HTTP server, add a Deno.addSignalListener for cleanup:

typescript
1Deno.addSignalListener('SIGTERM', async () => {
2 await avsb.flush()
3 await avsb.close()
4 Deno.exit(0)
5})

Testing

main_test.ts
typescript
1import { TestData, createMockClient } from 'npm:@avsbhq/test'
2import { assertEquals } from 'https://deno.land/std/assert/mod.ts'
3
4const td = TestData.flag('checkout-v2')
5 .booleanFlag()
6 .variationForUser('u_1', true)
7 .fallthroughVariation(false)
8
9const mock = createMockClient({ flags: [td.build()] })
10
11Deno.test('returns true for u_1', () => {
12 const flag = mock.forUser({ kind: 'user', key: 'u_1' }).getFlag('checkout-v2', false)
13 assertEquals(flag.value, true)
14})
15
16Deno.test('returns default false', () => {
17 const flag = mock.forUser({ kind: 'user', key: 'other' }).getFlag('checkout-v2', false)
18 assertEquals(flag.value, false)
19})

What's next