smithery/madooei

setup-events

Configure event-driven architecture with event emitter and base service. Use when adding real-time updates or event system. Triggers on "setup events", "add events", "event system", "SSE", "real-time".

Installation

$ npx skills add smithery/madooei --skill setup-events

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/madooei · top by installs.

npx skills add smithery/madooei

Browse all from smithery/madooei

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 8,726 B
  • docs SUMMARY.md 221 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Setup Events

Configures event-driven architecture infrastructure including an event emitter, base service class for event emission, and event schema.

Quick Reference

Files created:

  • src/events/event-emitter.ts - Central event hub
  • src/events/base.service.ts - Abstract service with event emission
  • src/schemas/event.schema.ts - Event type definitions

When to use: When you need real-time updates via SSE or event-driven architecture

Prerequisites

  • Project bootstrapped with bootstrap-project
  • uuid package installed

Instructions

Phase 1: Install Dependencies

Step 1: Install UUID Package

pnpm add uuid
pnpm add -D @types/uuid

Phase 2: Create Event Infrastructure

Step 2: Create Events Directory

mkdir -p src/events

Step 3: Create Event Emitter

Create src/events/event-emitter.ts:

import { EventEmitter } from "events";
import type { ServiceEventType } from "@/schemas/event.schema";

class AppEventEmitter extends EventEmitter {
  emitServiceEvent(serviceName: string, event: ServiceEventType) {
    this.emit(`${serviceName}:${event.action}`, event);
  }
}

export const appEvents = new AppEventEmitter();

Step 4: Create Base Service

Create src/events/base.service.ts:

import { appEvents } from "./event-emitter";
import type { ServiceEventType } from "@/schemas/event.schema";
import { v4 as uuidv4 } from "uuid";

export abstract class BaseService {
  constructor(protected serviceName: string) {}

  protected emitEvent<T>(
    action: ServiceEventType["action"],
    data: T,
    options?: {
      id?: string;
      user?: { userId: string; [key: string]: unknown };
    },
  ) {
    const eventUser = options?.user
      ? {
          id: options.user.userId,
          ...options.user,
        }
      : undefined;

    appEvents.emitServiceEvent(this.serviceName, {
      id: options?.id || uuidv4(),
      action,
      data,
      user: eventUser,
      timestamp: new Date(),
      resourceType: this.serviceName,
    });
  }
}

Phase 3: Create Event Schema

Step 5: Create Event Schema

Create src/schemas/event.schema.ts:

import { z } from "zod";

export const serviceEventSchema = z.object({
  id: z.string(), // Event's own ID for storage/audit
  action: z.enum(["created", "updated", "deleted"]),
  data: z.unknown(), // Will be typed based on specific entity
  user: z
    .object({
      id: z.string(),
    })
    .passthrough()
    .optional(), // Optional for system events
  timestamp: z.date(), // When event occurred
  resourceType: z.string(), // 'notes', 'users', 'projects', etc.
});

export type ServiceEventType = z.infer<typeof serviceEventSchema>;

Usage Patterns

Extending BaseService

Services that need event emission should extend BaseService:

import { BaseService } from "@/events/base.service";
import type { INoteRepository } from "@/repositories/note.repository";
import type { CreateNoteType, NoteType } from "@/schemas/note.schema";
import type { AuthenticatedUserContextType } from "@/schemas/user.schemas";

export class NoteService extends BaseService {
  constructor(private noteRepository: INoteRepository) {
    super("notes"); // Service name for event routing
  }

  async create(
    data: CreateNoteType,
    user: AuthenticatedUserContextType,
  ): Promise<NoteType> {
    const note = await this.noteRepository.create(data, user.userId);

    // Emit event after successful operation
    this.emitEvent("created", note, {
      id: note.id,
      user,
    });

    return note;
  }

  async update(
    id: string,
    data: UpdateNoteType,
    user: AuthenticatedUserContextType,
  ): Promise<NoteType> {
    const note = await this.noteRepository.update(id, data);

    this.emitEvent("updated", note, {
      id: note.id,
      user,
    });

    return note;
  }

