Skip to content
Back to skills

Projections

ASecurity

Build or query TES projections (current state views). State is derived from TES events - projections are the queryable current truth.

  • 57 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 17, 2026
developmentjavascriptjavaapi

Works with

  • api

Security analysis

A100/100

Scanned October 17, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill projections --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Projections?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Projections
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-projections/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-projections)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: projections
description: Build or query TES projections (current state views). State is derived from TES events - projections are the queryable current truth.
allowed-tools: Read, Grep, Glob, Edit, Write
---

# Projections Skill

## Core Principle

**State is derived from TES events. Projections are the queryable current truth.**

---

## Projection Types

| Projection | Purpose | Key |
|------------|---------|-----|
| ItemCurrentState | Current state of an item | item canonical_id |
| ListingCurrentState | Current listing status | listing canonical_id |
| ParticipantInventory | What a user owns | participant canonical_id |
| CampaignState | Campaign metrics | campaign canonical_id |

---

## Durable Object Pattern

```javascript
// workers/projections/ItemProjection.js

export class ItemProjection {
  constructor(state, env) {
    this.state = state;
    this.env = env;
  }

  async fetch(request) {
    const url = new URL(request.url);

    if (request.method === "GET") {
      const currentState = await this.state.storage.get("state");
      return Response.json(currentState || { error: "Not found" });
    }

    if (request.method === "POST" && url.pathname === "/apply-event") {
      const event = await request.json();
      return this.applyEvent(event);
    }
  }

  async applyEvent(event) {
    // Idempotency check
    const processedEvents = await this.state.storage.get("processed") || [];
    if (processedEvents.includes(event.id)) {
      return Response.json({ status: "already_processed" });
    }

    // Get current state
    let state = await this.state.storage.get("state") || this.initialState();

    // Apply event
    state = this.reduce(state, event);
    state.last_event_id = event.id;
    state.last_updated = event.timestamp;

    // Save
    await this.state.storage.put("state", state);
    await this.state.storage.put("processed", [...processedEvents.slice(-100), event.id]);

    return Response.json({ status: "applied", state });
  }

  reduce(state, event) {
    switch (event.type) {
      case "item.captured":
        return { ...state, status: "captured", media: event.payload.image_urls };
      case "item.identified":
        return { ...state, status: "identified", product_ref: event.payload.product_ref };
      case "item.listed":
        return { ...state, status: "listed", listing_id: event.payload.listing_id };
      case "item.sold":
        return { ...state, status: "sold", sold_at: event.timestamp };
      default:
        return state;
    }
  }

  initialState() {
    return { status: "unknown", created_at: new Date().toISOString() };
  }
}
```

---

## Querying Projections

```javascript
// Get current item state
async function getItemState(env, itemId) {
  const doId = env.ITEM_PROJECTION.idFromName(itemId);
  const stub = env.ITEM_PROJECTION.get(doId);

  const response = await stub.fetch(new Request("https://do/"));
  return response.json();
}

// In API handler
const itemState = await getItemState(env, "ptc_item_abc123");
```

---

## TES Router (Event Fan-out)

```javascript
// workers/tes-router.js

export default {
  async queue(batch, env) {
    for (const message of batch.messages) {
      const event = message.body;

      // Route to appropriate projections
      const routes = getRoutes(event);

      await Promise.all(routes.map(async (route) => {
        const doId = env[route.binding].idFromName(route.key);
        const stub = env[route.binding].get(doId);

        await stub.fetch(new Request("https://do/apply-event", {
          method: "POST",
          body: JSON.stringify(event),
        }));
      }));

      message.ack();
    }
  },
};

function getRoutes(event) {
  const routes = [];

  if (event.entity_type === "item") {
    routes.push({ binding: "ITEM_PROJECTION", key: event.entity_id });
  }

  if (event.type === "item.sold") {
    routes.push({ binding: "LISTING_PROJECTION", key: event.payload.listing_id });
    routes.push({ binding: "PARTICIPANT_PROJECTION", key: event.payload.seller_id });
    routes.push({ binding: "PARTICIPANT_PROJECTION", key: event.payload.buyer_id });
  }

  return routes;
}
```

---

## Anti-Patterns

- Querying D1 for "current truth" instead of projections
- Mutating projection state without event
- Global projections (hot-key bottleneck)
- Not handling idempotency

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.