Docs/Middlewarev1.0.0-rc.1

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
From the canonical UDIOT public documentation.Try it in the playground ↗