@girardmedia/bootspring 3.3.2 → 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.
- package/assets/agents/accessibility-auditor.md +39 -0
- package/assets/agents/api-designer.md +40 -0
- package/assets/agents/auth-implementer.md +64 -0
- package/assets/agents/bug-hunter.md +42 -0
- package/assets/agents/bundle-analyzer.md +40 -0
- package/assets/agents/cache-optimizer.md +55 -0
- package/assets/agents/changelog-writer.md +55 -0
- package/assets/agents/ci-cd-builder.md +40 -0
- package/assets/agents/code-explainer.md +39 -0
- package/assets/agents/code-reviewer.md +39 -0
- package/assets/agents/cost-optimizer.md +57 -0
- package/assets/agents/cron-scheduler.md +51 -0
- package/assets/agents/data-seeder.md +56 -0
- package/assets/agents/database-architect.md +40 -0
- package/assets/agents/dependency-updater.md +40 -0
- package/assets/agents/deploy-checker.md +40 -0
- package/assets/agents/docker-optimizer.md +40 -0
- package/assets/agents/documentation-writer.md +40 -0
- package/assets/agents/email-builder.md +55 -0
- package/assets/agents/env-setup.md +40 -0
- package/assets/agents/error-handler.md +40 -0
- package/assets/agents/eslint-fixer.md +46 -0
- package/assets/agents/feature-flagger.md +69 -0
- package/assets/agents/git-detective.md +39 -0
- package/assets/agents/graphql-builder.md +60 -0
- package/assets/agents/incident-responder.md +59 -0
- package/assets/agents/log-analyzer.md +39 -0
- package/assets/agents/migration-planner.md +41 -0
- package/assets/agents/monorepo-navigator.md +39 -0
- package/assets/agents/nextjs-expert.md +57 -0
- package/assets/agents/notification-builder.md +56 -0
- package/assets/agents/onboarding-guide.md +39 -0
- package/assets/agents/performance-profiler.md +40 -0
- package/assets/agents/prisma-expert.md +57 -0
- package/assets/agents/rate-limiter.md +58 -0
- package/assets/agents/react-expert.md +58 -0
- package/assets/agents/refactorer.md +42 -0
- package/assets/agents/regex-builder.md +46 -0
- package/assets/agents/release-manager.md +40 -0
- package/assets/agents/s3-manager.md +58 -0
- package/assets/agents/schema-validator.md +40 -0
- package/assets/agents/search-builder.md +62 -0
- package/assets/agents/security-auditor.md +39 -0
- package/assets/agents/sitemap-generator.md +53 -0
- package/assets/agents/stripe-integrator.md +59 -0
- package/assets/agents/tailwind-expert.md +55 -0
- package/assets/agents/tech-debt-tracker.md +39 -0
- package/assets/agents/test-writer.md +42 -0
- package/assets/agents/type-fixer.md +45 -0
- package/assets/agents/webhook-builder.md +54 -0
- package/assets/rules/cpp.md +53 -0
- package/assets/rules/css.md +52 -0
- package/assets/rules/go.md +50 -0
- package/assets/rules/html.md +52 -0
- package/assets/rules/java.md +51 -0
- package/assets/rules/kotlin.md +50 -0
- package/assets/rules/php.md +51 -0
- package/assets/rules/python.md +51 -0
- package/assets/rules/ruby.md +51 -0
- package/assets/rules/rust.md +49 -0
- package/assets/rules/shell.md +52 -0
- package/assets/rules/sql.md +49 -0
- package/assets/rules/swift.md +50 -0
- package/assets/rules/typescript.md +52 -0
- package/assets/rules/yaml-json.md +51 -0
- package/assets/skills/accessibility.md +210 -0
- package/assets/skills/agent-patterns.md +387 -0
- package/assets/skills/ai-integration.md +263 -0
- package/assets/skills/animation-patterns.md +224 -0
- package/assets/skills/api-design.md +218 -0
- package/assets/skills/api-gateway.md +341 -0
- package/assets/skills/api-versioning.md +226 -0
- package/assets/skills/astro-patterns.md +233 -0
- package/assets/skills/auth-patterns.md +248 -0
- package/assets/skills/aws-patterns.md +171 -0
- package/assets/skills/background-jobs.md +162 -0
- package/assets/skills/browser-extensions.md +309 -0
- package/assets/skills/caching-patterns.md +253 -0
- package/assets/skills/ci-cd.md +251 -0
- package/assets/skills/cli-development.md +296 -0
- package/assets/skills/code-review.md +185 -0
- package/assets/skills/cron-patterns.md +327 -0
- package/assets/skills/data-fetching.md +231 -0
- package/assets/skills/database-migrations.md +346 -0
- package/assets/skills/database-patterns.md +219 -0
- package/assets/skills/debugging.md +281 -0
- package/assets/skills/design-system.md +289 -0
- package/assets/skills/django-patterns.md +182 -0
- package/assets/skills/docker-patterns.md +235 -0
- package/assets/skills/e2e-testing.md +287 -0
- package/assets/skills/edge-computing.md +268 -0
- package/assets/skills/electron-patterns.md +266 -0
- package/assets/skills/email-templates.md +206 -0
- package/assets/skills/error-handling.md +265 -0
- package/assets/skills/event-driven.md +232 -0
- package/assets/skills/express-patterns.md +239 -0
- package/assets/skills/fastapi-patterns.md +198 -0
- package/assets/skills/feature-flags.md +212 -0
- package/assets/skills/figma-to-code.md +298 -0
- package/assets/skills/file-upload.md +228 -0
- package/assets/skills/forms-patterns.md +264 -0
- package/assets/skills/gcp-patterns.md +189 -0
- package/assets/skills/git-workflow.md +187 -0
- package/assets/skills/golang-patterns.md +185 -0
- package/assets/skills/graphql-patterns.md +244 -0
- package/assets/skills/i18n-patterns.md +172 -0
- package/assets/skills/image-processing.md +350 -0
- package/assets/skills/java-springboot.md +226 -0
- package/assets/skills/kotlin-patterns.md +207 -0
- package/assets/skills/kubernetes-patterns.md +326 -0
- package/assets/skills/laravel-patterns.md +261 -0
- package/assets/skills/llm-fine-tuning.md +335 -0
- package/assets/skills/load-testing.md +303 -0
- package/assets/skills/logging-observability.md +228 -0
- package/assets/skills/markdown-processing.md +318 -0
- package/assets/skills/mcp-server-patterns.md +292 -0
- package/assets/skills/microservices.md +272 -0
- package/assets/skills/migration-patterns.md +239 -0
- package/assets/skills/mongodb-patterns.md +189 -0
- package/assets/skills/monorepo-patterns.md +287 -0
- package/assets/skills/nextjs-app-router.md +237 -0
- package/assets/skills/notification-patterns.md +348 -0
- package/assets/skills/oauth-patterns.md +246 -0
- package/assets/skills/payment-integration.md +222 -0
- package/assets/skills/pdf-generation.md +307 -0
- package/assets/skills/performance-optimization.md +277 -0
- package/assets/skills/php-patterns.md +210 -0
- package/assets/skills/prisma-patterns.md +241 -0
- package/assets/skills/prompt-engineering.md +193 -0
- package/assets/skills/pwa-patterns.md +247 -0
- package/assets/skills/python-patterns.md +158 -0
- package/assets/skills/python-testing.md +172 -0
- package/assets/skills/queue-patterns.md +295 -0
- package/assets/skills/rag-patterns.md +159 -0
- package/assets/skills/rate-limiting.md +319 -0
- package/assets/skills/react-components.md +201 -0
- package/assets/skills/react-native-patterns.md +299 -0
- package/assets/skills/real-time-patterns.md +181 -0
- package/assets/skills/redis-patterns.md +188 -0
- package/assets/skills/refactoring.md +218 -0
- package/assets/skills/regex-patterns.md +191 -0
- package/assets/skills/remix-patterns.md +262 -0
- package/assets/skills/responsive-design.md +199 -0
- package/assets/skills/ruby-rails-patterns.md +178 -0
- package/assets/skills/rust-patterns.md +211 -0
- package/assets/skills/search-patterns.md +227 -0
- package/assets/skills/security-hardening.md +237 -0
- package/assets/skills/seo-patterns.md +179 -0
- package/assets/skills/serverless-patterns.md +223 -0
- package/assets/skills/sql-optimization.md +154 -0
- package/assets/skills/state-management.md +254 -0
- package/assets/skills/storybook-patterns.md +330 -0
- package/assets/skills/svelte-patterns.md +258 -0
- package/assets/skills/swift-patterns.md +227 -0
- package/assets/skills/tailwind-patterns.md +272 -0
- package/assets/skills/tdd-workflow.md +199 -0
- package/assets/skills/terraform-patterns.md +270 -0
- package/assets/skills/testing-react.md +240 -0
- package/assets/skills/testing-vitest.md +232 -0
- package/assets/skills/typescript-strict.md +159 -0
- package/assets/skills/video-processing.md +340 -0
- package/assets/skills/vue-patterns.md +247 -0
- package/assets/skills/web-workers.md +327 -0
- package/assets/skills/webhooks-patterns.md +283 -0
- package/assets/skills/websocket-patterns.md +306 -0
- package/dist/cli/index.js +941 -958
- package/dist/core/index.d.ts +341 -11
- package/dist/core.js +58 -95
- package/dist/mcp/index.d.ts +33 -1
- package/dist/mcp-server.js +177 -255
- package/package.json +4 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: websocket-patterns
|
|
3
|
+
description: WebSocket patterns for connection management, heartbeat, reconnection, rooms, binary data, and scaling.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# WebSocket Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Apply these patterns when building real-time features: chat, live dashboards,
|
|
11
|
+
collaborative editing, notifications, or streaming data. Use this skill for
|
|
12
|
+
managing connections reliably, implementing heartbeat/ping-pong, handling
|
|
13
|
+
reconnection on the client, organizing connections into rooms, and scaling
|
|
14
|
+
across multiple server instances.
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
### Connection Manager
|
|
19
|
+
|
|
20
|
+
Track active connections in a Map keyed by user/session ID. Handle connect,
|
|
21
|
+
disconnect, and broadcast operations. Clean up stale connections.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
interface Connection {
|
|
25
|
+
ws: WebSocket
|
|
26
|
+
userId: string
|
|
27
|
+
connectedAt: number
|
|
28
|
+
lastPing: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class ConnectionManager {
|
|
32
|
+
private connections = new Map<string, Connection>()
|
|
33
|
+
|
|
34
|
+
add(userId: string, ws: WebSocket): void {
|
|
35
|
+
// Close existing connection for same user (prevent duplicates)
|
|
36
|
+
const existing = this.connections.get(userId)
|
|
37
|
+
if (existing) {
|
|
38
|
+
existing.ws.close(4000, 'Replaced by new connection')
|
|
39
|
+
}
|
|
40
|
+
this.connections.set(userId, {
|
|
41
|
+
ws, userId, connectedAt: Date.now(), lastPing: Date.now(),
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
remove(userId: string): void {
|
|
46
|
+
this.connections.delete(userId)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
send(userId: string, data: unknown): boolean {
|
|
50
|
+
const conn = this.connections.get(userId)
|
|
51
|
+
if (conn && conn.ws.readyState === WebSocket.OPEN) {
|
|
52
|
+
conn.ws.send(JSON.stringify(data))
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
broadcast(data: unknown, exclude?: string): void {
|
|
59
|
+
const payload = JSON.stringify(data)
|
|
60
|
+
for (const [userId, conn] of this.connections) {
|
|
61
|
+
if (userId !== exclude && conn.ws.readyState === WebSocket.OPEN) {
|
|
62
|
+
conn.ws.send(payload)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get size(): number {
|
|
68
|
+
return this.connections.size
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Heartbeat (Server-Side Ping/Pong)
|
|
74
|
+
|
|
75
|
+
Send periodic pings to detect dead connections. Close connections that miss
|
|
76
|
+
two consecutive pongs. Run the heartbeat on a fixed interval.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
class HeartbeatMonitor {
|
|
80
|
+
private interval: NodeJS.Timeout | null = null
|
|
81
|
+
|
|
82
|
+
start(manager: ConnectionManager, intervalMs = 30_000): void {
|
|
83
|
+
this.interval = setInterval(() => {
|
|
84
|
+
const now = Date.now()
|
|
85
|
+
const staleThreshold = now - intervalMs * 2
|
|
86
|
+
|
|
87
|
+
for (const [userId, conn] of manager.allConnections()) {
|
|
88
|
+
if (conn.lastPing < staleThreshold) {
|
|
89
|
+
console.log(`Closing stale connection: ${userId}`)
|
|
90
|
+
conn.ws.terminate()
|
|
91
|
+
manager.remove(userId)
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
if (conn.ws.readyState === WebSocket.OPEN) {
|
|
95
|
+
conn.ws.ping()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}, intervalMs)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
stop(): void {
|
|
102
|
+
if (this.interval) clearInterval(this.interval)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// On the WebSocket server:
|
|
107
|
+
wss.on('connection', (ws, req) => {
|
|
108
|
+
const userId = authenticate(req)
|
|
109
|
+
manager.add(userId, ws)
|
|
110
|
+
|
|
111
|
+
ws.on('pong', () => {
|
|
112
|
+
manager.updatePing(userId)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
ws.on('close', () => {
|
|
116
|
+
manager.remove(userId)
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Client-Side Reconnection
|
|
122
|
+
|
|
123
|
+
Implement exponential backoff with jitter. Track connection state for UI feedback.
|
|
124
|
+
Re-authenticate after reconnecting.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
class ReconnectingWebSocket {
|
|
128
|
+
private ws: WebSocket | null = null
|
|
129
|
+
private attempt = 0
|
|
130
|
+
private maxAttempts = 10
|
|
131
|
+
private baseDelay = 1000
|
|
132
|
+
private maxDelay = 30_000
|
|
133
|
+
|
|
134
|
+
constructor(
|
|
135
|
+
private url: string,
|
|
136
|
+
private onMessage: (data: unknown) => void,
|
|
137
|
+
private onStateChange: (state: 'connecting' | 'open' | 'closed') => void,
|
|
138
|
+
) {}
|
|
139
|
+
|
|
140
|
+
connect(): void {
|
|
141
|
+
this.onStateChange('connecting')
|
|
142
|
+
this.ws = new WebSocket(this.url)
|
|
143
|
+
|
|
144
|
+
this.ws.onopen = () => {
|
|
145
|
+
this.attempt = 0
|
|
146
|
+
this.onStateChange('open')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.ws.onmessage = (event) => {
|
|
150
|
+
this.onMessage(JSON.parse(event.data))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.ws.onclose = (event) => {
|
|
154
|
+
this.onStateChange('closed')
|
|
155
|
+
if (event.code !== 4000 && this.attempt < this.maxAttempts) {
|
|
156
|
+
this.scheduleReconnect()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private scheduleReconnect(): void {
|
|
162
|
+
const delay = Math.min(
|
|
163
|
+
this.baseDelay * Math.pow(2, this.attempt) + Math.random() * 1000,
|
|
164
|
+
this.maxDelay,
|
|
165
|
+
)
|
|
166
|
+
this.attempt++
|
|
167
|
+
setTimeout(() => this.connect(), delay)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
send(data: unknown): void {
|
|
171
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
172
|
+
this.ws.send(JSON.stringify(data))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
close(): void {
|
|
177
|
+
this.ws?.close(4000, 'Client closed')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Room-Based Broadcasting
|
|
183
|
+
|
|
184
|
+
Group connections by topic/channel. Users can join/leave rooms. Broadcast to
|
|
185
|
+
all members of a room.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
class RoomManager {
|
|
189
|
+
private rooms = new Map<string, Set<string>>() // roomId -> Set<userId>
|
|
190
|
+
|
|
191
|
+
join(roomId: string, userId: string): void {
|
|
192
|
+
if (!this.rooms.has(roomId)) {
|
|
193
|
+
this.rooms.set(roomId, new Set())
|
|
194
|
+
}
|
|
195
|
+
this.rooms.get(roomId)!.add(userId)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
leave(roomId: string, userId: string): void {
|
|
199
|
+
this.rooms.get(roomId)?.delete(userId)
|
|
200
|
+
if (this.rooms.get(roomId)?.size === 0) {
|
|
201
|
+
this.rooms.delete(roomId)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
leaveAll(userId: string): void {
|
|
206
|
+
for (const [roomId, members] of this.rooms) {
|
|
207
|
+
members.delete(userId)
|
|
208
|
+
if (members.size === 0) this.rooms.delete(roomId)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
broadcast(roomId: string, data: unknown, connManager: ConnectionManager, exclude?: string): void {
|
|
213
|
+
const members = this.rooms.get(roomId)
|
|
214
|
+
if (!members) return
|
|
215
|
+
for (const userId of members) {
|
|
216
|
+
if (userId !== exclude) {
|
|
217
|
+
connManager.send(userId, data)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
members(roomId: string): string[] {
|
|
223
|
+
return [...(this.rooms.get(roomId) ?? [])]
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Message Protocol
|
|
229
|
+
|
|
230
|
+
Define a typed message protocol. Use a `type` field for routing. Include a
|
|
231
|
+
message ID for acknowledgment.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
type ClientMessage =
|
|
235
|
+
| { type: 'join'; roomId: string }
|
|
236
|
+
| { type: 'leave'; roomId: string }
|
|
237
|
+
| { type: 'chat'; roomId: string; text: string; msgId: string }
|
|
238
|
+
| { type: 'ping' }
|
|
239
|
+
|
|
240
|
+
type ServerMessage =
|
|
241
|
+
| { type: 'chat'; roomId: string; text: string; userId: string; timestamp: number }
|
|
242
|
+
| { type: 'ack'; msgId: string }
|
|
243
|
+
| { type: 'error'; code: string; message: string }
|
|
244
|
+
| { type: 'members'; roomId: string; users: string[] }
|
|
245
|
+
| { type: 'pong' }
|
|
246
|
+
|
|
247
|
+
function handleMessage(userId: string, raw: string): void {
|
|
248
|
+
const msg: ClientMessage = JSON.parse(raw)
|
|
249
|
+
switch (msg.type) {
|
|
250
|
+
case 'join': rooms.join(msg.roomId, userId); break
|
|
251
|
+
case 'leave': rooms.leave(msg.roomId, userId); break
|
|
252
|
+
case 'chat': handleChat(userId, msg); break
|
|
253
|
+
case 'ping': connManager.send(userId, { type: 'pong' }); break
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Scaling with Redis Pub/Sub
|
|
259
|
+
|
|
260
|
+
When running multiple server instances, use Redis pub/sub to fan out messages
|
|
261
|
+
across all instances. Each instance subscribes to relevant channels.
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import Redis from 'ioredis'
|
|
265
|
+
|
|
266
|
+
const pub = new Redis(REDIS_URL)
|
|
267
|
+
const sub = new Redis(REDIS_URL)
|
|
268
|
+
|
|
269
|
+
// Subscribe to room channels
|
|
270
|
+
sub.on('message', (channel, message) => {
|
|
271
|
+
const roomId = channel.replace('room:', '')
|
|
272
|
+
const data = JSON.parse(message)
|
|
273
|
+
rooms.broadcast(roomId, data, connManager)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
function publishToRoom(roomId: string, data: unknown): void {
|
|
277
|
+
pub.publish(`room:${roomId}`, JSON.stringify(data))
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Examples
|
|
282
|
+
|
|
283
|
+
**Pattern: Binary data with ArrayBuffer**
|
|
284
|
+
```typescript
|
|
285
|
+
ws.binaryType = 'arraybuffer'
|
|
286
|
+
ws.onmessage = (event) => {
|
|
287
|
+
if (event.data instanceof ArrayBuffer) {
|
|
288
|
+
const view = new DataView(event.data)
|
|
289
|
+
const messageType = view.getUint8(0)
|
|
290
|
+
// handle binary protocol
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Checklist
|
|
296
|
+
|
|
297
|
+
- [ ] Connection manager tracks all active connections with metadata
|
|
298
|
+
- [ ] Server-side ping/pong heartbeat detects and cleans up dead connections
|
|
299
|
+
- [ ] Client reconnects with exponential backoff + jitter
|
|
300
|
+
- [ ] Connections closed with meaningful close codes (4000+ for application codes)
|
|
301
|
+
- [ ] Room manager for topic-based broadcasting with join/leave/leaveAll
|
|
302
|
+
- [ ] Typed message protocol with `type` field for routing
|
|
303
|
+
- [ ] Message acknowledgments (`ack`) for delivery confirmation
|
|
304
|
+
- [ ] Redis pub/sub for multi-instance broadcasting
|
|
305
|
+
- [ ] Authentication on initial connection (token in query string or first message)
|
|
306
|
+
- [ ] Connection state exposed to UI (`connecting`, `open`, `closed`)
|