Skip to content

Context

Context allows you to inject request-scoped data (like cookies, headers, or user sessions) into your Vla classes. This is essential for building applications that need to access request-specific information.

Your services and repos often need access to request-specific data:

class UserService extends Vla.Service {
async getCurrentUser() {
// How do we access the current request's cookies?
// How do we know which user is logged in?
}
}

Passing this data through every method call becomes unwieldy:

// ❌ Not ideal
async function handler(req: Request) {
const user = await userService.getCurrentUser(req.cookies)
const posts = await postService.getPosts(req.cookies)
// ...
}

Vla’s context system provides request-scoped dependency injection:

// 1. Define your context type
const RequestContext = Vla.createContext<{
cookies: Record<string, string>
headers: Headers
}>()
// 2. Inject it in your classes
class SessionService extends Vla.Service {
ctx = this.inject(RequestContext)
async currentUser() {
const userId = this.ctx.cookies['user_id']
return this.findUser(userId)
}
}
// 3. Provide the context when creating a scoped kernel
const scoped = kernel.scoped().context(RequestContext, {
cookies: req.cookies,
headers: req.headers
})

Define a context with Vla.createContext():

// Simple context
const AppContext = Vla.createContext<{
cookies: Record<string, string>
}>()
// Multiple contexts for different concerns
const RequestContext = Vla.createContext<{
headers: Headers
ip: string
}>()
const AuthContext = Vla.createContext<{
userId: string | null
isAuthenticated: boolean
}>()

Inject context like any other dependency:

class UserService extends Vla.Service {
ctx = this.inject(RequestContext)
auth = this.inject(AuthContext)
async getCurrentUser() {
if (!this.auth.isAuthenticated) {
throw new Error('Not authenticated')
}
const userId = this.auth.userId
return this.findUserById(userId)
}
async logRequest() {
console.log('Request from:', this.ctx.ip)
console.log('User-Agent:', this.ctx.headers.get('user-agent'))
}
}

The most common approach is to provide context when creating a scoped kernel:

import { Kernel, Vla } from 'vla'
const kernel = new Kernel()
// In your request handler
app.use((req, res, next) => {
const scoped = kernel
.scoped()
.context(RequestContext, {
cookies: req.cookies,
headers: req.headers
})
Vla.withKernel(scoped, () => next())
})

You can provide multiple contexts:

const scoped = kernel
.scoped()
.context(RequestContext, {
headers: req.headers,
ip: req.ip
})
.context(AuthContext, {
userId: session.userId,
isAuthenticated: session.isAuthenticated
})

Context values can be promises:

import { cookies } from 'next/headers'
const scoped = kernel.scoped().context(AppContext, {
// Next.js cookies() returns a promise in some versions
cookies: cookies()
})

Vla will automatically await the promise when you access it.

Use React’s cache() to create a scoped kernel per request:

src/data/kernel.ts
import { Kernel, Vla } from 'vla'
import { cache } from 'react'
import { cookies } from 'next/headers'
const kernel = new Kernel()
Vla.setInvokeKernelProvider(
cache(async () => {
return kernel.scoped().context(AppContext, {
cookies: await cookies()
})
})
)

Use the handle hook to provide context:

src/hooks.server.ts
import { Vla } from 'vla'
import type { Handle } from '@sveltejs/kit'
import { kernel } from './lib/data/kernel'
export const handle: Handle = async ({ event, resolve }) => {
const scoped = kernel.scoped().context(RequestContext, {
cookies: event.cookies,
headers: event.request.headers
})
return Vla.withKernel(scoped, () => resolve(event))
}

Use middleware to provide context:

import express from 'express'
import { Vla } from 'vla'
import { kernel } from './data/kernel'
const app = express()
app.use((req, res, next) => {
const scoped = kernel.scoped().context(RequestContext, {
cookies: req.cookies,
headers: req.headers,
ip: req.ip
})
Vla.withKernel(scoped, () => next())
})

Create a session service that uses context:

const AppContext = Vla.createContext<{
cookies: Record<string, string>
}>()
class SessionService extends Vla.Service {
ctx = this.inject(AppContext)
userRepo = this.inject(UserRepo)
async currentUser() {
const userId = this.ctx.cookies['session_id']
if (!userId) return null
return this.userRepo.findById(userId)
}
async requireAuth() {
const user = await this.currentUser()
if (!user) throw new Error('Authentication required')
return user
}
}
// Use in other services
class PostService extends Vla.Service {
session = this.inject(SessionService)
repo = this.inject(PostRepo)
async createPost(data: PostData) {
const user = await this.session.requireAuth()
return this.repo.create({
...data,
authorId: user.id
})
}
}

Use context to provide feature flag information:

const FeatureContext = Vla.createContext<{
flags: Record<string, boolean>
}>()
class FeatureService extends Vla.Service {
ctx = this.inject(FeatureContext)
isEnabled(flag: string): boolean {
return this.ctx.flags[flag] ?? false
}
}
class PostService extends Vla.Service {
features = this.inject(FeatureService)
async createPost(data: PostData) {
if (this.features.isEnabled('rich-text')) {
// Handle rich text posts
} else {
// Handle plain text posts
}
}
}

Use context for request IDs and tracing:

const TraceContext = Vla.createContext<{
requestId: string
startTime: number
}>()
class Logger extends Vla.Service {
ctx = this.inject(TraceContext)
log(message: string, data?: any) {
console.log({
requestId: this.ctx.requestId,
duration: Date.now() - this.ctx.startTime,
message,
...data
})
}
}

You can also bind values directly to the kernel:

// Using context (recommended)
const scoped = kernel.scoped().context(AppContext, {
cookies: req.cookies
})
// Using bindValue (alternative)
const scoped = kernel.scoped()
scoped.bindValue(AppContext, {
cookies: req.cookies
})

Both approaches work, but .context() is more concise and clearer in intent.

Provide mock context in your tests:

import { test, expect } from 'vitest'
import { Kernel } from 'vla'
test('requires authentication', async () => {
const kernel = new Kernel()
kernel.context(AppContext, {
cookies: { session_id: 'test-user-id' }
})
const service = kernel.create(PostService)
await expect(service.createPost(data)).resolves.toBeTruthy()
})
test('throws when not authenticated', async () => {
const kernel = new Kernel()
kernel.context(AppContext, {
cookies: {} // No session
})
const service = kernel.create(PostService)
await expect(service.createPost(data)).rejects.toThrow()
})
  • Request headers and cookies
  • User session information
  • Request-specific configuration
  • Feature flags per request
  • Request tracing and logging metadata
  • Large objects (keep it lean)
  • Computed values (use services instead)
  • Database connections (use Resources)
  • Application configuration (use Resources)
// ✅ Good: Simple, focused context
const AuthContext = Vla.createContext<{
userId: string | null
}>()
// ❌ Bad: Too much responsibility
const AppContext = Vla.createContext<{
userId: string
userPermissions: string[]
featureFlags: Record<string, boolean>
config: AppConfig
logger: Logger
}>()

Instead, split into multiple contexts or use services.