Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Upcaster

ASecurity

Define a Protean event upcaster — a class that transforms old event payloads to match the current schema version, enabling event schema evolution without breaking stored events. Upcasters extend BaseUpcaster, implement an upcast(data) method, and are registered with @domain.upcaster(event_type=EventClass, from_version='v1', to_version='v2'). The framework automatically chains individual upcasters and applies them lazily during deserialization. Use when the user asks to 'create an upcaster', '...

45 stars
0 votes
0 copies
0 views
Added 9/20/2026
developmentpythongoapidatabase

Works with

terminalapi

Security Analysis

A100/100

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add proteanhq/protean --skill upcaster --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Upcaster?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Upcaster
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/proteanhq-upcaster/badge)](https://www.skillsdirectory.com/skills/proteanhq-upcaster)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: upcaster
description: "Define a Protean event upcaster — a class that transforms old event payloads to match the current schema version, enabling event schema evolution without breaking stored events. Upcasters extend BaseUpcaster, implement an upcast(data) method, and are registered with @domain.upcaster(event_type=EventClass, from_version='v1', to_version='v2'). The framework automatically chains individual upcasters and applies them lazily during deserialization. Use when the user asks to 'create an upcaster', 'handle event schema migration', 'evolve an event schema', 'add a field to an existing event', 'rename a field in an event', 'migrate old events', 'transform stored events', 'handle event versioning', or when they need to change an event's schema while keeping old stored events compatible."
license: Apache-2.0
compatibility: Requires Python 3.11+, protean framework
metadata:
  author: proteanhq
  version: "0.1"
  category: element
---

# Upcaster

## Basic structure

An upcaster transforms old event payloads to the current schema during deserialization. Define one by extending `BaseUpcaster` and implementing `upcast()`:

```python
from protean import Domain
from protean.core.upcaster import BaseUpcaster
from protean.fields import Float, Identifier, String

domain = Domain()

@domain.event(part_of="Order")
class OrderPlaced:
    __version__ = 2
    order_id = Identifier(required=True)
    amount = Float(required=True)
    currency = String(required=True)

@domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=2)
class UpcastOrderPlacedV1ToV2(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data["currency"] = "USD"
        return data
```

## Key rules

1. **Extend `BaseUpcaster`** — Import from `protean.core.upcaster`
2. **Register with `@domain.upcaster(event_type=..., from_version=..., to_version=...)`** — All three options are required
3. **Implement `upcast(self, data: dict) -> dict`** — Receives the raw event payload dict, returns the transformed dict
4. **`event_type` is always the CURRENT event class** — Not the old version. The upcaster knows which event it targets by its current definition
5. **Bump `__version__` on the event class** — Set `__version__ = 2` on the event when you add an upcaster targeting v2. Default version is `1`
6. **One upcaster per version step** — Write v1→v2 and v2→v3 separately. Never skip versions that existed in production
7. **Keep upcasters pure** — No I/O, no database queries, no external API calls. Upcasting runs on every deserialization and must be fast
8. **Chains build automatically** — Register individual steps; the framework chains them into v1→v2→v3 during `domain.init()`
9. **Validated at startup** — `domain.init()` detects duplicates, cycles, non-convergent chains, and missing event classes. All errors are caught at startup, never at runtime
10. **Works everywhere transparently** — Event-sourced aggregate reconstruction (`@apply`), event handlers (`@handle`), and projectors all receive upcast events
11. **Lazy, zero-overhead for current events** — Current-version events take a fast path (direct type-string lookup). The upcaster chain is only consulted for old-version type strings

## Common transformations

### Adding a new required field

```python
@domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=2)
class UpcastV1ToV2(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data["currency"] = "USD"  # All v1 orders were in USD
        return data
```

### Renaming a field

```python
@domain.upcaster(event_type=OrderPlaced, from_version=2, to_version=3)
class UpcastV2ToV3(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data["total_amount"] = data.pop("amount")
        return data
```

### Removing an obsolete field

```python
@domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=2)
class UpcastV1ToV2(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data.pop("legacy_code", None)
        return data
```

### Computing a derived field

```python
@domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=2)
class UpcastV1ToV2(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data["line_item_count"] = len(data.get("items", []))
        return data
```

### Restructuring data (flat → nested)

```python
@domain.upcaster(event_type=CustomerRegistered, from_version=1, to_version=2)
class UpcastV1ToV2(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data["address"] = {
            "street": data.pop("street", ""),
            "city": data.pop("city", ""),
            "zip_code": data.pop("zip_code", ""),
        }
        return data
```

## Multi-step chains

When an event evolves through multiple versions, register one upcaster per step. The framework chains them automatically:

```python
@domain.event(part_of="Order")
class OrderPlaced:
    __version__ = 3
    order_id = Identifier(required=True)
    total_amount = Float(required=True)
    currency = String(required=True)

# v1 → v2: add currency
@domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=2)
class UpcastV1ToV2(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data["currency"] = "USD"
        return data

# v2 → v3: rename amount → total_amount
@domain.upcaster(event_type=OrderPlaced, from_version=2, to_version=3)
class UpcastV2ToV3(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data["total_amount"] = data.pop("amount")
        return data
```

A stored v1 event passes through both: v1→v2→v3. A stored v2 event passes through only v2→v3. A v3 event skips upcasting entirely.

## With event-sourced aggregates

Upcasting is especially valuable for ES aggregates because every reconstruction replays all events. With upcasters, `@apply` handlers only handle the current schema:

```python
@domain.aggregate(is_event_sourced=True)
class Order:
    order_id = Identifier(identifier=True)
    total_amount = Float()
    currency = String()

    @apply
    def on_placed(self, event: OrderPlaced):
        # Always receives current v3 schema — upcasters handle old versions
        self.total_amount = event.total_amount
        self.currency = event.currency
```

## With event handlers and projectors

Upcasting also applies to asynchronous event processing. Old events are upcast before reaching `@handle`:

```python
@domain.event_handler(part_of=Analytics)
class AnalyticsHandler:
    @handle(OrderPlaced)
    def on_order_placed(self, event: OrderPlaced):
        # Always receives current schema, even for historical replays
        record_revenue(event.total_amount, event.currency)
```

## Common mistakes

### Skipping versions in chains

```python
# WRONG — if v2 existed in production, you need v1→v2 AND v2→v3
@domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=3)
class SkipV2(BaseUpcaster): ...

# CORRECT — one step per version
@domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=2)
class V1ToV2(BaseUpcaster): ...

@domain.upcaster(event_type=OrderPlaced, from_version=2, to_version=3)
class V2ToV3(BaseUpcaster): ...
```

### Performing I/O in upcast()

```python
# WRONG — upcasting runs on every deserialization
class SlowUpcaster(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        user = db.query(User, data["user_id"])  # NO! No I/O
        data["user_name"] = user.name
        return data

# CORRECT — pure dict transformation only
class FastUpcaster(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data["user_name"] = data.get("user_name", "Unknown")
        return data
```

### Pointing event_type at old class

```python
# WRONG — event_type must be the CURRENT event class
@domain.upcaster(event_type=OrderPlacedV1, from_version=1, to_version=2)

# CORRECT — always point to the current class
@domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=2)
```

### Forgetting to bump __version__

```python
# WRONG — event still at default v1, but upcaster targets v2
@domain.event(part_of="Order")
class OrderPlaced:
    # __version__ not set — defaults to `1`
    ...

# CORRECT — set __version__ to match the upcaster chain's terminal version
@domain.event(part_of="Order")
class OrderPlaced:
    __version__ = 2
    ...
```

See [Anti-patterns](references/anti-patterns.md) for more.

## Detailed references

### Core Concepts
- [Upcaster Chain](references/upcaster-chain.md) — How chain building, validation, and runtime application work
- [Anti-patterns](references/anti-patterns.md) — Common mistakes and how to avoid them
- [When to Upcast](references/when-to-upcast.md) — Decision guide: upcasting vs. new event type vs. no action

### Complete Examples
- [Basic Upcasters](assets/upcaster_basic.py) — Add field, rename field, remove field, compute derived field
- [Multi-Step Chain](assets/upcaster_multi_step_chain.py) — Event evolving through 4 versions with automatic chaining
- [With ES Aggregate](assets/upcaster_with_es_aggregate.py) — Clean @apply handlers with transparent upcasting
- [With Event Handler](assets/upcaster_with_event_handler.py) — Event handler and projector receiving upcast events

### Related Skills
- `event` — Event definition, `__version__`, naming conventions
- `event-sourced-aggregate` — `@apply` decorator, `from_events()`, ES repository
- `event-handler` — `@handle` decorator, event handler structure
- `projector` — `@handle`/`@on` decorator, projector structure

Attribution

proteanhqproteanhq
View sourceMore from proteanhq →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

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 (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

281612 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2132 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →