ultra-dex 3.2.0 → 3.4.0

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 (46) hide show
  1. package/README.md +52 -10
  2. package/assets/cursor-rules/18-webhooks.mdc +52 -0
  3. package/assets/cursor-rules/19-cron-jobs.mdc +61 -0
  4. package/assets/cursor-rules/20-rate-limiting.mdc +59 -0
  5. package/assets/cursor-rules/21-logging.mdc +58 -0
  6. package/assets/cursor-rules/22-caching.mdc +70 -0
  7. package/assets/cursor-rules/23-forms.mdc +67 -0
  8. package/assets/cursor-rules/24-file-upload.mdc +78 -0
  9. package/assets/cursor-rules/25-websockets.mdc +100 -0
  10. package/bin/ultra-dex.js +40 -2
  11. package/lib/commands/agents.js +185 -4
  12. package/lib/commands/banner.js +1 -1
  13. package/lib/commands/cloud.js +780 -0
  14. package/lib/commands/exec.js +434 -0
  15. package/lib/commands/github.js +475 -0
  16. package/lib/commands/monitoring.js +207 -0
  17. package/lib/commands/run.js +39 -5
  18. package/lib/commands/scaffold.js +25 -4
  19. package/lib/commands/search.js +477 -0
  20. package/lib/commands/serve.js +1 -1
  21. package/lib/commands/state.js +1 -1
  22. package/lib/commands/swarm.js +78 -34
  23. package/lib/commands/sync.js +189 -0
  24. package/lib/mcp/client.js +521 -0
  25. package/lib/mcp/graph.js +14 -5
  26. package/lib/mcp/server.js +1 -1
  27. package/lib/mcp/tools.js +47 -7
  28. package/lib/providers/agent-sdk.js +630 -0
  29. package/lib/providers/anthropic-agents.js +580 -0
  30. package/lib/providers/base.js +36 -9
  31. package/lib/providers/claude.js +146 -89
  32. package/lib/providers/index.js +6 -1
  33. package/lib/providers/langchain.js +315 -0
  34. package/lib/providers/openai-assistants.js +484 -0
  35. package/lib/quality/scanner.js +47 -24
  36. package/lib/swarm/index.js +83 -29
  37. package/lib/swarm/protocol.js +48 -5
  38. package/lib/swarm/tiers.js +41 -7
  39. package/lib/utils/browser.js +373 -0
  40. package/lib/utils/config-manager.js +446 -0
  41. package/lib/utils/error-recovery.js +473 -0
  42. package/lib/utils/graph.js +25 -6
  43. package/lib/utils/interactive-mode.js +417 -0
  44. package/lib/utils/monitoring.js +413 -0
  45. package/lib/utils/version-display.js +1 -1
  46. package/package.json +11 -4
package/README.md CHANGED
@@ -2,23 +2,31 @@
2
2
 
3
3
  > Scaffold Ultra-Dex projects from the command line, now with **AI-powered plan generation** and **God Mode** autonomous agents.
4
4
 
5
- ## What's New in v3.2.0 (Professional Purple)
5
+ ## What's New in v3.4.0 (Survival Mode)
6
6
 
7
7
  ```bash
8
+ # 🧠 Auto-sync CONTEXT.md from codebase (eliminates manual updates)
9
+ npx ultra-dex sync --brain
10
+
11
+ # 🐳 Execute code in Docker sandbox
12
+ npx ultra-dex exec "console.log('safe!')" --lang javascript
13
+
14
+ # 🔍 Semantic search with vector embeddings
15
+ npx ultra-dex search "user authentication" --index
16
+
17
+ # 🐙 GitHub integration (issues → tasks, auto-PR)
18
+ npx ultra-dex github sync
19
+ npx ultra-dex github pr --from-swarm
20
+
8
21
  # 🤖 Autonomous agent swarms with parallel execution
9
22
  npx ultra-dex swarm "Build user authentication" --parallel
10
23
 
11
24
  # 🔄 Start the Active Kernel (MCP + WebSocket + Dashboard)
12
25
  npx ultra-dex serve
13
26
 
14
- # 🧠 Structural Graph Health Check
15
- npx ultra-dex check
16
-
17
- # 🖥️ Live Dashboard with Brain Visualization
18
- npx ultra-dex dashboard
19
-
20
- # 🛠️ Self-Healing CI/CD Monitor
21
- npx ultra-dex ci-monitor --port 3003
27
+ # 📊 Health monitoring and metrics
28
+ npx ultra-dex health
29
+ npx ultra-dex metrics
22
30
  ```
