[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Streaming Data Pipeline

Build a Streaming Data Pipeline

Build a streaming data pipeline with event ingestion, real-time transformations, windowed aggregations, dead letter queues, backpressure handling, and monitoring for high-throughput data processing.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 96/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Viktor leads data at a 25-person analytics company ingesting 50M events/day from customer websites. Their batch ETL runs nightly — dashboards show yesterday's data. Customers want real-time analytics: "how many users are on my site right now?" Batch processing misses spikes: a traffic surge at 2 PM shows up at midnight. Failed events are silently dropped. When a downstream consumer is slow, the entire pipeline backs up. They need streaming: real-time event ingestion, windowed aggregations, backpressure handling, dead letter queue for failures, and sub-second dashboard updates.

Step 1: Build the Streaming Pipeline

typescript
// src/pipeline/streaming.ts — Real-time event pipeline with windowed aggregation and DLQ
import { Redis } from "ioredis";
import { pool } from "../db";
import { randomBytes } from "node:crypto";

const redis = new Redis(process.env.REDIS_URL!);

interface PipelineEvent {
  id: string;
  type: string;
  source: string;
  timestamp: number;
  data: Record<string, any>;
}

interface PipelineStage {
  name: string;
  type: "transform" | "filter" | "aggregate" | "enrich" | "sink";
  handler: (event: PipelineEvent, context: PipelineContext) => Promise<PipelineEvent | PipelineEvent[] | null>;
  config: Record<string, any>;
}

interface PipelineContext {
  pipelineId: string;
  stageIndex: number;
  retryCount: number;
  metadata: Record<string, any>;
}

interface WindowAggregation {
  windowId: string;
  windowType: "tumbling" | "sliding" | "session";
  windowSize: number;         // milliseconds
  key: string;
  values: Map<string, number>;
  startTime: number;
  endTime: number;
  eventCount: number;
}

const pipelines = new Map<string, PipelineStage[]>();
const windows = new Map<string, WindowAggregation>();

// Register a pipeline
export function registerPipeline(id: string, stages: PipelineStage[]): void {
  pipelines.set(id, stages);
}

// Ingest events into the pipeline
export async function ingest(pipelineId: string, events: PipelineEvent[]): Promise<{
  accepted: number; rejected: number; processingTimeMs: number;
}> {
  const start = Date.now();
  const stages = pipelines.get(pipelineId);
  if (!stages) throw new Error(`Pipeline '${pipelineId}' not found`);

  let accepted = 0;
  let rejected = 0;

  // Check backpressure
  const queueSize = await redis.llen(`pipeline:queue:${pipelineId}`);
  if (queueSize > 100000) {
    // Apply backpressure — reject with retry-after
    throw new BackpressureError(`Pipeline queue full: ${queueSize} events pending`);
  }

  for (const event of events) {
    try {
      await processEvent(pipelineId, stages, event);
      accepted++;
    } catch (error: any) {
      // Send to dead letter queue
      await sendToDLQ(pipelineId, event, error.message);
      rejected++;
    }
  }

  // Update metrics
  await redis.hincrby(`pipeline:metrics:${pipelineId}`, "accepted", accepted);
  await redis.hincrby(`pipeline:metrics:${pipelineId}`, "rejected", rejected);

  return { accepted, rejected, processingTimeMs: Date.now() - start };
}

async function processEvent(
  pipelineId: string,
  stages: PipelineStage[],
  event: PipelineEvent
): Promise<void> {
  let currentEvents: PipelineEvent[] = [event];

  for (let i = 0; i < stages.length; i++) {
    const stage = stages[i];
    const nextEvents: PipelineEvent[] = [];

    for (const evt of currentEvents) {
      const ctx: PipelineContext = { pipelineId, stageIndex: i, retryCount: 0, metadata: {} };

      try {
        const result = await stage.handler(evt, ctx);
        if (result === null) continue;  // filtered out
        if (Array.isArray(result)) nextEvents.push(...result);
        else nextEvents.push(result);
      } catch (error: any) {
        // Retry with backoff
        let retried = false;
        for (let retry = 1; retry <= 3; retry++) {
          await sleep(retry * 100);
          try {
            ctx.retryCount = retry;
            const result = await stage.handler(evt, ctx);
            if (result !== null) {
              if (Array.isArray(result)) nextEvents.push(...result);
              else nextEvents.push(result);
            }
            retried = true;
            break;
          } catch {}
        }
        if (!retried) throw error;
      }
    }

    currentEvents = nextEvents;
    if (currentEvents.length === 0) break;
  }
}

// Windowed aggregation
export async function aggregateWindow(
  windowType: WindowAggregation["windowType"],
  windowSizeMs: number,
  event: PipelineEvent,
  groupKey: string,
  valueField: string,
  aggregation: "count" | "sum" | "avg" | "min" | "max"
): Promise<{ windowComplete: boolean; result?: Record<string, number> }> {
  const windowStart = Math.floor(event.timestamp / windowSizeMs) * windowSizeMs;
  const windowId = `${groupKey}:${windowStart}`;

  const key = `window:${windowId}`;
  const group = event.data[groupKey] || "default";
  const value = event.data[valueField] || 1;

  // Update window in Redis
  await redis.hincrby(key, `${group}:count`, 1);
  await redis.hincrbyfloat(key, `${group}:sum`, value);
  await redis.expire(key, Math.ceil(windowSizeMs / 1000) * 2);
  await redis.hincrby(key, "totalEvents", 1);

  // Check if window is complete (tumbling window)
  const windowEnd = windowStart + windowSizeMs;
  if (Date.now() >= windowEnd) {
    // Flush window
    const data = await redis.hgetall(key);
    const result: Record<string, number> = {};

    for (const [k, v] of Object.entries(data)) {
      if (k === "totalEvents") continue;
      const [groupName, metric] = k.split(":");
      if (aggregation === "count" && metric === "count") result[groupName] = parseInt(v);
      if (aggregation === "sum" && metric === "sum") result[groupName] = parseFloat(v);
      if (aggregation === "avg" && metric === "sum") {
        const count = parseInt(data[`${groupName}:count`] || "1");
        result[groupName] = parseFloat(v) / count;
      }
    }

    await redis.del(key);
    return { windowComplete: true, result };
  }

  return { windowComplete: false };
}

// Dead letter queue
async function sendToDLQ(pipelineId: string, event: PipelineEvent, error: string): Promise<void> {
  await redis.rpush(`pipeline:dlq:${pipelineId}`, JSON.stringify({
    event, error, failedAt: new Date().toISOString(),
  }));
  await redis.hincrby(`pipeline:metrics:${pipelineId}`, "dlqSize", 1);
}

// Replay DLQ events
export async function replayDLQ(pipelineId: string, limit: number = 100): Promise<{ replayed: number; failed: number }> {
  let replayed = 0, failed = 0;
  for (let i = 0; i < limit; i++) {
    const item = await redis.lpop(`pipeline:dlq:${pipelineId}`);
    if (!item) break;
    const { event } = JSON.parse(item);
    try {
      await ingest(pipelineId, [event]);
      replayed++;
    } catch {
      failed++;
      await redis.rpush(`pipeline:dlq:${pipelineId}`, item);  // put back
    }
  }
  return { replayed, failed };
}

// Pipeline monitoring
export async function getMetrics(pipelineId: string): Promise<{
  eventsAccepted: number; eventsRejected: number;
  dlqSize: number; queueDepth: number; throughputPerSecond: number;
}> {
  const stats = await redis.hgetall(`pipeline:metrics:${pipelineId}`);
  const queueDepth = await redis.llen(`pipeline:queue:${pipelineId}`);

  return {
    eventsAccepted: parseInt(stats.accepted || "0"),
    eventsRejected: parseInt(stats.rejected || "0"),
    dlqSize: parseInt(stats.dlqSize || "0"),
    queueDepth,
    throughputPerSecond: 0,
  };
}

function sleep(ms: number): Promise<void> {
  return new Promise((r) => setTimeout(r, ms));
}

export class BackpressureError extends Error {
  constructor(message: string) { super(message); this.name = "BackpressureError"; }
}

Results

  • Real-time analytics: next-day → sub-second — events processed as they arrive; "users online right now" dashboard updates every second; customers see live data
  • Windowed aggregations — "page views per URL in last 5 minutes" computed as tumbling windows; spike detection catches traffic surges immediately, not 10 hours later
  • Dead letter queue — failed events captured with error context; replay button retries when downstream is fixed; zero data loss vs previous silent drops
  • Backpressure handling — slow consumer triggers backpressure at 100K queue depth; producers get retry-after header; pipeline degrades gracefully instead of crashing
  • 50M events/day processed — Redis-backed pipeline handles 600 events/second sustained; horizontal scaling by adding consumer workers

Related use cases