omgkit 2.0.7 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/package.json +2 -2
  2. package/plugin/skills/backend/api-architecture/SKILL.md +857 -0
  3. package/plugin/skills/backend/caching-strategies/SKILL.md +755 -0
  4. package/plugin/skills/backend/event-driven-architecture/SKILL.md +753 -0
  5. package/plugin/skills/backend/real-time-systems/SKILL.md +635 -0
  6. package/plugin/skills/databases/database-optimization/SKILL.md +571 -0
  7. package/plugin/skills/databases/postgresql/SKILL.md +494 -18
  8. package/plugin/skills/devops/docker/SKILL.md +466 -18
  9. package/plugin/skills/devops/monorepo-management/SKILL.md +595 -0
  10. package/plugin/skills/devops/observability/SKILL.md +622 -0
  11. package/plugin/skills/devops/performance-profiling/SKILL.md +905 -0
  12. package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
  13. package/plugin/skills/frameworks/react/SKILL.md +1006 -32
  14. package/plugin/skills/frontend/advanced-ui-design/SKILL.md +426 -0
  15. package/plugin/skills/integrations/ai-integration/SKILL.md +730 -0
  16. package/plugin/skills/integrations/payment-integration/SKILL.md +735 -0
  17. package/plugin/skills/languages/python/SKILL.md +489 -25
  18. package/plugin/skills/languages/typescript/SKILL.md +379 -30
  19. package/plugin/skills/methodology/problem-solving/SKILL.md +355 -0
  20. package/plugin/skills/methodology/research-validation/SKILL.md +668 -0
  21. package/plugin/skills/methodology/sequential-thinking/SKILL.md +260 -0
  22. package/plugin/skills/mobile/mobile-development/SKILL.md +756 -0
  23. package/plugin/skills/security/security-hardening/SKILL.md +633 -0
  24. package/plugin/skills/tools/document-processing/SKILL.md +916 -0
  25. package/plugin/skills/tools/image-processing/SKILL.md +748 -0
  26. package/plugin/skills/tools/mcp-development/SKILL.md +883 -0
  27. package/plugin/skills/tools/media-processing/SKILL.md +831 -0
