Claude-skill-registry hono-ipc-patterns
Advanced patterns for Hono-based Electron IPC including CQRS, Zod validation, error handling, and reactive data with RxJS. Use when implementing complex IPC patterns or needing guidance on architecture decisions.
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/hono-ipc-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-hono-ipc-patterns && rm -rf "$T"
manifest:
skills/data/hono-ipc-patterns/SKILL.mdsource content
Hono IPC Patterns
This skill provides advanced patterns for building robust IPC communication in Electron applications using Hono.
When This Skill Applies
- Implementing CQRS (Command Query Responsibility Segregation) pattern
- Adding Zod validation to routes
- Handling errors with Result types (neverthrow)
- Implementing reactive data streams with RxJS
- Testing IPC routes
CQRS Pattern
Core Principles
| Aspect | Query | Command |
|---|---|---|
| Purpose | Read data | Modify state |
| Return Type | | |
| Side Effects | None | Database/API writes |
| Caching | Yes (reactive updates) | No |
Service Interface Example
// src/shared/services/event.service.ts import type { Observable } from 'rxjs'; import type { ResultAsync } from 'neverthrow'; export interface EventService { // === QUERIES (return Observable) === // Get active event (reactive - auto-updates on changes) active(): Observable<EventInstance | undefined>; // Get event history (reactive) histories(includeHidden?: boolean): Observable<EventInstance[]>; // Get single event get(id: number): Observable<EventInstance | undefined>; // === COMMANDS (return ResultAsync) === // Create event (one-shot operation) create(data: CreateEventData): ResultAsync<void, ApplicationError>; // Update event update(id: number, data: UpdateEventData): ResultAsync<void, ApplicationError>; // Delete event delete(id: number): ResultAsync<void, ApplicationError>; // Perform action on event invite(userId: string, location: Location): ResultAsync<void, ApplicationError>; }
Route Handler Pattern
// For Queries - convert Observable to single value .get('/', async (c) => { const result = await firstValueFrom(c.var.services.events.histories()); return c.json(result, 200); }) // For Commands - use Result pattern matching .post('/', zValidator('json', CreateEventBody), async (c) => { const body = c.req.valid('json'); const result = await c.var.services.events.create(body); return result.match( () => c.json({ success: true }, 201), (error) => c.json({ error: error.message }, error.statusCode ?? 500) ); })
See CQRS.md for complete CQRS documentation.
Zod Validation Patterns
Request Validation Layers
import { zValidator } from '@hono/zod-validator'; import { z } from 'zod'; // Path parameter validation const IdParam = z.object({ id: z.string().regex(/^[a-z]{3}_[a-zA-Z0-9]+$/), }); // Query parameter validation (with coercion) const PaginationQuery = z.object({ limit: z.coerce.number().int().positive().max(100).default(10), offset: z.coerce.number().int().nonnegative().default(0), }); // JSON body validation const CreateBody = z.object({ name: z.string().min(1).max(100), description: z.string().max(1000).optional(), }); // Combined usage .get('/:id', zValidator('param', IdParam), (c) => { ... }) .get('/', zValidator('query', PaginationQuery), (c) => { ... }) .post('/', zValidator('json', CreateBody), (c) => { ... })
See ZOD-VALIDATION.md for complete validation documentation.
Error Handling Patterns
Centralized Error Handler
const factory = (deps: Dependencies) => createFactory<HonoEnv>({ initApp(app) { // Global error handler app.onError((err, c) => { // Log error deps.logger?.error('Request error:', err); // Handle known error types if (err instanceof HTTPException) { return c.json({ error: err.message }, err.status); } if (err instanceof ValidationError) { return c.json({ error: err.message, details: err.issues }, 400); } // Unknown errors return c.json({ error: 'Internal Server Error' }, 500); }); }, });
Result Type Pattern Matching
.post('/action', async (c) => { const result = await c.var.services.action.perform(); return result.match( (data) => c.json(data, 200), (error) => { // Type-safe error handling switch (error.code) { case 'NOT_FOUND': return c.json({ error: error.message }, 404); case 'UNAUTHORIZED': return c.json({ error: error.message }, 401); case 'VALIDATION_ERROR': return c.json({ error: error.message, details: error.details }, 400); default: return c.json({ error: 'Operation failed' }, 500); } } ); })
Reactive Data Patterns
BehaviorSubject for Query Updates
// Service implementation pattern export class UserServiceImpl implements UserService { #notify = new BehaviorSubject(Date.now()); // Query: Returns Observable that updates on changes list(): Observable<User[]> { return concat( // Initial data fetch from(this.#fetchUsers()), // Re-fetch when notified this.#notify.pipe( distinctUntilChanged(), mergeMap(() => this.#fetchUsers()) ) ); } // Command: Modifies data and notifies subscribers async create(data: CreateUserData): ResultAsync<void, Error> { const result = await this.#createUser(data); if (result.isOk()) { // Notify all query subscribers to refresh this.#notify.next(Date.now()); } return result; } }
Helper for Observable to Value
// utils/observable.ts import { firstValueFrom, Observable } from 'rxjs'; export const firstValueFromResult = <T>( observable: Observable<T> ): Promise<T> => { return firstValueFrom(observable); }; // Usage in routes .get('/', async (c) => { const users = await firstValueFromResult(c.var.services.users.list()); return c.json(users, 200); })
Testing Patterns
Route Unit Testing
import { Hono } from 'hono'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { of } from 'rxjs'; import { ok, err } from 'neverthrow'; describe('Events Route', () => { let app: Hono<HonoEnv>; let mockEventService: Partial<EventService>; beforeEach(() => { mockEventService = { list: vi.fn(), create: vi.fn(), }; app = new Hono<HonoEnv>(); app.use((c, next) => { c.set('services', { events: mockEventService } as any); return next(); }); app.route('/events', routes); }); it('should list events', async () => { mockEventService.list.mockReturnValue(of([{ id: 1, name: 'Event' }])); const res = await app.request('/events'); const data = await res.json(); expect(res.status).toBe(200); expect(data).toHaveLength(1); }); it('should handle create error', async () => { mockEventService.create.mockReturnValue( err({ code: 'VALIDATION_ERROR', message: 'Invalid data' }) ); const res = await app.request('/events', { method: 'POST', body: JSON.stringify({ name: '' }), headers: { 'Content-Type': 'application/json' }, }); expect(res.status).toBe(400); }); });
Files Reference
- CQRS.md - Complete CQRS pattern documentation
- ZOD-VALIDATION.md - Zod validation patterns