Claude-skill-registry add-resource-events

Add real-time event emission to a resource service. Use when adding SSE/real-time capabilities to a resource. Triggers on "add events", "real-time events", "SSE events".

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/add-resource-events" ~/.claude/skills/majiayu000-claude-skill-registry-add-resource-events && rm -rf "$T"
manifest: skills/data/add-resource-events/SKILL.md
source content

Add Resource Events

Enables real-time event emission for a resource service via Server-Sent Events (SSE).

Quick Reference

Event Infrastructure:

  • src/events/event-emitter.ts
    - Singleton
    AppEventEmitter
  • src/events/base.service.ts
    -
    BaseService
    with
    emitEvent()
  • src/schemas/event.schema.ts
    - Event type definitions
  • src/routes/events.router.ts
    - SSE endpoint

Event Pattern:

{serviceName}:{action}
(e.g.,
notes:created
,
notes:updated
)

Prerequisites

To add events to a resource:

  1. Service must extend
    BaseService
    (see
    create-resource-service
    )
  2. Authorization methods for events in
    AuthorizationService

How It Works

1. BaseService Provides emitEvent()

// src/events/base.service.ts
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 };
    },
  ) {
    appEvents.emitServiceEvent(this.serviceName, {
      id: options?.id || uuidv4(),
      action,
      data,
      user: eventUser,
      timestamp: new Date(),
      resourceType: this.serviceName,
    });
  }
}

2. Resource Service Emits Events

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

  async create(data, user) {
    // ... create logic ...

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

    return note;
  }

  async update(id, data, user) {
    // ... update logic ...

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

    return updatedNote;
  }

  async delete(id, user) {
    // ... delete logic ...

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

    return true;
  }
}

3. Events Router Streams to Clients

// src/routes/events.router.ts
appEvents.on("notes:created", eventHandler);
appEvents.on("notes:updated", eventHandler);
appEvents.on("notes:deleted", eventHandler);

Adding Events to a New Resource

Step 1: Extend BaseService

Your service must extend

BaseService
:

import { BaseService } from "@/events/base.service";

export class {Entity}Service extends BaseService {
  constructor(...) {
    super("{entities}"); // Plural for event namespace
  }
}

Step 2: Emit Events in CRUD Methods

Add

emitEvent()
calls after successful operations:

async create(data, user) {
  const {entity} = await this.repository.create(data, user.userId);

  this.emitEvent("created", {entity}, { id: {entity}.id, user });

  return {entity};
}

async update(id, data, user) {
  const updated = await this.repository.update(id, data);
  if (!updated) return null;

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

  return updated;
}

async delete(id, user) {
  const {entity} = await this.repository.findById(id);
  const deleted = await this.repository.remove(id);

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

  return deleted;
}

Step 3: Add Event Authorization

In

src/services/authorization.service.ts
:

async canReceive{Entity}Event(
  user: AuthenticatedUserContextType,
  {entity}Data: { createdBy: string; [key: string]: unknown },
): Promise<boolean> {
  if (this.isAdmin(user)) return true;
  if ({entity}Data.createdBy === user.userId) return true;
  return false;
}

Step 4: Update Events Router

In

src/routes/events.router.ts
, add listeners:

// Listen to {entity} events
appEvents.on("{entities}:created", eventHandler);
appEvents.on("{entities}:updated", eventHandler);
appEvents.on("{entities}:deleted", eventHandler);

Update the

shouldUserReceiveEvent
function:

async function shouldUserReceiveEvent(
  event: ServiceEventType,
  user: AuthenticatedUserContextType,
  authorizationService: AuthorizationService,
): Promise<boolean> {
  switch (event.resourceType) {
    case "notes":
      // ... existing ...
    case "{entities}":
      if (
        typeof event.data === "object" &&
        event.data !== null &&
        "createdBy" in event.data
      ) {
        return await authorizationService.canReceive{Entity}Event(
          user,
          event.data as { createdBy: string; [key: string]: unknown },
        );
      }
      return false;
    default:
      return false;
  }
}

Step 5: Add Event Schema (Optional)

In

src/schemas/event.schema.ts
:

export const {entity}EventSchema = serviceEventSchema.extend({
  data: z.object({
    id: z.string(),
    // ... entity-specific fields
    createdAt: z.date().optional(),
    updatedAt: z.date().optional(),
  }),
});

export type {Entity}EventType = z.infer<typeof {entity}EventSchema>;

Event Flow

1. Client: POST /notes (create a note)
2. Controller: calls NoteService.create()
3. Service: creates note, calls this.emitEvent("created", note, ...)
4. BaseService: appEvents.emitServiceEvent("notes", { action: "created", ... })
5. EventEmitter: emits "notes:created" event
6. Events Router: catches event, checks authorization
7. SSE Stream: sends to authorized clients
8. Client: receives { event: "notes:created", data: {...} }

Client-Side Usage

const eventSource = new EventSource("/events", {
  headers: { Authorization: `Bearer ${token}` },
});

eventSource.addEventListener("notes:created", (event) => {
  const data = JSON.parse(event.data);
  console.log("New note:", data);
});

eventSource.addEventListener("notes:updated", (event) => {
  const data = JSON.parse(event.data);
  console.log("Updated note:", data);
});

eventSource.addEventListener("notes:deleted", (event) => {
  const data = JSON.parse(event.data);
  console.log("Deleted note:", data);
});

What NOT to Do

  • Do NOT emit events before confirming operation succeeded
  • Do NOT emit events for failed/unauthorized operations
  • Do NOT skip authorization checks in events router
  • Do NOT forget to clean up event listeners on disconnect

See Also

  • create-resource-service
    - Creating services with event emission
  • create-routes
    - Events router example
  • test-resource-service
    - Testing event emission