@@ -0,0 +1,635 @@
1
+ ---
2
+ name: real-time-systems
3
+ description: WebSocket, Server-Sent Events, and real-time communication patterns for live features
4
+ category: backend
5
+ triggers:
6
+ - real-time
7
+ - websocket
8
+ - socket.io
9
+ - server-sent events
10
+ - sse
11
+ - live updates
12
+ - presence
13
+ ---
14
+
15
+ # Real-Time Systems
16
+
17
+ Build **real-time communication systems** with WebSocket, SSE, and pub/sub patterns. This skill covers connection management, scaling, and production deployment.
18
+
19
+ ## Purpose
20
+
21
+ Implement live features that users expect:
22
+
23
+ - Real-time messaging and chat
24
+ - Live notifications and updates
25
+ - Collaborative editing
26
+ - Presence detection
27
+ - Live dashboards and metrics
28
+ - Gaming and interactive experiences
29
+
30
+ ## Features
31
+
32
+ ### 1. WebSocket Server with Socket.io
33
+
34
+ ```typescript
35
+ import { Server } from 'socket.io';
36
+ import { createAdapter } from '@socket.io/redis-adapter';
37
+ import { createClient } from 'redis';
38
+
39
+ // Initialize Socket.io with Redis adapter for scaling
40
+ async function createSocketServer(httpServer: http.Server) {
41
+ const io = new Server(httpServer, {
42
+ cors: {
43
+ origin: process.env.CLIENT_URL,
44
+ credentials: true,
45
+ },
46
+ pingTimeout: 60000,
47
+ pingInterval: 25000,
48
+ });
49
+
50
+ // Redis adapter for multi-server deployment
51
+ const pubClient = createClient({ url: process.env.REDIS_URL });
52
+ const subClient = pubClient.duplicate();
53
+ await Promise.all([pubClient.connect(), subClient.connect()]);
54
+ io.adapter(createAdapter(pubClient, subClient));
55
+
56
+ // Authentication middleware
57
+ io.use(async (socket, next) => {
58
+ const token = socket.handshake.auth.token;
59
+
60
+ if (!token) {
61
+ return next(new Error('Authentication required'));
62
+ }
63
+
64
+ try {
65
+ const user = await verifyToken(token);
66
+ socket.data.user = user;
67
+ next();
68
+ } catch (error) {
69
+ next(new Error('Invalid token'));
70
+ }
71
+ });
72
+
73
+ // Connection handling
74
+ io.on('connection', (socket) => {
75
+ const userId = socket.data.user.id;
76
+ console.log(`User connected: ${userId}`);
77
+
78
+ // Join user's personal room
79
+ socket.join(`user:${userId}`);
80
+
81
+ // Handle joining rooms
82
+ socket.on('join:room', async (roomId: string) => {
83
+ // Verify access
84
+ const hasAccess = await checkRoomAccess(userId, roomId);
85
+ if (!hasAccess) {
86
+ socket.emit('error', { message: 'Access denied' });
87
+ return;
88
+ }
89
+
90
+ socket.join(`room:${roomId}`);
91
+ socket.to(`room:${roomId}`).emit('user:joined', {
92
+ userId,
93
+ username: socket.data.user.name,
94
+ });
95
+ });
96
+
97
+ // Handle messages
98
+ socket.on('message:send', async (data: { roomId: string; content: string }) => {
99
+ const message = await saveMessage({
100
+ roomId: data.roomId,
101
+ userId,
102
+ content: data.content,
103
+ });
104
+
105
+ io.to(`room:${data.roomId}`).emit('message:new', message);
106
+ });
107
+
108
+ // Typing indicators
109
+ socket.on('typing:start', (roomId: string) => {
110
+ socket.to(`room:${roomId}`).emit('typing:user', {
111
+ userId,
112
+ username: socket.data.user.name,
113
+ typing: true,
114
+ });
115
+ });
116
+
117
+ socket.on('typing:stop', (roomId: string) => {
118
+ socket.to(`room:${roomId}`).emit('typing:user', {
119
+ userId,
120
+ typing: false,
121
+ });
122
+ });
123
+
124
+ // Presence
125
+ socket.on('presence:update', async (status: 'online' | 'away' | 'busy') => {
126
+ await updatePresence(userId, status);
127
+ io.emit('presence:changed', { userId, status });
128
+ });
129
+
130
+ // Disconnect handling
131
+ socket.on('disconnect', async (reason) => {
132
+ console.log(`User disconnected: ${userId}, reason: ${reason}`);
133
+ await updatePresence(userId, 'offline');
134
+ io.emit('presence:changed', { userId, status: 'offline' });
135
+ });
136
+ });
137
+
138
+ return io;
139
+ }
140
+ ```
141
+
142
+ ### 2. Server-Sent Events (SSE)
143
+
144
+ ```typescript
145
+ import { Router } from 'express';
146
+
147
+ const router = Router();
148
+
149
+ // SSE endpoint for notifications
150
+ router.get('/events/notifications', authenticate, (req, res) => {
151
+ const userId = req.user.id;
152
+
153
+ // Set SSE headers
154
+ res.setHeader('Content-Type', 'text/event-stream');
155
+ res.setHeader('Cache-Control', 'no-cache');
156
+ res.setHeader('Connection', 'keep-alive');
157
+ res.setHeader('X-Accel-Buffering', 'no'); // Disable nginx buffering
158
+
159
+ // Send initial connection event
160
+ res.write(`event: connected\ndata: ${JSON.stringify({ userId })}\n\n`);
161
+
162
+ // Keep-alive ping
163
+ const pingInterval = setInterval(() => {
164
+ res.write(`: ping\n\n`);
165
+ }, 30000);
166
+
167
+ // Subscribe to user's notifications
168
+ const subscription = pubsub.subscribe(`notifications:${userId}`, (message) => {
169
+ res.write(`event: notification\ndata: ${JSON.stringify(message)}\n\n`);
170
+ });
171
+
172
+ // Cleanup on disconnect
173
+ req.on('close', () => {
174
+ clearInterval(pingInterval);
175
+ subscription.unsubscribe();
176
+ console.log(`SSE connection closed for user ${userId}`);
177
+ });
178
+ });
179
+
180
+ // SSE for live updates (e.g., stock prices, metrics)
181
+ router.get('/events/stream/:channel', authenticate, async (req, res) => {
182
+ const { channel } = req.params;
183
+
184
+ res.setHeader('Content-Type', 'text/event-stream');
185
+ res.setHeader('Cache-Control', 'no-cache');
186
+ res.setHeader('Connection', 'keep-alive');
187
+
188
+ // Send initial data
189
+ const initialData = await getChannelData(channel);
190
+ res.write(`event: initial\ndata: ${JSON.stringify(initialData)}\n\n`);
191
+
192
+ // Stream updates
193
+ const unsubscribe = subscribeToChannel(channel, (update) => {
194
+ res.write(`event: update\ndata: ${JSON.stringify(update)}\n\n`);
195
+ });
196
+
197
+ // Handle retry on reconnection
198
+ res.write(`retry: 3000\n\n`);
199
+
200
+ req.on('close', () => {
201
+ unsubscribe();
202
+ });
203
+ });
204
+
205
+ // Client-side SSE handling
206
+ const EventSourceComponent = () => {
207
+ useEffect(() => {
208
+ const eventSource = new EventSource('/api/events/notifications', {
209
+ withCredentials: true,
210
+ });
211
+
212
+ eventSource.onopen = () => {
213
+ console.log('SSE connected');
214
+ };
215
+
216
+ eventSource.addEventListener('notification', (event) => {
217
+ const notification = JSON.parse(event.data);
218
+ showNotification(notification);
219
+ });
220
+
221
+ eventSource.onerror = (error) => {
222
+ console.error('SSE error:', error);
223
+ // EventSource auto-reconnects
224
+ };
225
+
226
+ return () => {
227
+ eventSource.close();
228
+ };
229
+ }, []);
230
+
231
+ return null;
232
+ };
233
+ ```
234
+
235
+ ### 3. Pub/Sub with Redis
236
+
237
+ ```typescript
238
+ import { createClient } from 'redis';
239
+
240
+ class PubSubService {
241
+ private publisher: ReturnType<typeof createClient>;
242
+ private subscriber: ReturnType<typeof createClient>;
243
+ private handlers: Map<string, Set<(message: any) => void>> = new Map();
244
+
245
+ async connect() {
246
+ this.publisher = createClient({ url: process.env.REDIS_URL });
247
+ this.subscriber = this.publisher.duplicate();
248
+
249
+ await Promise.all([
250
+ this.publisher.connect(),
251
+ this.subscriber.connect(),
252
+ ]);
253
+
254
+ // Handle incoming messages
255
+ this.subscriber.on('message', (channel, message) => {
256
+ const handlers = this.handlers.get(channel);
257
+ if (handlers) {
258
+ const parsed = JSON.parse(message);
259
+ handlers.forEach(handler => handler(parsed));
260
+ }
261
+ });
262
+ }
263
+
264
+ async publish(channel: string, message: any): Promise<void> {
265
+ await this.publisher.publish(channel, JSON.stringify(message));
266
+ }
267
+
268
+ subscribe(channel: string, handler: (message: any) => void): () => void {
269
+ if (!this.handlers.has(channel)) {
270
+ this.handlers.set(channel, new Set());
271
+ this.subscriber.subscribe(channel);
272
+ }
273
+
274
+ this.handlers.get(channel)!.add(handler);
275
+
276
+ // Return unsubscribe function
277
+ return () => {
278
+ const handlers = this.handlers.get(channel);
279
+ if (handlers) {
280
+ handlers.delete(handler);
281
+ if (handlers.size === 0) {
282
+ this.handlers.delete(channel);
283
+ this.subscriber.unsubscribe(channel);
284
+ }
285
+ }
286
+ };
287
+ }
288
+
289
+ // Pattern subscription
290
+ async psubscribe(pattern: string, handler: (channel: string, message: any) => void): Promise<() => void> {
291
+ await this.subscriber.pSubscribe(pattern, (message, channel) => {
292
+ handler(channel, JSON.parse(message));
293
+ });
294
+
295
+ return () => {
296
+ this.subscriber.pUnsubscribe(pattern);
297
+ };
298
+ }
299
+ }
300
+
301
+ const pubsub = new PubSubService();
302
+
303
+ // Usage in services
304
+ class NotificationService {
305
+ async sendNotification(userId: string, notification: Notification): Promise<void> {
306
+ // Save to database
307
+ await db.notification.create({ data: { ...notification, userId } });
308
+
309
+ // Publish to real-time channel
310
+ await pubsub.publish(`notifications:${userId}`, notification);
311
+ }
312
+
313
+ async broadcastToRoom(roomId: string, event: string, data: any): Promise<void> {
314
+ await pubsub.publish(`room:${roomId}`, { event, data });
315
+ }
316
+ }
317
+ ```
318
+
319
+ ### 4. Presence System
320
+
321
+ ```typescript
322
+ interface PresenceData {
323
+ status: 'online' | 'away' | 'busy' | 'offline';
324
+ lastSeen: Date;
325
+ socketIds: string[];
326
+ }
327
+
328
+ class PresenceService {
329
+ private redis: ReturnType<typeof createClient>;
330
+ private readonly PRESENCE_TTL = 300; // 5 minutes
331
+
332
+ async setPresence(userId: string, socketId: string, status: string): Promise<void> {
333
+ const key = `presence:${userId}`;
334
+
335
+ // Use MULTI for atomic operations
336
+ await this.redis.multi()
337
+ .hSet(key, {
338
+ status,
339
+ lastSeen: Date.now().toString(),
340
+ })
341
+ .sAdd(`${key}:sockets`, socketId)
342
+ .expire(key, this.PRESENCE_TTL)
343
+ .exec();
344
+
345
+ // Publish presence change
346
+ await pubsub.publish('presence:updates', {
347
+ userId,
348
+ status,
349
+ lastSeen: new Date(),
350
+ });
351
+ }
352
+
353
+ async removeSocket(userId: string, socketId: string): Promise<void> {
354
+ const key = `presence:${userId}`;
355
+
356
+ await this.redis.sRem(`${key}:sockets`, socketId);
357
+ const remaining = await this.redis.sCard(`${key}:sockets`);
358
+
359
+ if (remaining === 0) {
360
+ await this.redis.hSet(key, 'status', 'offline');
361
+ await pubsub.publish('presence:updates', {
362
+ userId,
363
+ status: 'offline',
364
+ lastSeen: new Date(),
365
+ });
366
+ }
367
+ }
368
+
369
+ async getPresence(userId: string): Promise<PresenceData | null> {
370
+ const key = `presence:${userId}`;
371
+ const data = await this.redis.hGetAll(key);
372
+
373
+ if (!data.status) return null;
374
+
375
+ return {
376
+ status: data.status as PresenceData['status'],
377
+ lastSeen: new Date(parseInt(data.lastSeen)),
378
+ socketIds: await this.redis.sMembers(`${key}:sockets`),
379
+ };
380
+ }
381
+
382
+ async getMultiplePresence(userIds: string[]): Promise<Map<string, PresenceData>> {
383
+ const pipeline = this.redis.multi();
384
+
385
+ userIds.forEach(id => {
386
+ pipeline.hGetAll(`presence:${id}`);
387
+ });
388
+
389
+ const results = await pipeline.exec();
390
+ const presenceMap = new Map<string, PresenceData>();
391
+
392
+ userIds.forEach((id, index) => {
393
+ const data = results[index] as Record<string, string>;
394
+ if (data?.status) {
395
+ presenceMap.set(id, {
396
+ status: data.status as PresenceData['status'],
397
+ lastSeen: new Date(parseInt(data.lastSeen)),
398
+ socketIds: [],
399
+ });
400
+ }
401
+ });
402
+
403
+ return presenceMap;
404
+ }
405
+ }
406
+ ```
407
+
408
+ ### 5. Connection Recovery
409
+
410
+ ```typescript
411
+ // Client-side reconnection logic
412
+ class ReconnectingWebSocket {
413
+ private ws: WebSocket | null = null;
414
+ private reconnectAttempts = 0;
415
+ private maxReconnectAttempts = 10;
416
+ private reconnectInterval = 1000;
417
+ private messageQueue: any[] = [];
418
+
419
+ constructor(
420
+ private url: string,
421
+ private options: {
422
+ onMessage: (data: any) => void;
423
+ onConnect: () => void;
424
+ onDisconnect: () => void;
425
+ }
426
+ ) {
427
+ this.connect();
428
+ }
429
+
430
+ private connect(): void {
431
+ this.ws = new WebSocket(this.url);
432
+
433
+ this.ws.onopen = () => {
434
+ console.log('WebSocket connected');
435
+ this.reconnectAttempts = 0;
436
+ this.options.onConnect();
437
+
438
+ // Flush queued messages
439
+ while (this.messageQueue.length > 0) {
440
+ const msg = this.messageQueue.shift();
441
+ this.send(msg);
442
+ }
443
+ };
444
+
445
+ this.ws.onmessage = (event) => {
446
+ const data = JSON.parse(event.data);
447
+ this.options.onMessage(data);
448
+ };
449
+
450
+ this.ws.onclose = (event) => {
451
+ console.log('WebSocket closed:', event.code, event.reason);
452
+ this.options.onDisconnect();
453
+ this.scheduleReconnect();
454
+ };
455
+
456
+ this.ws.onerror = (error) => {
457
+ console.error('WebSocket error:', error);
458
+ };
459
+ }
460
+
461
+ private scheduleReconnect(): void {
462
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
463
+ console.error('Max reconnection attempts reached');
464
+ return;
465
+ }
466
+
467
+ const delay = Math.min(
468
+ this.reconnectInterval * Math.pow(2, this.reconnectAttempts),
469
+ 30000 // Max 30 seconds
470
+ );
471
+
472
+ console.log(`Reconnecting in ${delay}ms...`);
473
+
474
+ setTimeout(() => {
475
+ this.reconnectAttempts++;
476
+ this.connect();
477
+ }, delay);
478
+ }
479
+
480
+ send(data: any): void {
481
+ if (this.ws?.readyState === WebSocket.OPEN) {
482
+ this.ws.send(JSON.stringify(data));
483
+ } else {
484
+ // Queue message for when connection is restored
485
+ this.messageQueue.push(data);
486
+ }
487
+ }
488
+
489
+ close(): void {
490
+ this.maxReconnectAttempts = 0; // Prevent reconnection
491
+ this.ws?.close();
492
+ }
493
+ }
494
+
495
+ // Server-side missed message recovery
496
+ class MessageRecovery {
497
+ async getMessagesSince(roomId: string, lastMessageId: string): Promise<Message[]> {
498
+ // Fetch messages after the last seen message
499
+ return db.message.findMany({
500
+ where: {
501
+ roomId,
502
+ id: { gt: lastMessageId },
503
+ },
504
+ orderBy: { createdAt: 'asc' },
505
+ take: 100, // Limit recovery batch
506
+ });
507
+ }
508
+
509
+ async recoverClientState(userId: string, lastSyncTimestamp: number): Promise<{
510
+ messages: Message[];
511
+ notifications: Notification[];
512
+ presenceUpdates: PresenceUpdate[];
513
+ }> {
514
+ const since = new Date(lastSyncTimestamp);
515
+
516
+ return {
517
+ messages: await this.getUnreadMessages(userId, since),
518
+ notifications: await this.getUnreadNotifications(userId, since),
519
+ presenceUpdates: await this.getPresenceChanges(since),
520
+ };
521
+ }
522
+ }
523
+ ```
524
+
525
+ ### 6. Scaling WebSockets
526
+
527
+ ```typescript
528
+ // Horizontal scaling with sticky sessions
529
+ // nginx.conf
530
+ upstream websocket_servers {
531
+ ip_hash; // Sticky sessions
532
+ server ws1.example.com:3000;
533
+ server ws2.example.com:3000;
534
+ server ws3.example.com:3000;
535
+ }
536
+
537
+ server {
538
+ location /socket.io/ {
539
+ proxy_pass http://websocket_servers;
540
+ proxy_http_version 1.1;
541
+ proxy_set_header Upgrade $http_upgrade;
542
+ proxy_set_header Connection "upgrade";
543
+ proxy_set_header Host $host;
544
+ proxy_set_header X-Real-IP $remote_addr;
545
+ proxy_read_timeout 86400;
546
+ }
547
+ }
548
+
549
+ // Broadcasting across servers
550
+ class ScaledBroadcaster {
551
+ async broadcastToRoom(roomId: string, event: string, data: any): Promise<void> {
552
+ // Publish to Redis - all servers will receive
553
+ await pubsub.publish(`broadcast:room:${roomId}`, {
554
+ event,
555
+ data,
556
+ timestamp: Date.now(),
557
+ });
558
+ }
559
+
560
+ // Each server subscribes and emits locally
561
+ setupBroadcastListener(io: Server): void {
562
+ pubsub.psubscribe('broadcast:*', (channel, message) => {
563
+ const [, type, id] = channel.split(':');
564
+
565
+ if (type === 'room') {
566
+ io.to(`room:${id}`).emit(message.event, message.data);
567
+ } else if (type === 'user') {
568
+ io.to(`user:${id}`).emit(message.event, message.data);
569
+ }
570
+ });
571
+ }
572
+ }
573
+ ```
574
+
575
+ ## Use Cases
576
+
577
+ ### 1. Chat Application
578
+
579
+ ```typescript
580
+ // Real-time chat with typing indicators and read receipts
581
+ socket.on('chat:message', async (data) => {
582
+ const message = await createMessage(data);
583
+ io.to(`room:${data.roomId}`).emit('chat:message', message);
584
+ });
585
+
586
+ socket.on('chat:read', async ({ roomId, messageId }) => {
587
+ await markAsRead(socket.data.user.id, roomId, messageId);
588
+ socket.to(`room:${roomId}`).emit('chat:read', {
589
+ userId: socket.data.user.id,
590
+ messageId,
591
+ });
592
+ });
593
+ ```
594
+
595
+ ### 2. Live Dashboard
596
+
597
+ ```typescript
598
+ // Real-time metrics with SSE
599
+ setInterval(async () => {
600
+ const metrics = await gatherMetrics();
601
+ await pubsub.publish('dashboard:metrics', metrics);
602
+ }, 5000);
603
+ ```
604
+
605
+ ## Best Practices
606
+
607
+ ### Do's
608
+
609
+ - **Implement heartbeat/ping** - Detect dead connections
610
+ - **Handle reconnection gracefully** - Queue messages, recover state
611
+ - **Use rooms for scaling** - Don't broadcast to all
612
+ - **Implement backpressure** - Handle slow clients
613
+ - **Plan for offline scenarios** - Message queuing
614
+ - **Monitor connection metrics** - Track active connections
615
+
616
+ ### Don'ts
617
+
618
+ - Don't trust client data without validation
619
+ - Don't skip authentication
620
+ - Don't broadcast sensitive data
621
+ - Don't ignore connection limits
622
+ - Don't forget cleanup on disconnect
623
+ - Don't use WebSocket for everything
624
+
625
+ ## Related Skills
626
+
627
+ - **redis** - Pub/sub and state management
628
+ - **backend-development** - Server architecture
629
+ - **api-architecture** - REST fallbacks
630
+
631
+ ## Reference Resources
632
+
633
+ - [Socket.io Documentation](https://socket.io/docs/)
634
+ - [WebSocket MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
635
+ - [SSE MDN](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)