  async delete(id: string, user: AuthenticatedUserContextType): Promise<void> {
    await this.noteRepository.remove(id);

    this.emitEvent(
      "deleted",
      { id },
      {
        id,
        user,
      },
    );
  }
}

Listening to Events

import { appEvents } from "@/events/event-emitter";
import type { ServiceEventType } from "@/schemas/event.schema";

// Listen to specific event types
appEvents.on("notes:created", (event: ServiceEventType) => {
  console.log("Note created:", event.data);
});

appEvents.on("notes:updated", (event: ServiceEventType) => {
  console.log("Note updated:", event.data);
});

appEvents.on("notes:deleted", (event: ServiceEventType) => {
  console.log("Note deleted:", event.data);
});

SSE Endpoint Pattern

For real-time updates via Server-Sent Events, see the add-resource-events skill. Basic pattern:

import { Hono } from "hono";
import { appEvents } from "@/events/event-emitter";
import type { AppEnv } from "@/schemas/app-env.schema";
import type { ServiceEventType } from "@/schemas/event.schema";

export function createEventsRoutes() {
  const router = new Hono<AppEnv>();

  router.get("/", async (c) => {
    const readable = new ReadableStream({
      start(controller) {
        // Send initial connection message
        controller.enqueue(
          new TextEncoder().encode(`data: {"type":"connected"}\n\n`),
        );

        const eventHandler = (event: ServiceEventType) => {
          const eventData = `event: ${event.resourceType}:${event.action}\ndata: ${JSON.stringify(event)}\n\n`;
          controller.enqueue(new TextEncoder().encode(eventData));
        };

        // Listen to events
        appEvents.on("notes:created", eventHandler);
        appEvents.on("notes:updated", eventHandler);
        appEvents.on("notes:deleted", eventHandler);

        // Store cleanup function
        (controller as any).cleanup = () => {
          appEvents.off("notes:created", eventHandler);
          appEvents.off("notes:updated", eventHandler);
          appEvents.off("notes:deleted", eventHandler);
        };
      },
      cancel(controller) {
        if ((controller as any).cleanup) {
          (controller as any).cleanup();
        }
      },
    });

    return new Response(readable, {
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        Connection: "keep-alive",
      },
    });
  });

  return router;
}

Event Structure

Events follow this structure:

{
  id: "uuid-v4-event-id",           // Unique event ID
  action: "created" | "updated" | "deleted",
  data: { /* entity data */ },       // The affected entity
  user: {                            // User who triggered the event
    id: "user-id",
    userId: "user-id",
    globalRole: "user"
  },
  timestamp: Date,                   // When event occurred
  resourceType: "notes"              // Service/resource name
}

Files Created Summary

src/
├── events/
│   ├── event-emitter.ts   # Central event hub
│   └── base.service.ts    # Abstract service with event emission
└── schemas/
    └── event.schema.ts    # Event type definitions

Testing Events

import { describe, it, expect, vi } from "vitest";
import { appEvents } from "@/events/event-emitter";
import { NoteService } from "@/services/note.service";

describe("NoteService Event Emission", () => {
  it("should emit created event after successful note creation", async () => {
    const eventSpy = vi.spyOn(appEvents, "emitServiceEvent");

    const noteService = new NoteService(mockRepository);
    const note = await noteService.create(validNoteData, mockUser);

    expect(eventSpy).toHaveBeenCalledWith("notes", {
      id: expect.any(String),
      action: "created",
      data: note,
      user: {
        id: mockUser.userId,
        ...mockUser,
      },
      timestamp: expect.any(Date),
      resourceType: "notes",
    });
  });
});

What NOT to Do

  • Do NOT emit events before the operation succeeds (emit after success)
  • Do NOT include sensitive data in events (passwords, tokens, etc.)
  • Do NOT forget to clean up event listeners when SSE connections close
  • Do NOT create circular event dependencies
  • Do NOT emit events in the constructor

See Also

  • add-resource-events - Add SSE endpoint with authorization
  • create-resource-service - Services that extend BaseService
  • test-resource-service - Testing event emission