[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7160DFKQCHQD70JS20MWG30":3,"skill-same:01M7160DFKQCHQD70JS20MWG30":80},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":48,"linkOnly":49,"markdownContent":50,"markdownBody":51,"frontMatter":52,"refName":54,"commitSha":55,"skillDirectory":56,"forksCount":14,"repositoryHtmlUrl":57,"repositoryHomepageUrl":6,"ownerHtmlUrl":58,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":59,"install":60,"resources":73},"01M7160DFKQCHQD70JS20MWG30","event-store-design",null,"Design and implement event stores for event-sourced systems. Use when building event sourcing infrastructure, choosing event store technologies, or implementing event persistence patterns.","sapsapshen\u002Fai-memory-router","sapsapshen","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F20347959?v=4","01M6XDQEF1DP50DKF37H81J3J5","shenmian","01M6XDQEJPE41Y3DT2763X8ZJN",0,"MIT","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcursor\u002Fevent-store-design\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],"event store design","event","design","event store","store","systems","2026","when","unknown","sourcing","for","implementing","patterns","community","use","persistence","event sourcing","infrastructure","implement","2026-11-10 10:12:30",false,"---\nname: event-store-design\ndescription: \"Design and implement event stores for event-sourced systems. Use when building event sourcing infrastructure, choosing event store technologies, or implementing event persistence patterns.\"\nrisk: unknown\nsource: community\ndate_added: \"2026-02-27\"\n---\n\n# Event Store Design\n\nComprehensive guide to designing event stores for event-sourced applications.\n\n## Do not use this skill when\n\n- The task is unrelated to event store design\n- You need a different domain or tool outside this scope\n\n## Instructions\n\n- Clarify goals, constraints, and required inputs.\n- Apply relevant best practices and validate outcomes.\n- Provide actionable steps and verification.\n- If detailed examples are required, open `resources\u002Fimplementation-playbook.md`.\n\n## Use this skill when\n\n- Designing event sourcing infrastructure\n- Choosing between event store technologies\n- Implementing custom event stores\n- Optimizing event storage and retrieval\n- Setting up event store schemas\n- Planning for event store scaling\n\n## Core Concepts\n\n### 1. Event Store Architecture\n\n```\n┌─────────────────────────────────────────────────────┐\n│                    Event Store                       │\n├─────────────────────────────────────────────────────┤\n│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │\n│  │   Stream 1   │  │   Stream 2   │  │   Stream 3   │ │\n│  │ (Aggregate)  │  │ (Aggregate)  │  │ (Aggregate)  │ │\n│  ├─────────────┤  ├─────────────┤  ├─────────────┤ │\n│  │ Event 1     │  │ Event 1     │  │ Event 1     │ │\n│  │ Event 2     │  │ Event 2     │  │ Event 2     │ │\n│  │ Event 3     │  │ ...         │  │ Event 3     │ │\n│  │ ...         │  │             │  │ Event 4     │ │\n│  └─────────────┘  └─────────────┘  └─────────────┘ │\n├─────────────────────────────────────────────────────┤\n│  Global Position: 1 → 2 → 3 → 4 → 5 → 6 → ...     │\n└─────────────────────────────────────────────────────┘\n```\n\n### 2. Event Store Requirements\n\n| Requirement       | Description                        |\n| ----------------- | ---------------------------------- |\n| **Append-only**   | Events are immutable, only appends |\n| **Ordered**       | Per-stream and global ordering     |\n| **Versioned**     | Optimistic concurrency control     |\n| **Subscriptions** | Real-time event notifications      |\n| **Idempotent**    | Handle duplicate writes safely     |\n\n## Technology Comparison\n\n| Technology       | Best For                  | Limitations                      |\n| ---------------- | ------------------------- | -------------------------------- |\n| **EventStoreDB** | Pure event sourcing       | Single-purpose                   |\n| **PostgreSQL**   | Existing Postgres stack   | Manual implementation            |\n| **Kafka**        | High-throughput streaming | Not ideal for per-stream queries |\n| **DynamoDB**     | Serverless, AWS-native    | Query limitations                |\n| **Marten**       | .NET ecosystems           | .NET specific                    |\n\n## Templates\n\n### Template 1: PostgreSQL Event Store Schema\n\n```sql\n-- Events table\nCREATE TABLE events (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    stream_id VARCHAR(255) NOT NULL,\n    stream_type VARCHAR(255) NOT NULL,\n    event_type VARCHAR(255) NOT NULL,\n    event_data JSONB NOT NULL,\n    metadata JSONB DEFAULT '{}',\n    version BIGINT NOT NULL,\n    global_position BIGSERIAL,\n    created_at TIMESTAMPTZ DEFAULT NOW(),\n\n    CONSTRAINT unique_stream_version UNIQUE (stream_id, version)\n);\n\n-- Index for stream queries\nCREATE INDEX idx_events_stream_id ON events(stream_id, version);\n\n-- Index for global subscription\nCREATE INDEX idx_events_global_position ON events(global_position);\n\n-- Index for event type queries\nCREATE INDEX idx_events_event_type ON events(event_type);\n\n-- Index for time-based queries\nCREATE INDEX idx_events_created_at ON events(created_at);\n\n-- Snapshots table\nCREATE TABLE snapshots (\n    stream_id VARCHAR(255) PRIMARY KEY,\n    stream_type VARCHAR(255) NOT NULL,\n    snapshot_data JSONB NOT NULL,\n    version BIGINT NOT NULL,\n    created_at TIMESTAMPTZ DEFAULT NOW()\n);\n\n-- Subscriptions checkpoint table\nCREATE TABLE subscription_checkpoints (\n    subscription_id VARCHAR(255) PRIMARY KEY,\n    last_position BIGINT NOT NULL DEFAULT 0,\n    updated_at TIMESTAMPTZ DEFAULT NOW()\n);\n```\n\n### Template 2: Python Event Store Implementation\n\n```python\nfrom dataclasses import dataclass, field\nfrom datetime import datetime\nfrom typing import Any, Optional, List\nfrom uuid import UUID, uuid4\nimport json\nimport asyncpg\n\n@dataclass\nclass Event:\n    stream_id: str\n    event_type: str\n    data: dict\n    metadata: dict = field(default_factory=dict)\n    event_id: UUID = field(default_factory=uuid4)\n    version: Optional[int] = None\n    global_position: Optional[int] = None\n    created_at: datetime = field(default_factory=datetime.utcnow)\n\n\nclass EventStore:\n    def __init__(self, pool: asyncpg.Pool):\n        self.pool = pool\n\n    async def append_events(\n        self,\n        stream_id: str,\n        stream_type: str,\n        events: List[Event],\n        expected_version: Optional[int] = None\n    ) -> List[Event]:\n        \"\"\"Append events to a stream with optimistic concurrency.\"\"\"\n        async with self.pool.acquire() as conn:\n            async with conn.transaction():\n                # Check expected version\n                if expected_version is not None:\n                    current = await conn.fetchval(\n                        \"SELECT MAX(version) FROM events WHERE stream_id = $1\",\n                        stream_id\n                    )\n                    current = current or 0\n                    if current != expected_version:\n                        raise ConcurrencyError(\n                            f\"Expected version {expected_version}, got {current}\"\n                        )\n\n                # Get starting version\n                start_version = await conn.fetchval(\n                    \"SELECT COALESCE(MAX(version), 0) + 1 FROM events WHERE stream_id = $1\",\n                    stream_id\n                )\n\n                # Insert events\n                saved_events = []\n                for i, event in enumerate(events):\n                    event.version = start_version + i\n                    row = await conn.fetchrow(\n                        \"\"\"\n                        INSERT INTO events (id, stream_id, stream_type, event_type,\n                                          event_data, metadata, version, created_at)\n                        VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n                        RETURNING global_position\n                        \"\"\",\n                        event.event_id,\n                        stream_id,\n                        stream_type,\n                        event.event_type,\n                        json.dumps(event.data),\n                        json.dumps(event.metadata),\n                        event.version,\n                        event.created_at\n                    )\n                    event.global_position = row['global_position']\n                    saved_events.append(event)\n\n                return saved_events\n\n    async def read_stream(\n        self,\n        stream_id: str,\n        from_version: int = 0,\n        limit: int = 1000\n    ) -> List[Event]:\n        \"\"\"Read events from a stream.\"\"\"\n        async with self.pool.acquire() as conn:\n            rows = await conn.fetch(\n                \"\"\"\n                SELECT id, stream_id, event_type, event_data, metadata,\n                       version, global_position, created_at\n                FROM events\n                WHERE stream_id = $1 AND version >= $2\n                ORDER BY version\n                LIMIT $3\n                \"\"\",\n                stream_id, from_version, limit\n            )\n            return [self._row_to_event(row) for row in rows]\n\n    async def read_all(\n        self,\n        from_position: int = 0,\n        limit: int = 1000\n    ) -> List[Event]:\n        \"\"\"Read all events globally.\"\"\"\n        async with self.pool.acquire() as conn:\n            rows = await conn.fetch(\n                \"\"\"\n                SELECT id, stream_id, event_type, event_data, metadata,\n                       version, global_position, created_at\n                FROM events\n                WHERE global_position > $1\n                ORDER BY global_position\n                LIMIT $2\n                \"\"\",\n                from_position, limit\n            )\n            return [self._row_to_event(row) for row in rows]\n\n    async def subscribe(\n        self,\n        subscription_id: str,\n        handler,\n        from_position: int = 0,\n        batch_size: int = 100\n    ):\n        \"\"\"Subscribe to all events from a position.\"\"\"\n        # Get checkpoint\n        async with self.pool.acquire() as conn:\n            checkpoint = await conn.fetchval(\n                \"\"\"\n                SELECT last_position FROM subscription_checkpoints\n                WHERE subscription_id = $1\n                \"\"\",\n                subscription_id\n            )\n            position = checkpoint or from_position\n\n        while True:\n            events = await self.read_all(position, batch_size)\n            if not events:\n                await asyncio.sleep(1)  # Poll interval\n                continue\n\n            for event in events:\n                await handler(event)\n                position = event.global_position\n\n            # Save checkpoint\n            async with self.pool.acquire() as conn:\n                await conn.execute(\n                    \"\"\"\n                    INSERT INTO subscription_checkpoints (subscription_id, last_position)\n                    VALUES ($1, $2)\n                    ON CONFLICT (subscription_id)\n                    DO UPDATE SET last_position = $2, updated_at = NOW()\n                    \"\"\",\n                    subscription_id, position\n                )\n\n    def _row_to_event(self, row) -> Event:\n        return Event(\n            event_id=row['id'],\n            stream_id=row['stream_id'],\n            event_type=row['event_type'],\n            data=json.loads(row['event_data']),\n            metadata=json.loads(row['metadata']),\n            version=row['version'],\n            global_position=row['global_position'],\n            created_at=row['created_at']\n        )\n\n\nclass ConcurrencyError(Exception):\n    \"\"\"Raised when optimistic concurrency check fails.\"\"\"\n    pass\n```\n\n### Template 3: EventStoreDB Usage\n\n```python\nfrom esdbclient import EventStoreDBClient, NewEvent, StreamState\nimport json\n\n# Connect\nclient = EventStoreDBClient(uri=\"esdb:\u002F\u002Flocalhost:2113?tls=false\")\n\n# Append events\ndef append_events(stream_name: str, events: list, expected_revision=None):\n    new_events = [\n        NewEvent(\n            type=event['type'],\n            data=json.dumps(event['data']).encode(),\n            metadata=json.dumps(event.get('metadata', {})).encode()\n        )\n        for event in events\n    ]\n\n    if expected_revision is None:\n        state = StreamState.ANY\n    elif expected_revision == -1:\n        state = StreamState.NO_STREAM\n    else:\n        state = expected_revision\n\n    return client.append_to_stream(\n        stream_name=stream_name,\n        events=new_events,\n        current_version=state\n    )\n\n# Read stream\ndef read_stream(stream_name: str, from_revision: int = 0):\n    events = client.get_stream(\n        stream_name=stream_name,\n        stream_position=from_revision\n    )\n    return [\n        {\n            'type': event.type,\n            'data': json.loads(event.data),\n            'metadata': json.loads(event.metadata) if event.metadata else {},\n            'stream_position': event.stream_position,\n            'commit_position': event.commit_position\n        }\n        for event in events\n    ]\n\n# Subscribe to all\nasync def subscribe_to_all(handler, from_position: int = 0):\n    subscription = client.subscribe_to_all(commit_position=from_position)\n    async for event in subscription:\n        await handler({\n            'type': event.type,\n            'data': json.loads(event.data),\n            'stream_id': event.stream_name,\n            'position': event.commit_position\n        })\n\n# Category projection ($ce-Category)\ndef read_category(category: str):\n    \"\"\"Read all events for a category using system projection.\"\"\"\n    return read_stream(f\"$ce-{category}\")\n```\n\n### Template 4: DynamoDB Event Store\n\n```python\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nfrom datetime import datetime\nimport json\nimport uuid\n\nclass DynamoEventStore:\n    def __init__(self, table_name: str):\n        self.dynamodb = boto3.resource('dynamodb')\n        self.table = self.dynamodb.Table(table_name)\n\n    def append_events(self, stream_id: str, events: list, expected_version: int = None):\n        \"\"\"Append events with conditional write for concurrency.\"\"\"\n        with self.table.batch_writer() as batch:\n            for i, event in enumerate(events):\n                version = (expected_version or 0) + i + 1\n                item = {\n                    'PK': f\"STREAM#{stream_id}\",\n                    'SK': f\"VERSION#{version:020d}\",\n                    'GSI1PK': 'EVENTS',\n                    'GSI1SK': datetime.utcnow().isoformat(),\n                    'event_id': str(uuid.uuid4()),\n                    'stream_id': stream_id,\n                    'event_type': event['type'],\n                    'event_data': json.dumps(event['data']),\n                    'version': version,\n                    'created_at': datetime.utcnow().isoformat()\n                }\n                batch.put_item(Item=item)\n        return events\n\n    def read_stream(self, stream_id: str, from_version: int = 0):\n        \"\"\"Read events from a stream.\"\"\"\n        response = self.table.query(\n            KeyConditionExpression=Key('PK').eq(f\"STREAM#{stream_id}\") &\n                                  Key('SK').gte(f\"VERSION#{from_version:020d}\")\n        )\n        return [\n            {\n                'event_type': item['event_type'],\n                'data': json.loads(item['event_data']),\n                'version': item['version']\n            }\n            for item in response['Items']\n        ]\n\n# Table definition (CloudFormation\u002FTerraform)\n\"\"\"\nDynamoDB Table:\n  - PK (Partition Key): String\n  - SK (Sort Key): String\n  - GSI1PK, GSI1SK for global ordering\n\nCapacity: On-demand or provisioned based on throughput needs\n\"\"\"\n```\n\n## Best Practices\n\n### Do's\n\n- **Use stream IDs that include aggregate type** - `Order-{uuid}`\n- **Include correlation\u002Fcausation IDs** - For tracing\n- **Version events from day one** - Plan for schema evolution\n- **Implement idempotency** - Use event IDs for deduplication\n- **Index appropriately** - For your query patterns\n\n### Don'ts\n\n- **Don't update or delete events** - They're immutable facts\n- **Don't store large payloads** - Keep events small\n- **Don't skip optimistic concurrency** - Prevents data corruption\n- **Don't ignore backpressure** - Handle slow consumers\n\n## Resources\n\n- [EventStoreDB](https:\u002F\u002Fwww.eventstore.com\u002F)\n- [Marten Events](https:\u002F\u002Fmartendb.io\u002Fevents\u002F)\n- [Event Sourcing Pattern](https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fazure\u002Farchitecture\u002Fpatterns\u002Fevent-sourcing)\n","# Event Store Design\n\nComprehensive guide to designing event stores for event-sourced applications.\n\n## Do not use this skill when\n\n- The task is unrelated to event store design\n- You need a different domain or tool outside this scope\n\n## Instructions\n\n- Clarify goals, constraints, and required inputs.\n- Apply relevant best practices and validate outcomes.\n- Provide actionable steps and verification.\n- If detailed examples are required, open `resources\u002Fimplementation-playbook.md`.\n\n## Use this skill when\n\n- Designing event sourcing infrastructure\n- Choosing between event store technologies\n- Implementing custom event stores\n- Optimizing event storage and retrieval\n- Setting up event store schemas\n- Planning for event store scaling\n\n## Core Concepts\n\n### 1. Event Store Architecture\n\n```\n┌─────────────────────────────────────────────────────┐\n│                    Event Store                       │\n├─────────────────────────────────────────────────────┤\n│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │\n│  │   Stream 1   │  │   Stream 2   │  │   Stream 3   │ │\n│  │ (Aggregate)  │  │ (Aggregate)  │  │ (Aggregate)  │ │\n│  ├─────────────┤  ├─────────────┤  ├─────────────┤ │\n│  │ Event 1     │  │ Event 1     │  │ Event 1     │ │\n│  │ Event 2     │  │ Event 2     │  │ Event 2     │ │\n│  │ Event 3     │  │ ...         │  │ Event 3     │ │\n│  │ ...         │  │             │  │ Event 4     │ │\n│  └─────────────┘  └─────────────┘  └─────────────┘ │\n├─────────────────────────────────────────────────────┤\n│  Global Position: 1 → 2 → 3 → 4 → 5 → 6 → ...     │\n└─────────────────────────────────────────────────────┘\n```\n\n### 2. Event Store Requirements\n\n| Requirement       | Description                        |\n| ----------------- | ---------------------------------- |\n| **Append-only**   | Events are immutable, only appends |\n| **Ordered**       | Per-stream and global ordering     |\n| **Versioned**     | Optimistic concurrency control     |\n| **Subscriptions** | Real-time event notifications      |\n| **Idempotent**    | Handle duplicate writes safely     |\n\n## Technology Comparison\n\n| Technology       | Best For                  | Limitations                      |\n| ---------------- | ------------------------- | -------------------------------- |\n| **EventStoreDB** | Pure event sourcing       | Single-purpose                   |\n| **PostgreSQL**   | Existing Postgres stack   | Manual implementation            |\n| **Kafka**        | High-throughput streaming | Not ideal for per-stream queries |\n| **DynamoDB**     | Serverless, AWS-native    | Query limitations                |\n| **Marten**       | .NET ecosystems           | .NET specific                    |\n\n## Templates\n\n### Template 1: PostgreSQL Event Store Schema\n\n```sql\n-- Events table\nCREATE TABLE events (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    stream_id VARCHAR(255) NOT NULL,\n    stream_type VARCHAR(255) NOT NULL,\n    event_type VARCHAR(255) NOT NULL,\n    event_data JSONB NOT NULL,\n    metadata JSONB DEFAULT '{}',\n    version BIGINT NOT NULL,\n    global_position BIGSERIAL,\n    created_at TIMESTAMPTZ DEFAULT NOW(),\n\n    CONSTRAINT unique_stream_version UNIQUE (stream_id, version)\n);\n\n-- Index for stream queries\nCREATE INDEX idx_events_stream_id ON events(stream_id, version);\n\n-- Index for global subscription\nCREATE INDEX idx_events_global_position ON events(global_position);\n\n-- Index for event type queries\nCREATE INDEX idx_events_event_type ON events(event_type);\n\n-- Index for time-based queries\nCREATE INDEX idx_events_created_at ON events(created_at);\n\n-- Snapshots table\nCREATE TABLE snapshots (\n    stream_id VARCHAR(255) PRIMARY KEY,\n    stream_type VARCHAR(255) NOT NULL,\n    snapshot_data JSONB NOT NULL,\n    version BIGINT NOT NULL,\n    created_at TIMESTAMPTZ DEFAULT NOW()\n);\n\n-- Subscriptions checkpoint table\nCREATE TABLE subscription_checkpoints (\n    subscription_id VARCHAR(255) PRIMARY KEY,\n    last_position BIGINT NOT NULL DEFAULT 0,\n    updated_at TIMESTAMPTZ DEFAULT NOW()\n);\n```\n\n### Template 2: Python Event Store Implementation\n\n```python\nfrom dataclasses import dataclass, field\nfrom datetime import datetime\nfrom typing import Any, Optional, List\nfrom uuid import UUID, uuid4\nimport json\nimport asyncpg\n\n@dataclass\nclass Event:\n    stream_id: str\n    event_type: str\n    data: dict\n    metadata: dict = field(default_factory=dict)\n    event_id: UUID = field(default_factory=uuid4)\n    version: Optional[int] = None\n    global_position: Optional[int] = None\n    created_at: datetime = field(default_factory=datetime.utcnow)\n\n\nclass EventStore:\n    def __init__(self, pool: asyncpg.Pool):\n        self.pool = pool\n\n    async def append_events(\n        self,\n        stream_id: str,\n        stream_type: str,\n        events: List[Event],\n        expected_version: Optional[int] = None\n    ) -> List[Event]:\n        \"\"\"Append events to a stream with optimistic concurrency.\"\"\"\n        async with self.pool.acquire() as conn:\n            async with conn.transaction():\n                # Check expected version\n                if expected_version is not None:\n                    current = await conn.fetchval(\n                        \"SELECT MAX(version) FROM events WHERE stream_id = $1\",\n                        stream_id\n                    )\n                    current = current or 0\n                    if current != expected_version:\n                        raise ConcurrencyError(\n                            f\"Expected version {expected_version}, got {current}\"\n                        )\n\n                # Get starting version\n                start_version = await conn.fetchval(\n                    \"SELECT COALESCE(MAX(version), 0) + 1 FROM events WHERE stream_id = $1\",\n                    stream_id\n                )\n\n                # Insert events\n                saved_events = []\n                for i, event in enumerate(events):\n                    event.version = start_version + i\n                    row = await conn.fetchrow(\n                        \"\"\"\n                        INSERT INTO events (id, stream_id, stream_type, event_type,\n                                          event_data, metadata, version, created_at)\n                        VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n                        RETURNING global_position\n                        \"\"\",\n                        event.event_id,\n                        stream_id,\n                        stream_type,\n                        event.event_type,\n                        json.dumps(event.data),\n                        json.dumps(event.metadata),\n                        event.version,\n                        event.created_at\n                    )\n                    event.global_position = row['global_position']\n                    saved_events.append(event)\n\n                return saved_events\n\n    async def read_stream(\n        self,\n        stream_id: str,\n        from_version: int = 0,\n        limit: int = 1000\n    ) -> List[Event]:\n        \"\"\"Read events from a stream.\"\"\"\n        async with self.pool.acquire() as conn:\n            rows = await conn.fetch(\n                \"\"\"\n                SELECT id, stream_id, event_type, event_data, metadata,\n                       version, global_position, created_at\n                FROM events\n                WHERE stream_id = $1 AND version >= $2\n                ORDER BY version\n                LIMIT $3\n                \"\"\",\n                stream_id, from_version, limit\n            )\n            return [self._row_to_event(row) for row in rows]\n\n    async def read_all(\n        self,\n        from_position: int = 0,\n        limit: int = 1000\n    ) -> List[Event]:\n        \"\"\"Read all events globally.\"\"\"\n        async with self.pool.acquire() as conn:\n            rows = await conn.fetch(\n                \"\"\"\n                SELECT id, stream_id, event_type, event_data, metadata,\n                       version, global_position, created_at\n                FROM events\n                WHERE global_position > $1\n                ORDER BY global_position\n                LIMIT $2\n                \"\"\",\n                from_position, limit\n            )\n            return [self._row_to_event(row) for row in rows]\n\n    async def subscribe(\n        self,\n        subscription_id: str,\n        handler,\n        from_position: int = 0,\n        batch_size: int = 100\n    ):\n        \"\"\"Subscribe to all events from a position.\"\"\"\n        # Get checkpoint\n        async with self.pool.acquire() as conn:\n            checkpoint = await conn.fetchval(\n                \"\"\"\n                SELECT last_position FROM subscription_checkpoints\n                WHERE subscription_id = $1\n                \"\"\",\n                subscription_id\n            )\n            position = checkpoint or from_position\n\n        while True:\n            events = await self.read_all(position, batch_size)\n            if not events:\n                await asyncio.sleep(1)  # Poll interval\n                continue\n\n            for event in events:\n                await handler(event)\n                position = event.global_position\n\n            # Save checkpoint\n            async with self.pool.acquire() as conn:\n                await conn.execute(\n                    \"\"\"\n                    INSERT INTO subscription_checkpoints (subscription_id, last_position)\n                    VALUES ($1, $2)\n                    ON CONFLICT (subscription_id)\n                    DO UPDATE SET last_position = $2, updated_at = NOW()\n                    \"\"\",\n                    subscription_id, position\n                )\n\n    def _row_to_event(self, row) -> Event:\n        return Event(\n            event_id=row['id'],\n            stream_id=row['stream_id'],\n            event_type=row['event_type'],\n            data=json.loads(row['event_data']),\n            metadata=json.loads(row['metadata']),\n            version=row['version'],\n            global_position=row['global_position'],\n            created_at=row['created_at']\n        )\n\n\nclass ConcurrencyError(Exception):\n    \"\"\"Raised when optimistic concurrency check fails.\"\"\"\n    pass\n```\n\n### Template 3: EventStoreDB Usage\n\n```python\nfrom esdbclient import EventStoreDBClient, NewEvent, StreamState\nimport json\n\n# Connect\nclient = EventStoreDBClient(uri=\"esdb:\u002F\u002Flocalhost:2113?tls=false\")\n\n# Append events\ndef append_events(stream_name: str, events: list, expected_revision=None):\n    new_events = [\n        NewEvent(\n            type=event['type'],\n            data=json.dumps(event['data']).encode(),\n            metadata=json.dumps(event.get('metadata', {})).encode()\n        )\n        for event in events\n    ]\n\n    if expected_revision is None:\n        state = StreamState.ANY\n    elif expected_revision == -1:\n        state = StreamState.NO_STREAM\n    else:\n        state = expected_revision\n\n    return client.append_to_stream(\n        stream_name=stream_name,\n        events=new_events,\n        current_version=state\n    )\n\n# Read stream\ndef read_stream(stream_name: str, from_revision: int = 0):\n    events = client.get_stream(\n        stream_name=stream_name,\n        stream_position=from_revision\n    )\n    return [\n        {\n            'type': event.type,\n            'data': json.loads(event.data),\n            'metadata': json.loads(event.metadata) if event.metadata else {},\n            'stream_position': event.stream_position,\n            'commit_position': event.commit_position\n        }\n        for event in events\n    ]\n\n# Subscribe to all\nasync def subscribe_to_all(handler, from_position: int = 0):\n    subscription = client.subscribe_to_all(commit_position=from_position)\n    async for event in subscription:\n        await handler({\n            'type': event.type,\n            'data': json.loads(event.data),\n            'stream_id': event.stream_name,\n            'position': event.commit_position\n        })\n\n# Category projection ($ce-Category)\ndef read_category(category: str):\n    \"\"\"Read all events for a category using system projection.\"\"\"\n    return read_stream(f\"$ce-{category}\")\n```\n\n### Template 4: DynamoDB Event Store\n\n```python\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nfrom datetime import datetime\nimport json\nimport uuid\n\nclass DynamoEventStore:\n    def __init__(self, table_name: str):\n        self.dynamodb = boto3.resource('dynamodb')\n        self.table = self.dynamodb.Table(table_name)\n\n    def append_events(self, stream_id: str, events: list, expected_version: int = None):\n        \"\"\"Append events with conditional write for concurrency.\"\"\"\n        with self.table.batch_writer() as batch:\n            for i, event in enumerate(events):\n                version = (expected_version or 0) + i + 1\n                item = {\n                    'PK': f\"STREAM#{stream_id}\",\n                    'SK': f\"VERSION#{version:020d}\",\n                    'GSI1PK': 'EVENTS',\n                    'GSI1SK': datetime.utcnow().isoformat(),\n                    'event_id': str(uuid.uuid4()),\n                    'stream_id': stream_id,\n                    'event_type': event['type'],\n                    'event_data': json.dumps(event['data']),\n                    'version': version,\n                    'created_at': datetime.utcnow().isoformat()\n                }\n                batch.put_item(Item=item)\n        return events\n\n    def read_stream(self, stream_id: str, from_version: int = 0):\n        \"\"\"Read events from a stream.\"\"\"\n        response = self.table.query(\n            KeyConditionExpression=Key('PK').eq(f\"STREAM#{stream_id}\") &\n                                  Key('SK').gte(f\"VERSION#{from_version:020d}\")\n        )\n        return [\n            {\n                'event_type': item['event_type'],\n                'data': json.loads(item['event_data']),\n                'version': item['version']\n            }\n            for item in response['Items']\n        ]\n\n# Table definition (CloudFormation\u002FTerraform)\n\"\"\"\nDynamoDB Table:\n  - PK (Partition Key): String\n  - SK (Sort Key): String\n  - GSI1PK, GSI1SK for global ordering\n\nCapacity: On-demand or provisioned based on throughput needs\n\"\"\"\n```\n\n## Best Practices\n\n### Do's\n\n- **Use stream IDs that include aggregate type** - `Order-{uuid}`\n- **Include correlation\u002Fcausation IDs** - For tracing\n- **Version events from day one** - Plan for schema evolution\n- **Implement idempotency** - Use event IDs for deduplication\n- **Index appropriately** - For your query patterns\n\n### Don'ts\n\n- **Don't update or delete events** - They're immutable facts\n- **Don't store large payloads** - Keep events small\n- **Don't skip optimistic concurrency** - Prevents data corruption\n- **Don't ignore backpressure** - Handle slow consumers\n\n## Resources\n\n- [EventStoreDB](https:\u002F\u002Fwww.eventstore.com\u002F)\n- [Marten Events](https:\u002F\u002Fmartendb.io\u002Fevents\u002F)\n- [Event Sourcing Pattern](https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fazure\u002Farchitecture\u002Fpatterns\u002Fevent-sourcing)\n",{"name":5,"risk":37,"source":42,"date_added":53,"description":7},"2026-02-27","main","7a38d8e7073e61c16eda85681cb32fb68a8657a5","skills-store\u002Fcursor\u002Fevent-store-design","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router","https:\u002F\u002Fgithub.com\u002Fsapsapshen","2026-04-14 21:12:12",{"source":8,"skillName":5,"commands":61,"prompt":71,"zipAvailable":72},[62,65,68],{"tool":63,"command":64},"npx","npx skills add sapsapshen\u002Fai-memory-router --skill event-store-design",{"tool":66,"command":67},"bunx","bunx skills add sapsapshen\u002Fai-memory-router --skill event-store-design",{"tool":69,"command":70},"pnpm","pnpm dlx skills add sapsapshen\u002Fai-memory-router --skill event-store-design","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcursor\u002Fevent-store-design\u002FSKILL.md",true,[74],{"path":75,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":78,"sourceHtmlUrl":79},"resources\u002Fimplementation-playbook.md","REFERENCE","参考文档",746,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcursor\u002Fevent-store-design\u002Fresources\u002Fimplementation-playbook.md",[81,102,127,137,150,167],{"skillNo":82,"name":83,"nameZh":6,"description":84,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":90,"publishedAt":48},"01M71ATQRKG48SH06FQ697T5G9","wechat-search","Search WeChat Official Account articles using OpenClaw's web search and fetch capabilities with compliance-focused design.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fwechat-search-release\u002FSKILL.md",[87],{"categoryNo":88,"categoryName":89,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000037","律师与法务",[91,92,93,94,95,31,96,97,98,99,100,101],"wechat search","wechat","search","capabilities","openclaw","web search","compliance","web","official","fetch","with",{"skillNo":103,"name":104,"nameZh":6,"description":105,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":106,"categories":107,"keywords":108,"publishedAt":48},"01M71ATNAVGZB35PFYM2GC5N00","trend-tap","Real-time trending topics aggregator across 7 platforms (X\u002FTwitter, Reddit, Google Trends, Hacker News, Zhihu, Bilibili, Weibo). Trigger: when user says 'trend', 'trends', 'trend tap', '热点', '热搜', or asks what is trending\u002Fhot. Do NOT use web_search — this skill already fetches live data from all platforms with zero API keys.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftrend-tap\u002FSKILL.md",[],[109,110,111,112,113,114,93,115,116,117,118,36,119,120,121,122,123,124,125,126],"trend","trend tap","tap","api keys","Bash","time","trending","hacker","aggregator","do","brew","live","data","google","install","read","across","api",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":133,"publishedAt":48},"01M71ATJ9AM5KM15AA8PS302EB","trend-radar","Real-time trending topics aggregator across 7 platforms (X\u002FTwitter, Reddit, Google Trends, Hacker News, Zhihu, Bilibili, Weibo). Trigger: when user says 'trend', 'trends', 'trend pulse', '热点', '热搜', or asks what is trending\u002Fhot. Do NOT use web_search — this skill already fetches live data from all platforms with zero API keys.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftrend-radar\u002FSKILL.md",[],[134,135,109,119,120,121,122,113,93,124,136,125,126,36,117,116,115,123,114,112],"trend radar","radar","pulse",{"skillNo":138,"name":139,"nameZh":6,"description":140,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":141,"categories":142,"keywords":143,"publishedAt":48},"01M71ATGF095N3EJM9G8SD0BB5","tech-news-daily","每日科技新闻汇——从全球多个 RSS 源抓取最新科技新闻，翻译（英→中）、去重、排版成每日简报。当用户询问科技新闻、科技新鲜事、每日科技、技术动态、Daily Tech News 或类似表达时触发此技能。","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftech-news-daily\u002FSKILL.md",[],[144,145,146,147,148,149],"news","tech","tech news daily","daily","rss","翻译",{"skillNo":151,"name":152,"nameZh":6,"description":153,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":154,"categories":155,"keywords":159,"publishedAt":48},"01M71ATFAEAMQ17M70NQNY7SR8","summarize","Summarize URLs or files with the summarize CLI (web, PDFs, images, audio, YouTube).","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fsummarize\u002FSKILL.md",[156],{"categoryNo":157,"categoryName":158,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000021","资料检索与研究",[152,98,160,161,162,101,119,163,164,165,111,166,123],"Audio","cli","youtube","images","https","files","pdfs",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":171,"categories":172,"keywords":173,"publishedAt":48},"01M71ATDNS3R0M4H936PACQAWZ","sonoscli","Control Sonos speakers (discover\u002Fstatus\u002Fplay\u002Fvolume\u002Fgroup).","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fsonoscli\u002FSKILL.md",[],[169,174,175,176,123,177,164,178,179,180,181,182],"status","play","go","latest","github","discover","control","cmd","volume"]