23
31
 
24
32
  ## First 10 Minutes
@@ -156,10 +164,37 @@ npx ultra-dex check
156
164
  ANTHROPIC_API_KEY=sk-ant-... # Claude (recommended for complex tasks)
157
165
  OPENAI_API_KEY=sk-... # OpenAI
158
166
  GOOGLE_AI_KEY=... # Gemini
167
+ OLLAMA_HOST=http://localhost:11434 # Ollama (local AI)
159
168
  ULTRA_DEX_DEFAULT_PROVIDER=claude # Default AI provider
160
169
  ```
161
170
 
162
- ## All Commands (38+)
171
+ ## Local AI with Ollama
172
+
173
+ Ultra-Dex supports Ollama for fully local, private AI operations:
174
+
175
+ ```bash
176
+ # 1. Install Ollama (https://ollama.ai)
177
+ curl -fsSL https://ollama.ai/install.sh | sh
178
+
179
+ # 2. Pull a model
180
+ ollama pull llama3.2
181
+ ollama pull codellama
182
+
183
+ # 3. Use with Ultra-Dex
184
+ npx ultra-dex generate "Your idea" --provider ollama --model codellama
185
+
186
+ # For agent swarms with local AI
187
+ npx ultra-dex swarm "Build user auth" --provider ollama
188
+ ```
189
+
190
+ **Supported Ollama Models:**
191
+ - `llama3.2` - General tasks
192
+ - `codellama` - Code generation (recommended)
193
+ - `mistral` - Fast, efficient
194
+ - `mixtral` - Best quality for local
195
+ - Any model available via `ollama list`
196
+
197
+ ## All Commands (44+)
163
198
 
164
199
  | Command | Description |
165
200
  |---------|-------------|
@@ -201,6 +236,13 @@ ULTRA_DEX_DEFAULT_PROVIDER=claude # Default AI provider
201
236
  | `pack` | Bundle agents and rules |
202
237
  | `run` | Execute agent task |
203
238
  | `diff` | Compare plan vs implemented code |
239
+ | `exec` | **NEW** Execute code in Docker sandbox |
240
+ | `search` | **NEW** Semantic codebase search |
241
+ | `github` | **NEW** GitHub sync (issues, PRs) |
242
+ | `cloud` | **NEW** Team collaboration server |
243
+ | `metrics` | **NEW** Show CLI metrics |
244
+ | `health` | **NEW** System health check |
245
+ | `debug` | **NEW** Debug information |
204
246
 
205
247
  ## Links
206
248
 
@@ -0,0 +1,52 @@
1
+ # Webhook Implementation Rules
2
+
3
+ ## Webhook Endpoint Design
4
+ - Use POST method for incoming webhooks
5
+ - Validate webhook signatures (HMAC-SHA256)
6
+ - Return 200 immediately, process async
7
+ - Implement idempotency with event IDs
8
+
9
+ ## Security
10
+ ```typescript
11
+ // Verify webhook signature
12
+ async function verifyWebhook(req: Request, secret: string) {
13
+ const signature = req.headers.get('x-signature-256');
14
+ const body = await req.text();
15
+ const expected = crypto
16
+ .createHmac('sha256', secret)
17
+ .update(body)
18
+ .digest('hex');
19
+ return crypto.timingSafeEqual(
20
+ Buffer.from(signature || ''),
21
+ Buffer.from(`sha256=${expected}`)
22
+ );
23
+ }
24
+ ```
25
+
26
+ ## Retry Strategy
27
+ - Implement exponential backoff (1s, 2s, 4s, 8s, 16s)
28
+ - Max 5 retry attempts
29
+ - Store failed webhooks for manual retry
30
+ - Log all webhook attempts with correlation IDs
31
+
32
+ ## Event Processing
33
+ ```typescript
34
+ // Idempotent webhook handler
35
+ async function handleWebhook(event: WebhookEvent) {
36
+ const existing = await db.webhookEvent.findUnique({
37
+ where: { eventId: event.id }
38
+ });
39
+ if (existing) return { status: 'duplicate' };
40
+
41
+ await db.$transaction([
42
+ db.webhookEvent.create({ data: { eventId: event.id } }),
43
+ processEvent(event)
44
+ ]);
45
+ }
46
+ ```
47
+
48
+ ## Outbound Webhooks
49
+ - Queue outbound webhooks with job processor
50
+ - Include timestamp and signature in headers
51
+ - Support webhook URL validation before saving
52
+ - Implement circuit breaker for failing endpoints
@@ -0,0 +1,61 @@
1
+ # Cron & Scheduled Jobs Rules
2
+
3
+ ## Job Scheduling Patterns
4
+ - Use cron expressions for recurring jobs
5
+ - Store job definitions in database, not code
6
+ - Implement job locking for distributed systems
7
+ - Track job execution history
8
+
9
+ ## Implementation
10
+ ```typescript
11
+ // Next.js API route with Vercel Cron
12
+ // vercel.json: { "crons": [{ "path": "/api/cron/daily", "schedule": "0 0 * * *" }] }
13
+
14
+ export async function GET(req: Request) {
15
+ // Verify cron secret
16
+ const authHeader = req.headers.get('authorization');
17
+ if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
18
+ return new Response('Unauthorized', { status: 401 });
19
+ }
20
+
21
+ // Execute with timeout protection
22
+ const controller = new AbortController();
23
+ const timeout = setTimeout(() => controller.abort(), 55000);
24
+
25
+ try {
26
+ await runDailyTasks({ signal: controller.signal });
27
+ return Response.json({ success: true });
28
+ } finally {
29
+ clearTimeout(timeout);
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## Job Locking
35
+ ```typescript
36
+ // Prevent concurrent execution
37
+ async function withJobLock(jobName: string, fn: () => Promise<void>) {
38
+ const lock = await db.jobLock.create({
39
+ data: {
40
+ name: jobName,
41
+ lockedAt: new Date(),
42
+ expiresAt: new Date(Date.now() + 60000)
43
+ }
44
+ }).catch(() => null);
45
+
46
+ if (!lock) return { skipped: true };
47
+
48
+ try {
49
+ await fn();
50
+ return { success: true };
51
+ } finally {
52
+ await db.jobLock.delete({ where: { name: jobName } });
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Monitoring
58
+ - Log job start/end with duration
59
+ - Alert on job failures or timeouts
60
+ - Track job queue depth
61
+ - Implement dead letter queue for failed jobs
@@ -0,0 +1,59 @@
1
+ # Rate Limiting Rules
2
+
3
+ ## Strategy Selection
4
+ - Token bucket for API endpoints
5
+ - Sliding window for auth attempts
6
+ - Fixed window for simple limits
7
+ - Leaky bucket for smoothing bursts
8
+
9
+ ## Implementation
10
+ ```typescript
11
+ // Redis-based rate limiter
12
+ import { Ratelimit } from '@upstash/ratelimit';
13
+ import { Redis } from '@upstash/redis';
14
+
15
+ const ratelimit = new Ratelimit({
16
+ redis: Redis.fromEnv(),
17
+ limiter: Ratelimit.slidingWindow(10, '10 s'),
18
+ analytics: true,
19
+ prefix: 'api',
20
+ });
21
+
22
+ // Middleware
23
+ export async function rateLimitMiddleware(req: Request) {
24
+ const ip = req.headers.get('x-forwarded-for') ?? '127.0.0.1';
25
+ const { success, limit, reset, remaining } = await ratelimit.limit(ip);
26
+
27
+ if (!success) {
28
+ return new Response('Rate limit exceeded', {
29
+ status: 429,
30
+ headers: {
31
+ 'X-RateLimit-Limit': limit.toString(),
32
+ 'X-RateLimit-Remaining': remaining.toString(),
33
+ 'X-RateLimit-Reset': reset.toString(),
34
+ 'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString(),
35
+ },
36
+ });
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Tiered Limits
42
+ ```typescript
43
+ const RATE_LIMITS = {
44
+ free: { requests: 100, window: '1 h' },
45
+ pro: { requests: 1000, window: '1 h' },
46
+ enterprise: { requests: 10000, window: '1 h' },
47
+ };
48
+
49
+ async function getRateLimit(userId: string) {
50
+ const user = await getUser(userId);
51
+ return RATE_LIMITS[user.plan];
52
+ }
53
+ ```
54
+
55
+ ## Headers to Return
56
+ - `X-RateLimit-Limit`: Total allowed requests
57
+ - `X-RateLimit-Remaining`: Requests left in window
58
+ - `X-RateLimit-Reset`: Unix timestamp when limit resets
59
+ - `Retry-After`: Seconds until retry (on 429)
@@ -0,0 +1,58 @@
1
+ # Structured Logging Rules
2
+
3
+ ## Log Levels
4
+ - ERROR: System failures, requires immediate action
5
+ - WARN: Unexpected but recoverable situations
6
+ - INFO: Business events, request lifecycle
7
+ - DEBUG: Detailed diagnostic information
8
+
9
+ ## Structured Format
10
+ ```typescript
11
+ import pino from 'pino';
12
+
13
+ const logger = pino({
14
+ level: process.env.LOG_LEVEL || 'info',
15
+ formatters: {
16
+ level: (label) => ({ level: label }),
17
+ },
18
+ timestamp: pino.stdTimeFunctions.isoTime,
19
+ base: {
20
+ service: 'ultra-dex',
21
+ version: process.env.VERSION,
22
+ env: process.env.NODE_ENV,
23
+ },
24
+ });
25
+
26
+ // Usage
27
+ logger.info({ userId, action: 'login' }, 'User logged in');
28
+ logger.error({ err, requestId }, 'Payment failed');
29
+ ```
30
+
31
+ ## Request Context
32
+ ```typescript
33
+ // Add request ID to all logs
34
+ import { AsyncLocalStorage } from 'async_hooks';
35
+
36
+ const requestContext = new AsyncLocalStorage<{ requestId: string }>();
37
+
38
+ export function withRequestId(req: Request, fn: () => Promise<Response>) {
39
+ const requestId = req.headers.get('x-request-id') || crypto.randomUUID();
40
+ return requestContext.run({ requestId }, fn);
41
+ }
42
+
43
+ export function getRequestId() {
44
+ return requestContext.getStore()?.requestId;
45
+ }
46
+ ```
47
+
48
+ ## What to Log
49
+ - Request: method, path, user, duration, status
50
+ - Errors: stack, context, correlation ID
51
+ - Business: user actions, state changes, payments
52
+ - Never log: passwords, tokens, PII, credit cards
53
+
54
+ ## Log Aggregation
55
+ - Use JSON format for machine parsing
56
+ - Send to centralized service (Datadog, Logtail, Axiom)
57
+ - Set up alerts for error rate spikes
58
+ - Implement log sampling for high-volume endpoints
@@ -0,0 +1,70 @@
1
+ # Caching Strategy Rules
2
+
3
+ ## Cache Layers
4
+ 1. Browser cache (Cache-Control headers)
5
+ 2. CDN/Edge cache (Vercel, Cloudflare)
6
+ 3. Application cache (Redis, in-memory)
7
+ 4. Database cache (query cache, materialized views)
8
+
9
+ ## HTTP Caching
10
+ ```typescript
11
+ // Next.js cache headers
12
+ export async function GET() {
13
+ const data = await fetchData();
14
+
15
+ return Response.json(data, {
16
+ headers: {
17
+ 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
18
+ },
19
+ });
20
+ }
21
+
22
+ // Static pages
23
+ export const revalidate = 3600; // ISR: revalidate every hour
24
+ ```
25
+
26
+ ## Redis Caching
27
+ ```typescript
28
+ import { Redis } from '@upstash/redis';
29
+
30
+ const redis = Redis.fromEnv();
31
+
32
+ async function getCached<T>(
33
+ key: string,
34
+ fetcher: () => Promise<T>,
35
+ ttl = 3600
36
+ ): Promise<T> {
37
+ const cached = await redis.get<T>(key);
38
+ if (cached) return cached;
39
+
40
+ const fresh = await fetcher();
41
+ await redis.set(key, fresh, { ex: ttl });
42
+ return fresh;
43
+ }
44
+
45
+ // Usage
46
+ const user = await getCached(
47
+ `user:${userId}`,
48
+ () => db.user.findUnique({ where: { id: userId } }),
49
+ 300
50
+ );
51
+ ```
52
+
53
+ ## Cache Invalidation
54
+ ```typescript
55
+ // Event-driven invalidation
56
+ async function updateUser(userId: string, data: UpdateData) {
57
+ await db.user.update({ where: { id: userId }, data });
58
+
59
+ // Invalidate related caches
60
+ await redis.del(`user:${userId}`);
61
+ await redis.del(`user:${userId}:profile`);
62
+ await redis.del(`team:${data.teamId}:members`);
63
+ }
64
+ ```
65
+
66
+ ## Cache Patterns
67
+ - Cache-aside: App manages cache
68
+ - Write-through: Write to cache + DB
69
+ - Write-behind: Write to cache, async DB
70
+ - Read-through: Cache fetches on miss
@@ -0,0 +1,67 @@
1
+ # Form Handling Rules
2
+
3
+ ## Server Actions (Next.js 15)
4
+ ```typescript
5
+ 'use server';
6
+
7
+ import { z } from 'zod';
8
+ import { revalidatePath } from 'next/cache';
9
+
10
+ const schema = z.object({
11
+ email: z.string().email(),
12
+ name: z.string().min(2).max(100),
13
+ });
14
+
15
+ export async function createUser(formData: FormData) {
16
+ const parsed = schema.safeParse({
17
+ email: formData.get('email'),
18
+ name: formData.get('name'),
19
+ });
20
+
21
+ if (!parsed.success) {
22
+ return { error: parsed.error.flatten().fieldErrors };
23
+ }
24
+
25
+ await db.user.create({ data: parsed.data });
26
+ revalidatePath('/users');
27
+ return { success: true };
28
+ }
29
+ ```
30
+
31
+ ## Client Form Component
32
+ ```tsx
33
+ 'use client';
34
+
35
+ import { useActionState } from 'react';
36
+ import { createUser } from './actions';
37
+
38
+ export function CreateUserForm() {
39
+ const [state, formAction, pending] = useActionState(createUser, null);
40
+
41
+ return (
42
+ <form action={formAction}>
43
+ <input name="email" type="email" required />
44
+ {state?.error?.email && <p className="error">{state.error.email}</p>}
45
+
46
+ <input name="name" required />
47
+ {state?.error?.name && <p className="error">{state.error.name}</p>}
48
+
49
+ <button disabled={pending}>
50
+ {pending ? 'Creating...' : 'Create User'}
51
+ </button>
52
+ </form>
53
+ );
54
+ }
55
+ ```
56
+
57
+ ## Validation
58
+ - Client: HTML5 validation + real-time feedback
59
+ - Server: Always re-validate with Zod
60
+ - Sanitize: Strip HTML, trim whitespace
61
+ - Rate limit: Form submissions per IP
62
+
63
+ ## File Uploads
64
+ - Use presigned URLs for large files
65
+ - Validate file type on both client and server
66
+ - Set maximum file size limits
67
+ - Scan uploads for malware in production
@@ -0,0 +1,78 @@
1
+ # File Upload Rules
2
+
3
+ ## Presigned URL Pattern
4
+ ```typescript
5
+ // Server action to get upload URL
6
+ 'use server';
7
+
8
+ import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
9
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
10
+
11
+ const s3 = new S3Client({ region: process.env.AWS_REGION });
12
+
13
+ export async function getUploadUrl(filename: string, contentType: string) {
14
+ const key = `uploads/${crypto.randomUUID()}/${filename}`;
15
+
16
+ const command = new PutObjectCommand({
17
+ Bucket: process.env.S3_BUCKET,
18
+ Key: key,
19
+ ContentType: contentType,
20
+ });
21
+
22
+ const url = await getSignedUrl(s3, command, { expiresIn: 3600 });
23
+ return { url, key };
24
+ }
25
+ ```
26
+
27
+ ## Client Upload
28
+ ```typescript
29
+ async function uploadFile(file: File) {
30
+ // 1. Get presigned URL
31
+ const { url, key } = await getUploadUrl(file.name, file.type);
32
+
33
+ // 2. Upload directly to S3
34
+ await fetch(url, {
35
+ method: 'PUT',
36
+ body: file,
37
+ headers: { 'Content-Type': file.type },
38
+ });
39
+
40
+ // 3. Save reference to database
41
+ await saveFileReference(key);
42
+ }
43
+ ```
44
+
45
+ ## Validation
46
+ ```typescript
47
+ const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
48
+ const MAX_SIZE = 10 * 1024 * 1024; // 10MB
49
+
50
+ function validateFile(file: File) {
51
+ if (!ALLOWED_TYPES.includes(file.type)) {
52
+ throw new Error('Invalid file type');
53
+ }
54
+ if (file.size > MAX_SIZE) {
55
+ throw new Error('File too large');
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Image Processing
61
+ ```typescript
62
+ // Process uploaded images
63
+ import sharp from 'sharp';
64
+
65
+ async function processImage(buffer: Buffer) {
66
+ return sharp(buffer)
67
+ .resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
68
+ .webp({ quality: 80 })
69
+ .toBuffer();
70
+ }
71
+ ```
72
+
73
+ ## Security
74
+ - Validate MIME type server-side (don't trust Content-Type)
75
+ - Generate random filenames to prevent enumeration
76
+ - Serve uploads from separate domain/CDN
77
+ - Set Content-Disposition: attachment for downloads
78
+ - Scan files with antivirus in production
@@ -0,0 +1,100 @@
1
+ # WebSocket & Real-time Rules
2
+
3
+ ## Connection Management
4
+ ```typescript
5
+ // Server setup with ws
6
+ import { WebSocketServer } from 'ws';
7
+
8
+ const wss = new WebSocketServer({ port: 8080 });
9
+ const clients = new Map<string, WebSocket>();
10
+
11
+ wss.on('connection', (ws, req) => {
12
+ const userId = authenticateFromHeaders(req);
13
+ if (!userId) return ws.close(4001, 'Unauthorized');
14
+
15
+ clients.set(userId, ws);
16
+
17
+ ws.on('message', (data) => handleMessage(userId, data));
18
+ ws.on('close', () => clients.delete(userId));
19
+ ws.on('error', (err) => console.error('WS error:', err));
20
+
21
+ // Heartbeat
22
+ ws.isAlive = true;
23
+ ws.on('pong', () => { ws.isAlive = true; });
24
+ });
25
+
26
+ // Ping interval
27
+ setInterval(() => {
28
+ wss.clients.forEach((ws) => {
29
+ if (!ws.isAlive) return ws.terminate();
30
+ ws.isAlive = false;
31
+ ws.ping();
32
+ });
33
+ }, 30000);
34
+ ```
35
+
36
+ ## Message Protocol
37
+ ```typescript
38
+ interface WSMessage {
39
+ type: string;
40
+ payload: unknown;
41
+ timestamp: number;
42
+ correlationId?: string;
43
+ }
44
+
45
+ function handleMessage(userId: string, data: Buffer) {
46
+ const message: WSMessage = JSON.parse(data.toString());
47
+
48
+ switch (message.type) {
49
+ case 'subscribe':
50
+ subscribeToChannel(userId, message.payload.channel);
51
+ break;
52
+ case 'unsubscribe':
53
+ unsubscribeFromChannel(userId, message.payload.channel);
54
+ break;
55
+ default:
56
+ sendError(userId, 'Unknown message type');
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Broadcasting
62
+ ```typescript
63
+ // Pub/sub with Redis for horizontal scaling
64
+ import { Redis } from 'ioredis';
65
+
66
+ const pub = new Redis(process.env.REDIS_URL);
67
+ const sub = new Redis(process.env.REDIS_URL);
68
+
69
+ sub.subscribe('notifications');
70
+ sub.on('message', (channel, message) => {
71
+ const { userIds, data } = JSON.parse(message);
72
+ userIds.forEach((userId: string) => {
73
+ clients.get(userId)?.send(JSON.stringify(data));
74
+ });
75
+ });
76
+
77
+ export function broadcast(userIds: string[], data: unknown) {
78
+ pub.publish('notifications', JSON.stringify({ userIds, data }));
79
+ }
80
+ ```
81
+
82
+ ## Client Reconnection
83
+ ```typescript
84
+ function createReconnectingSocket(url: string) {
85
+ let ws: WebSocket;
86
+ let reconnectDelay = 1000;
87
+
88
+ function connect() {
89
+ ws = new WebSocket(url);
90
+ ws.onopen = () => { reconnectDelay = 1000; };
91
+ ws.onclose = () => {
92
+ setTimeout(connect, reconnectDelay);
93
+ reconnectDelay = Math.min(reconnectDelay * 2, 30000);
94
+ };
95
+ }
96
+
97
+ connect();
98
+ return { send: (data: unknown) => ws.send(JSON.stringify(data)) };
99
+ }
100
+ ```