Context Types
Package: @tagna/udiot v1.0.0-rc.1
Source: src/middleware/context.ts
Import:
import type {
BaseContext,
HttpContext,
StateContext,
EventContext,
GenericContext
} from '@tagna/udiot';
import {
createHttpContext,
createStateContext,
createEventContext,
createGenericContext
} from '@tagna/udiot';
BaseContext
Every context in UDIOT extends BaseContext. This guarantees a consistent shape that the pipeline engine and all middleware can rely on.
interface BaseContext {
metadata: Map<string, any>; // Key-value store for middleware to share data
timestamp: number; // Unix ms timestamp when the context was created
result?: any; // Set by middleware; becomes PipelineResult.result
}
metadata is the primary communication channel between middleware that do not share a direct reference. For example, loggingMiddleware writes a requestId to metadata; downstream middleware can read it with ctx.metadata.get('requestId').
result is the output slot. When any middleware sets ctx.result, that value is returned as PipelineResult.result after the pipeline completes. The last middleware to write wins.
HttpContext
Used for HTTP request/response middleware. Typically created from an incoming request object.
interface HttpContext extends BaseContext {
request: Request; // The incoming request object
response: Response; // The outgoing response object
params: Record<string, any>; // Path parameters (e.g., /users/:id → { id: '42' })
query: Record<string, any>; // Query string parameters
body?: any; // Parsed request body
headers: Record<string, string>; // Request headers (lowercase keys)
user?: any; // Set by authentication middleware
startTime: number; // Unix ms timestamp (same as timestamp for HTTP)
}
Factory: createHttpContext
function createHttpContext(req: Request, res: Response): HttpContext
Constructs an HttpContext from a request and response pair. Copies req.params, req.query, req.body, and req.headers into the context. Initializes metadata to a new Map and sets timestamp and startTime to Date.now().
import { createHttpContext, MiddlewarePipeline } from '@tagna/udiot';
import type { HttpContext } from '@tagna/udiot';
// In an Express route handler:
app.use(async (req, res) => {
const ctx = createHttpContext(req as any, res as any);
const result = await pipeline.execute(ctx);
res.json(result.result);
});
Reading user from HttpContext
authMiddleware sets ctx.user when authentication succeeds. Read it in subsequent middleware:
const requireRole = (role: string): MiddlewareFunction<HttpContext> =>
async (ctx, next) => {
if (!ctx.user?.roles?.includes(role)) {
ctx.response.status(403);
ctx.result = { error: 'Forbidden' };
return;
}
await next();
};
StateContext<TState>
Used for middleware that intercepts state management actions — analogous to Redux middleware.
interface StateContext<TState = any> extends BaseContext {
store: Store<TState>; // The state store
action: Action; // The dispatched action ({ type: string; payload?: any })
prevState: TState; // Snapshot of state captured at context creation time
}
prevState is captured from store.getState() at the moment createStateContext is called, before any middleware runs. Middleware can compare ctx.store.getState() against ctx.prevState to detect what changed.
Factory: createStateContext
function createStateContext<TState>(
store: Store<TState>,
action: Action
): StateContext<TState>
import { createStateContext, MiddlewarePipeline } from '@tagna/udiot';
import type { StateContext } from '@tagna/udiot';
interface AppState { count: number }
const statePipeline = new MiddlewarePipeline<StateContext<AppState>>();
statePipeline.use(async (ctx, next) => {
console.log('Action:', ctx.action.type);
console.log('Before:', ctx.prevState);
await next();
console.log('After:', ctx.store.getState());
});
// When dispatching:
const ctx = createStateContext(store, { type: 'INCREMENT', payload: 1 });
await statePipeline.execute(ctx);
EventContext
Used for middleware triggered by browser or custom events.
interface EventContext extends BaseContext {
event: Event | CustomEvent; // The DOM event or custom event
source: any; // The emitter or element that fired the event
}
Factory: createEventContext
function createEventContext(
event: Event | CustomEvent,
source?: any
): EventContext
import { createEventContext, MiddlewarePipeline } from '@tagna/udiot';
import type { EventContext } from '@tagna/udiot';
const eventPipeline = new MiddlewarePipeline<EventContext>();
eventPipeline.use(async (ctx, next) => {
// Capture analytics before the event is processed
analytics.track(ctx.event.type, { source: ctx.source?.id });
await next();
});
document.addEventListener('click', async (event) => {
const ctx = createEventContext(event, event.target);
await eventPipeline.execute(ctx);
});
GenericContext
A flexible context for any workload that does not fit the HTTP, state, or event shapes.
interface GenericContext extends BaseContext {
data: any; // Arbitrary payload
type?: string; // Optional label for the kind of work
}
Factory: createGenericContext
function createGenericContext(data: any, type?: string): GenericContext
import { createGenericContext, MiddlewarePipeline } from '@tagna/udiot';
import type { GenericContext } from '@tagna/udiot';
const jobPipeline = new MiddlewarePipeline<GenericContext>();
jobPipeline.use(async (ctx, next) => {
console.log(`Processing job [${ctx.type}]:`, ctx.data);
await next();
});
// Running a background job:
const ctx = createGenericContext({ userId: 42, action: 'sendWelcomeEmail' }, 'email-job');
await jobPipeline.execute(ctx);
Using the metadata Map
metadata is designed for middleware-to-middleware communication within a single pipeline execution. Common patterns:
// Upstream middleware writes:
ctx.metadata.set('requestId', crypto.randomUUID());
ctx.metadata.set('tenantId', ctx.headers['x-tenant-id']);
// Downstream middleware reads:
const requestId = ctx.metadata.get('requestId') as string;
const tenantId = ctx.metadata.get('tenantId') as string;
loggingMiddleware uses metadata to propagate requestId, traceId, and spanId to downstream middleware for structured log correlation.
Custom Context
Extend BaseContext to add domain-specific fields:
import type { BaseContext, MiddlewareFunction } from '@tagna/udiot';
interface TenantContext extends BaseContext {
tenantId: string;
plan: 'free' | 'pro' | 'enterprise';
features: Set<string>;
}
function createTenantContext(tenantId: string, plan: TenantContext['plan']): TenantContext {
return {
tenantId,
plan,
features: new Set(planFeatures[plan]),
metadata: new Map(),
timestamp: Date.now()
};
}
const tenantPipeline = new MiddlewarePipeline<TenantContext>();
const featureGuard = (feature: string): MiddlewareFunction<TenantContext> =>
async (ctx, next) => {
if (!ctx.features.has(feature)) {
ctx.result = { error: `Feature '${feature}' not available on ${ctx.plan} plan` };
return;
}
await next();
};
tenantPipeline
.use(featureGuard('advanced-reporting'))
.use(reportingHandler);
Context Integration Summary
| Context type | Pipeline type | State flow |
|---|---|---|
HttpContext |
HTTP middleware pipeline | Flows through auth, rate limiting, validation, business logic, response assembly |
StateContext |
State middleware pipeline | Dispatched action flows through logger, validator, side-effect handlers |
EventContext |
Event middleware pipeline | DOM event flows through analytics capture, permission checks, handlers |
GenericContext |
Job / worker pipeline | Job payload flows through retry guards, timeout wrappers, processors |