/Docs

Typed Contexts

defineContextSchema() creates a compile-time contract between the attributes your application passes to the SDK and the attribute keys declared in your flag targeting rules. Mismatches — a typo in a field name, a type mismatch, a removed attribute still referenced in a rule — become TypeScript errors before they ship.

What it is

Without typed contexts, the evaluation context is a plain object with an open [attribute: string]: unknown index signature. You can write usrId: 'u_1' instead of userId: 'u_1' and the SDK will evaluate silently — the user just falls through to the default variation because the targeting condition never matches.

defineContextSchema() replaces the open bag with a Zod-validated, fully typed builder. The schema is co-located with your SDK client initialisation, so TypeScript enforces it wherever contexts are constructed — at the component level, in middleware, and in tests.

ts
1import { z } from 'zod'
2import { defineContextSchema } from '@avsbhq/utils'
3
4// Declare the shape once
5export const Ctx = defineContextSchema({
6 user: z.object({
7 id: z.string(),
8 plan: z.enum(['free', 'pro', 'enterprise']),
9 country: z.string().optional(),
10 }),
11 organization: z.object({
12 id: z.string(),
13 tier: z.number().int().min(1).max(5),
14 }),
15})
16
17// Build contexts with full type-checking
18const context = Ctx.builder()
19 .setUser('u_123', { id: 'u_123', plan: 'pro', country: 'US' })
20 .setOrganization('o_42', { id: 'o_42', tier: 3 })
21 .build()
22
23// TypeScript error: 'usrId' does not exist in type
24Ctx.builder().setUser('u_123', { usrId: 'u_123', plan: 'pro' })
25// ^^^^^^ Property 'usrId' does not exist

When to use it

Typed contexts have the highest value when:

  • Your flag targeting rules reference many custom attributes — plan tiers, roles, feature flags on the user record — and you want the TypeScript compiler to verify that attribute keys match between the rules and the SDK call sites.
  • You use the CLI's avsb codegen command, which emits a ContextSchemainterface matching your platform's declared registeredAttributes. This creates an end-to-end type chain: platform schema → generated TypeScript → SDK call sites.
  • Your codebase has multiple teams passing context objects and you want a single source of truth for the attribute shape.

How it works

defineContextSchema returns an object with two members:

  • builder() — returns a typed ContextBuilder whose setter methods accept only the declared attribute shape for the given kind. The returned EvalContext from build() is a plain object compatible with every SDK API.
  • validate(ctx) — runs the Zod schema against anyEvalContext and returns a SafeParseReturnType. Useful in middleware to verify incoming contexts before forwarding to the evaluator.
ts
1// Runtime validation in Express middleware
2app.use((req, res, next) => {
3 const raw = buildContextFromRequest(req)
4 const result = Ctx.validate(raw)
5
6 if (!result.success) {
7 logger.warn('invalid avsb context', { errors: result.error.issues })
8 // Fall through with a safe anonymous context
9 req.avsbContext = { kind: 'user', key: 'anonymous' }
10 } else {
11 req.avsbContext = result.data
12 }
13 next()
14})

CLI codegen integration

The avsb codegencommand reads your platform project's declared context kinds and attribute definitions and emits a typed schema file:

bash
1npx avsb codegen --output ./src/generated/flags.ts --project proj_abc123

The generated file exports three types:

ts
1// generated/flags.ts (example output)
2export type FlagKey =
3 | 'checkout-v2'
4 | 'homepage-hero'
5 | 'pricing-experiment'
6
7export interface FlagValues {
8 'checkout-v2': boolean
9 'homepage-hero': 'control' | 'variant-a' | 'variant-b'
10 'pricing-experiment': { tiers: number; perks: string[] }
11}
12
13export interface ContextSchema {
14 user: { id: string; plan: 'free' | 'pro' | 'enterprise' }
15 organization: { id: string; tier: number }
16}

Import ContextSchema into your defineContextSchema call to keep your local schema in sync with the platform:

ts
1import { z } from 'zod'
2import { defineContextSchema } from '@avsbhq/utils'
3import type { ContextSchema } from './generated/flags'
4
5// Zod schema derived from the generated interface
6export const Ctx = defineContextSchema({
7 user: z.object({
8 id: z.string(),
9 plan: z.enum(['free', 'pro', 'enterprise']),
10 } satisfies Record<keyof ContextSchema['user'], z.ZodTypeAny>),
11 organization: z.object({
12 id: z.string(),
13 tier: z.number(),
14 } satisfies Record<keyof ContextSchema['organization'], z.ZodTypeAny>),
15})
Tip
Add avsb codegen to your postinstall or pre-build step so the generated file is always fresh when you install dependencies or deploy.

CLI lint integration

When you run avsb lint, the CLI compares your generated schema against the flag rules fetched from the platform and reports any targeting condition that references an attribute key not declared in the schema. This catches drift between the platform configuration and your TypeScript schema without requiring a full type-check run.