@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.
Files changed (171) hide show
  1. package/assets/agents/accessibility-auditor.md +39 -0
  2. package/assets/agents/api-designer.md +40 -0
  3. package/assets/agents/auth-implementer.md +64 -0
  4. package/assets/agents/bug-hunter.md +42 -0
  5. package/assets/agents/bundle-analyzer.md +40 -0
  6. package/assets/agents/cache-optimizer.md +55 -0
  7. package/assets/agents/changelog-writer.md +55 -0
  8. package/assets/agents/ci-cd-builder.md +40 -0
  9. package/assets/agents/code-explainer.md +39 -0
  10. package/assets/agents/code-reviewer.md +39 -0
  11. package/assets/agents/cost-optimizer.md +57 -0
  12. package/assets/agents/cron-scheduler.md +51 -0
  13. package/assets/agents/data-seeder.md +56 -0
  14. package/assets/agents/database-architect.md +40 -0
  15. package/assets/agents/dependency-updater.md +40 -0
  16. package/assets/agents/deploy-checker.md +40 -0
  17. package/assets/agents/docker-optimizer.md +40 -0
  18. package/assets/agents/documentation-writer.md +40 -0
  19. package/assets/agents/email-builder.md +55 -0
  20. package/assets/agents/env-setup.md +40 -0
  21. package/assets/agents/error-handler.md +40 -0
  22. package/assets/agents/eslint-fixer.md +46 -0
  23. package/assets/agents/feature-flagger.md +69 -0
  24. package/assets/agents/git-detective.md +39 -0
  25. package/assets/agents/graphql-builder.md +60 -0
  26. package/assets/agents/incident-responder.md +59 -0
  27. package/assets/agents/log-analyzer.md +39 -0
  28. package/assets/agents/migration-planner.md +41 -0
  29. package/assets/agents/monorepo-navigator.md +39 -0
  30. package/assets/agents/nextjs-expert.md +57 -0
  31. package/assets/agents/notification-builder.md +56 -0
  32. package/assets/agents/onboarding-guide.md +39 -0
  33. package/assets/agents/performance-profiler.md +40 -0
  34. package/assets/agents/prisma-expert.md +57 -0
  35. package/assets/agents/rate-limiter.md +58 -0
  36. package/assets/agents/react-expert.md +58 -0
  37. package/assets/agents/refactorer.md +42 -0
  38. package/assets/agents/regex-builder.md +46 -0
  39. package/assets/agents/release-manager.md +40 -0
  40. package/assets/agents/s3-manager.md +58 -0
  41. package/assets/agents/schema-validator.md +40 -0
  42. package/assets/agents/search-builder.md +62 -0
  43. package/assets/agents/security-auditor.md +39 -0
  44. package/assets/agents/sitemap-generator.md +53 -0
  45. package/assets/agents/stripe-integrator.md +59 -0
  46. package/assets/agents/tailwind-expert.md +55 -0
  47. package/assets/agents/tech-debt-tracker.md +39 -0
  48. package/assets/agents/test-writer.md +42 -0
  49. package/assets/agents/type-fixer.md +45 -0
  50. package/assets/agents/webhook-builder.md +54 -0
  51. package/assets/rules/cpp.md +53 -0
  52. package/assets/rules/css.md +52 -0
  53. package/assets/rules/go.md +50 -0
  54. package/assets/rules/html.md +52 -0
  55. package/assets/rules/java.md +51 -0
  56. package/assets/rules/kotlin.md +50 -0
  57. package/assets/rules/php.md +51 -0
  58. package/assets/rules/python.md +51 -0
  59. package/assets/rules/ruby.md +51 -0
  60. package/assets/rules/rust.md +49 -0
  61. package/assets/rules/shell.md +52 -0
  62. package/assets/rules/sql.md +49 -0
  63. package/assets/rules/swift.md +50 -0
  64. package/assets/rules/typescript.md +52 -0
  65. package/assets/rules/yaml-json.md +51 -0
  66. package/assets/skills/accessibility.md +210 -0
  67. package/assets/skills/agent-patterns.md +387 -0
  68. package/assets/skills/ai-integration.md +263 -0
  69. package/assets/skills/animation-patterns.md +224 -0
  70. package/assets/skills/api-design.md +218 -0
  71. package/assets/skills/api-gateway.md +341 -0
  72. package/assets/skills/api-versioning.md +226 -0
  73. package/assets/skills/astro-patterns.md +233 -0
  74. package/assets/skills/auth-patterns.md +248 -0
  75. package/assets/skills/aws-patterns.md +171 -0
  76. package/assets/skills/background-jobs.md +162 -0
  77. package/assets/skills/browser-extensions.md +309 -0
  78. package/assets/skills/caching-patterns.md +253 -0
  79. package/assets/skills/ci-cd.md +251 -0
  80. package/assets/skills/cli-development.md +296 -0
  81. package/assets/skills/code-review.md +185 -0
  82. package/assets/skills/cron-patterns.md +327 -0
  83. package/assets/skills/data-fetching.md +231 -0
  84. package/assets/skills/database-migrations.md +346 -0
  85. package/assets/skills/database-patterns.md +219 -0
  86. package/assets/skills/debugging.md +281 -0
  87. package/assets/skills/design-system.md +289 -0
  88. package/assets/skills/django-patterns.md +182 -0
  89. package/assets/skills/docker-patterns.md +235 -0
  90. package/assets/skills/e2e-testing.md +287 -0
  91. package/assets/skills/edge-computing.md +268 -0
  92. package/assets/skills/electron-patterns.md +266 -0
  93. package/assets/skills/email-templates.md +206 -0
  94. package/assets/skills/error-handling.md +265 -0
  95. package/assets/skills/event-driven.md +232 -0
  96. package/assets/skills/express-patterns.md +239 -0
  97. package/assets/skills/fastapi-patterns.md +198 -0
  98. package/assets/skills/feature-flags.md +212 -0
  99. package/assets/skills/figma-to-code.md +298 -0
  100. package/assets/skills/file-upload.md +228 -0
  101. package/assets/skills/forms-patterns.md +264 -0
  102. package/assets/skills/gcp-patterns.md +189 -0
  103. package/assets/skills/git-workflow.md +187 -0
  104. package/assets/skills/golang-patterns.md +185 -0
  105. package/assets/skills/graphql-patterns.md +244 -0
  106. package/assets/skills/i18n-patterns.md +172 -0
  107. package/assets/skills/image-processing.md +350 -0
  108. package/assets/skills/java-springboot.md +226 -0
  109. package/assets/skills/kotlin-patterns.md +207 -0
  110. package/assets/skills/kubernetes-patterns.md +326 -0
  111. package/assets/skills/laravel-patterns.md +261 -0
  112. package/assets/skills/llm-fine-tuning.md +335 -0
  113. package/assets/skills/load-testing.md +303 -0
  114. package/assets/skills/logging-observability.md +228 -0
  115. package/assets/skills/markdown-processing.md +318 -0
  116. package/assets/skills/mcp-server-patterns.md +292 -0
  117. package/assets/skills/microservices.md +272 -0
  118. package/assets/skills/migration-patterns.md +239 -0
  119. package/assets/skills/mongodb-patterns.md +189 -0
  120. package/assets/skills/monorepo-patterns.md +287 -0
  121. package/assets/skills/nextjs-app-router.md +237 -0
  122. package/assets/skills/notification-patterns.md +348 -0
  123. package/assets/skills/oauth-patterns.md +246 -0
  124. package/assets/skills/payment-integration.md +222 -0
  125. package/assets/skills/pdf-generation.md +307 -0
  126. package/assets/skills/performance-optimization.md +277 -0
  127. package/assets/skills/php-patterns.md +210 -0
  128. package/assets/skills/prisma-patterns.md +241 -0
  129. package/assets/skills/prompt-engineering.md +193 -0
  130. package/assets/skills/pwa-patterns.md +247 -0
  131. package/assets/skills/python-patterns.md +158 -0
  132. package/assets/skills/python-testing.md +172 -0
  133. package/assets/skills/queue-patterns.md +295 -0
  134. package/assets/skills/rag-patterns.md +159 -0
  135. package/assets/skills/rate-limiting.md +319 -0
  136. package/assets/skills/react-components.md +201 -0
  137. package/assets/skills/react-native-patterns.md +299 -0
  138. package/assets/skills/real-time-patterns.md +181 -0
  139. package/assets/skills/redis-patterns.md +188 -0
  140. package/assets/skills/refactoring.md +218 -0
  141. package/assets/skills/regex-patterns.md +191 -0
  142. package/assets/skills/remix-patterns.md +262 -0
  143. package/assets/skills/responsive-design.md +199 -0
  144. package/assets/skills/ruby-rails-patterns.md +178 -0
  145. package/assets/skills/rust-patterns.md +211 -0
  146. package/assets/skills/search-patterns.md +227 -0
  147. package/assets/skills/security-hardening.md +237 -0
  148. package/assets/skills/seo-patterns.md +179 -0
  149. package/assets/skills/serverless-patterns.md +223 -0
  150. package/assets/skills/sql-optimization.md +154 -0
  151. package/assets/skills/state-management.md +254 -0
  152. package/assets/skills/storybook-patterns.md +330 -0
  153. package/assets/skills/svelte-patterns.md +258 -0
  154. package/assets/skills/swift-patterns.md +227 -0
  155. package/assets/skills/tailwind-patterns.md +272 -0
  156. package/assets/skills/tdd-workflow.md +199 -0
  157. package/assets/skills/terraform-patterns.md +270 -0
  158. package/assets/skills/testing-react.md +240 -0
  159. package/assets/skills/testing-vitest.md +232 -0
  160. package/assets/skills/typescript-strict.md +159 -0
  161. package/assets/skills/video-processing.md +340 -0
  162. package/assets/skills/vue-patterns.md +247 -0
  163. package/assets/skills/web-workers.md +327 -0
  164. package/assets/skills/webhooks-patterns.md +283 -0
  165. package/assets/skills/websocket-patterns.md +306 -0
  166. package/dist/cli/index.js +941 -958
  167. package/dist/core/index.d.ts +341 -11
  168. package/dist/core.js +58 -95
  169. package/dist/mcp/index.d.ts +33 -1
  170. package/dist/mcp-server.js +177 -255
  171. package/package.json +4 -1
@@ -0,0 +1,299 @@
1
+ ---
2
+ name: react-native-patterns
3
+ description: React Native patterns for navigation, native modules, animations, platform-specific code, and OTA updates.
4
+ ---
5
+
6
+ # React Native Patterns
7
+
8
+ ## When to Use
9
+ Apply these patterns when building cross-platform mobile applications with React Native. These cover the core challenges: navigation architecture, bridging to native APIs, performant animations, writing platform-specific code, and shipping updates without app store delays. Use these from project setup to avoid costly refactors later.
10
+
11
+ ## How It Works
12
+
13
+ ### Navigation with React Navigation
14
+
15
+ ```typescript
16
+ // src/navigation/RootNavigator.tsx
17
+ import { NavigationContainer } from '@react-navigation/native';
18
+ import { createNativeStackNavigator } from '@react-navigation/native-stack';
19
+ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
20
+
21
+ type RootStackParamList = {
22
+ Auth: undefined;
23
+ Main: undefined;
24
+ };
25
+
26
+ type MainTabParamList = {
27
+ Home: undefined;
28
+ Profile: { userId: string };
29
+ Settings: undefined;
30
+ };
31
+
32
+ const Stack = createNativeStackNavigator<RootStackParamList>();
33
+ const Tab = createBottomTabNavigator<MainTabParamList>();
34
+
35
+ function MainTabs() {
36
+ return (
37
+ <Tab.Navigator screenOptions={{ headerShown: false }}>
38
+ <Tab.Screen name="Home" component={HomeScreen} />
39
+ <Tab.Screen name="Profile" component={ProfileScreen} initialParams={{ userId: 'me' }} />
40
+ <Tab.Screen name="Settings" component={SettingsScreen} />
41
+ </Tab.Navigator>
42
+ );
43
+ }
44
+
45
+ export function RootNavigator() {
46
+ const { isAuthenticated } = useAuth();
47
+
48
+ return (
49
+ <NavigationContainer>
50
+ <Stack.Navigator screenOptions={{ headerShown: false }}>
51
+ {isAuthenticated ? (
52
+ <Stack.Screen name="Main" component={MainTabs} />
53
+ ) : (
54
+ <Stack.Screen name="Auth" component={AuthScreen} />
55
+ )}
56
+ </Stack.Navigator>
57
+ </NavigationContainer>
58
+ );
59
+ }
60
+ ```
61
+
62
+ ### Type-Safe Navigation Hooks
63
+
64
+ ```typescript
65
+ // src/navigation/hooks.ts
66
+ import { useNavigation, useRoute } from '@react-navigation/native';
67
+ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
68
+ import type { RouteProp } from '@react-navigation/native';
69
+
70
+ export function useAppNavigation() {
71
+ return useNavigation<NativeStackNavigationProp<RootStackParamList>>();
72
+ }
73
+
74
+ export function useProfileRoute() {
75
+ return useRoute<RouteProp<MainTabParamList, 'Profile'>>();
76
+ }
77
+
78
+ // Usage in component:
79
+ function ProfileScreen() {
80
+ const { params } = useProfileRoute();
81
+ const navigation = useAppNavigation();
82
+
83
+ return (
84
+ <Button title="Go Home" onPress={() => navigation.navigate('Main')} />
85
+ );
86
+ }
87
+ ```
88
+
89
+ ### Native Module Bridge (Turbo Modules)
90
+
91
+ ```typescript
92
+ // src/native/HapticModule.ts
93
+ import { TurboModuleRegistry } from 'react-native';
94
+ import type { TurboModule } from 'react-native';
95
+
96
+ export interface Spec extends TurboModule {
97
+ trigger(type: string): void;
98
+ impactFeedback(style: 'light' | 'medium' | 'heavy'): void;
99
+ }
100
+
101
+ export default TurboModuleRegistry.getEnforcing<Spec>('HapticModule');
102
+ ```
103
+
104
+ ```swift
105
+ // ios/HapticModule.swift
106
+ @objc(HapticModule)
107
+ class HapticModule: NSObject {
108
+ @objc func trigger(_ type: String) {
109
+ let generator = UINotificationFeedbackGenerator()
110
+ switch type {
111
+ case "success": generator.notificationOccurred(.success)
112
+ case "warning": generator.notificationOccurred(.warning)
113
+ case "error": generator.notificationOccurred(.error)
114
+ default: break
115
+ }
116
+ }
117
+
118
+ @objc func impactFeedback(_ style: String) {
119
+ let s: UIImpactFeedbackGenerator.FeedbackStyle = style == "heavy" ? .heavy : style == "medium" ? .medium : .light
120
+ UIImpactFeedbackGenerator(style: s).impactOccurred()
121
+ }
122
+
123
+ @objc static func requiresMainQueueSetup() -> Bool { true }
124
+ }
125
+ ```
126
+
127
+ ### Reanimated Animations
128
+
129
+ ```typescript
130
+ // src/components/AnimatedCard.tsx
131
+ import Animated, {
132
+ useSharedValue,
133
+ useAnimatedStyle,
134
+ withSpring,
135
+ withTiming,
136
+ interpolate,
137
+ } from 'react-native-reanimated';
138
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
139
+
140
+ export function AnimatedCard({ children }: { children: React.ReactNode }) {
141
+ const translateX = useSharedValue(0);
142
+ const scale = useSharedValue(1);
143
+
144
+ const panGesture = Gesture.Pan()
145
+ .onUpdate((e) => {
146
+ translateX.value = e.translationX;
147
+ })
148
+ .onEnd(() => {
149
+ if (Math.abs(translateX.value) > 150) {
150
+ translateX.value = withTiming(Math.sign(translateX.value) * 500);
151
+ } else {
152
+ translateX.value = withSpring(0);
153
+ }
154
+ });
155
+
156
+ const tapGesture = Gesture.Tap()
157
+ .onBegin(() => { scale.value = withSpring(0.95); })
158
+ .onFinalize(() => { scale.value = withSpring(1); });
159
+
160
+ const animatedStyle = useAnimatedStyle(() => ({
161
+ transform: [
162
+ { translateX: translateX.value },
163
+ { scale: scale.value },
164
+ { rotateZ: `${interpolate(translateX.value, [-200, 0, 200], [-15, 0, 15])}deg` },
165
+ ],
166
+ opacity: interpolate(Math.abs(translateX.value), [0, 200], [1, 0.5]),
167
+ }));
168
+
169
+ const composed = Gesture.Simultaneous(panGesture, tapGesture);
170
+
171
+ return (
172
+ <GestureDetector gesture={composed}>
173
+ <Animated.View style={[styles.card, animatedStyle]}>{children}</Animated.View>
174
+ </GestureDetector>
175
+ );
176
+ }
177
+ ```
178
+
179
+ ### Platform-Specific Code
180
+
181
+ ```typescript
182
+ // src/components/Shadow.tsx
183
+ import { Platform, StyleSheet, View, ViewProps } from 'react-native';
184
+
185
+ const styles = StyleSheet.create({
186
+ shadow: Platform.select({
187
+ ios: {
188
+ shadowColor: '#000',
189
+ shadowOffset: { width: 0, height: 2 },
190
+ shadowOpacity: 0.1,
191
+ shadowRadius: 8,
192
+ },
193
+ android: {
194
+ elevation: 4,
195
+ },
196
+ default: {},
197
+ })!,
198
+ });
199
+
200
+ // File-based platform splitting:
201
+ // src/components/BiometricAuth.ios.ts — Face ID implementation
202
+ // src/components/BiometricAuth.android.ts — Fingerprint implementation
203
+ // src/components/BiometricAuth.ts — exports from platform file
204
+
205
+ export function Shadow({ style, ...props }: ViewProps) {
206
+ return <View style={[styles.shadow, style]} {...props} />;
207
+ }
208
+ ```
209
+
210
+ ### OTA Updates with EAS Update
211
+
212
+ ```typescript
213
+ // src/updates/useAppUpdate.ts
214
+ import * as Updates from 'expo-updates';
215
+ import { useEffect, useState } from 'react';
216
+ import { AppState } from 'react-native';
217
+
218
+ export function useAppUpdate() {
219
+ const [updateAvailable, setUpdateAvailable] = useState(false);
220
+
221
+ useEffect(() => {
222
+ const subscription = AppState.addEventListener('change', async (state) => {
223
+ if (state !== 'active' || __DEV__) return;
224
+
225
+ try {
226
+ const update = await Updates.checkForUpdateAsync();
227
+ if (update.isAvailable) {
228
+ await Updates.fetchUpdateAsync();
229
+ setUpdateAvailable(true);
230
+ }
231
+ } catch (e) {
232
+ console.warn('Update check failed:', e);
233
+ }
234
+ });
235
+
236
+ return () => subscription.remove();
237
+ }, []);
238
+
239
+ const applyUpdate = async () => {
240
+ await Updates.reloadAsync();
241
+ };
242
+
243
+ return { updateAvailable, applyUpdate };
244
+ }
245
+ ```
246
+
247
+ ### Performance: FlatList Optimization
248
+
249
+ ```typescript
250
+ // src/components/OptimizedList.tsx
251
+ import { FlatList, View, Text } from 'react-native';
252
+ import { useCallback, memo } from 'react';
253
+
254
+ interface Item { id: string; title: string; subtitle: string }
255
+
256
+ const ListItem = memo(({ item }: { item: Item }) => (
257
+ <View style={{ padding: 16 }}>
258
+ <Text style={{ fontSize: 16, fontWeight: '600' }}>{item.title}</Text>
259
+ <Text style={{ fontSize: 14, color: '#666' }}>{item.subtitle}</Text>
260
+ </View>
261
+ ));
262
+
263
+ export function OptimizedList({ data }: { data: Item[] }) {
264
+ const renderItem = useCallback(({ item }: { item: Item }) => <ListItem item={item} />, []);
265
+ const keyExtractor = useCallback((item: Item) => item.id, []);
266
+
267
+ return (
268
+ <FlatList
269
+ data={data}
270
+ renderItem={renderItem}
271
+ keyExtractor={keyExtractor}
272
+ getItemLayout={(_, index) => ({ length: 64, offset: 64 * index, index })}
273
+ maxToRenderPerBatch={20}
274
+ windowSize={5}
275
+ removeClippedSubviews
276
+ initialNumToRender={15}
277
+ />
278
+ );
279
+ }
280
+ ```
281
+
282
+ ## Examples
283
+
284
+ | Pattern | Problem | Solution |
285
+ |---------|---------|----------|
286
+ | Conditional navigator | Auth vs main flow | Render different Stack.Screens based on auth state |
287
+ | Turbo Module | Need native haptics/biometrics | Define spec interface, implement per platform |
288
+ | Reanimated shared value | Smooth gesture animations | `useSharedValue` + `useAnimatedStyle` on UI thread |
289
+ | Platform.select | Different shadow APIs | iOS shadowColor vs Android elevation |
290
+ | EAS Update | Ship fixes without app store | `expo-updates` check on app foreground |
291
+
292
+ ## Checklist
293
+ - [ ] Navigation typed with ParamList generics for compile-time route safety
294
+ - [ ] Native modules use Turbo Module spec for New Architecture compatibility
295
+ - [ ] Animations run on UI thread via Reanimated worklets (no `runOnJS` in hot paths)
296
+ - [ ] Platform-specific files use `.ios.ts` / `.android.ts` suffixes where divergence is large
297
+ - [ ] FlatList uses `getItemLayout`, `memo`, and `removeClippedSubviews` for large lists
298
+ - [ ] OTA updates checked on app foreground with user-visible apply prompt
299
+ - [ ] Hermes engine enabled for both iOS and Android builds
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: real-time-patterns
3
+ description: Real-time patterns for SSE, WebSockets, reconnection with backoff, presence channels, and Redis scaling.
4
+ ---
5
+
6
+ # Real-Time Patterns
7
+
8
+ ## When to Use
9
+ Use real-time patterns when users need instant updates: chat messages, notifications, live dashboards, collaborative editing, or auction bidding. Choose SSE for server-to-client streaming, WebSockets for bidirectional communication, and long polling as a universal fallback. This skill covers implementation, reconnection, presence, and multi-server scaling.
10
+
11
+ ## How It Works
12
+
13
+ ### Server-Sent Events (SSE) -- Simplest Server Push
14
+
15
+ ```typescript
16
+ // Server -- one-direction, built-in auto-reconnection
17
+ app.get("/events/:userId", (req, res) => {
18
+ res.writeHead(200, {
19
+ "Content-Type": "text/event-stream",
20
+ "Cache-Control": "no-cache",
21
+ "Connection": "keep-alive",
22
+ "X-Accel-Buffering": "no",
23
+ });
24
+
25
+ const userId = req.params.userId;
26
+ const heartbeat = setInterval(() => res.write(": heartbeat\n\n"), 30_000);
27
+
28
+ const handler = (event: AppEvent) => {
29
+ res.write(`event: ${event.type}\n`);
30
+ res.write(`data: ${JSON.stringify(event.payload)}\n`);
31
+ res.write(`id: ${event.id}\n\n`);
32
+ };
33
+ eventBus.subscribe(userId, handler);
34
+
35
+ req.on("close", () => {
36
+ clearInterval(heartbeat);
37
+ eventBus.unsubscribe(userId, handler);
38
+ });
39
+ });
40
+
41
+ // Client -- browser auto-reconnects with Last-Event-ID header
42
+ const source = new EventSource("/events/user-123");
43
+ source.addEventListener("notification", (e) => {
44
+ showNotification(JSON.parse(e.data));
45
+ });
46
+ ```
47
+
48
+ ### WebSockets -- Bidirectional Communication
49
+
50
+ ```typescript
51
+ import { WebSocketServer, WebSocket } from "ws";
52
+
53
+ const wss = new WebSocketServer({ server: httpServer, path: "/ws" });
54
+ const rooms = new Map<string, Set<WebSocket>>();
55
+
56
+ wss.on("connection", (ws, req) => {
57
+ const userId = authenticateFromCookie(req);
58
+ if (!userId) { ws.close(4001, "Unauthorized"); return; }
59
+
60
+ ws.on("message", (raw) => {
61
+ const msg = JSON.parse(raw.toString());
62
+ switch (msg.type) {
63
+ case "join": joinRoom(msg.room, ws); break;
64
+ case "message":
65
+ broadcastToRoom(msg.room, {
66
+ type: "message", from: userId, text: msg.text, timestamp: Date.now(),
67
+ }, ws);
68
+ break;
69
+ case "ping": ws.send(JSON.stringify({ type: "pong" })); break;
70
+ }
71
+ });
72
+
73
+ ws.on("close", () => removeFromAllRooms(ws));
74
+ });
75
+
76
+ function broadcastToRoom(room: string, data: object, exclude?: WebSocket) {
77
+ const members = rooms.get(room);
78
+ if (!members) return;
79
+ const payload = JSON.stringify(data);
80
+ for (const client of members) {
81
+ if (client !== exclude && client.readyState === WebSocket.OPEN) {
82
+ client.send(payload);
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### Client Reconnection with Backoff
89
+
90
+ ```typescript
91
+ class ReconnectingSocket {
92
+ private ws: WebSocket | null = null;
93
+ private attempt = 0;
94
+ private maxDelay = 30_000;
95
+
96
+ constructor(private url: string, private onMessage: (data: any) => void) {
97
+ this.connect();
98
+ }
99
+
100
+ private connect() {
101
+ this.ws = new WebSocket(this.url);
102
+ this.ws.onopen = () => { this.attempt = 0; };
103
+ this.ws.onmessage = (e) => this.onMessage(JSON.parse(e.data));
104
+ this.ws.onclose = (e) => {
105
+ if (e.code !== 1000) this.scheduleReconnect();
106
+ };
107
+ }
108
+
109
+ private scheduleReconnect() {
110
+ const delay = Math.min(1000 * Math.pow(2, this.attempt), this.maxDelay);
111
+ const jitter = delay * 0.3 * Math.random();
112
+ this.attempt++;
113
+ setTimeout(() => this.connect(), delay + jitter);
114
+ }
115
+
116
+ send(data: object) {
117
+ if (this.ws?.readyState === WebSocket.OPEN) {
118
+ this.ws.send(JSON.stringify(data));
119
+ }
120
+ }
121
+ }
122
+ ```
123
+
124
+ ### Presence Channels -- Who Is Online
125
+
126
+ ```typescript
127
+ const presence = new Map<string, { userId: string; lastSeen: number }>();
128
+
129
+ function updatePresence(userId: string) {
130
+ presence.set(userId, { userId, lastSeen: Date.now() });
131
+ }
132
+
133
+ function getOnlineUsers(staleMs = 60_000): string[] {
134
+ const cutoff = Date.now() - staleMs;
135
+ const online: string[] = [];
136
+ for (const [userId, info] of presence) {
137
+ if (info.lastSeen > cutoff) online.push(userId);
138
+ else presence.delete(userId);
139
+ }
140
+ return online;
141
+ }
142
+ ```
143
+
144
+ ### Multi-Server Scaling with Redis
145
+
146
+ ```typescript
147
+ import { createClient } from "redis";
148
+
149
+ const pub = createClient();
150
+ const sub = createClient();
151
+ await pub.connect();
152
+ await sub.connect();
153
+
154
+ function broadcastToCluster(channel: string, data: object) {
155
+ pub.publish(`ws:${channel}`, JSON.stringify(data));
156
+ }
157
+
158
+ await sub.pSubscribe("ws:*", (message, channel) => {
159
+ const room = channel.replace("ws:", "");
160
+ broadcastToRoom(room, JSON.parse(message));
161
+ });
162
+ ```
163
+
164
+ ## Examples
165
+
166
+ | Transport | Direction | Auto-Reconnect | Use Case |
167
+ |-----------|-----------|----------------|----------|
168
+ | SSE | Server to client | Yes (built-in) | Notifications, live feeds |
169
+ | WebSocket | Bidirectional | Manual (implement) | Chat, gaming, collaboration |
170
+ | Long polling | Simulated push | N/A (client loops) | Legacy browser fallback |
171
+ | Socket.io | Bidirectional | Yes (built-in) | WebSocket + fallback + rooms |
172
+
173
+ ## Checklist
174
+ - [ ] Transport chosen based on requirements (SSE for push-only, WS for bidirectional)
175
+ - [ ] Authentication checked on connection, not just on first message
176
+ - [ ] Heartbeat/keepalive prevents proxy and load balancer timeouts
177
+ - [ ] Client reconnects with exponential backoff and jitter
178
+ - [ ] Last-Event-ID or cursor used to resume without data loss
179
+ - [ ] Presence tracking uses heartbeat + stale cleanup
180
+ - [ ] Multi-server scaling uses Redis pub/sub or a message broker
181
+ - [ ] Connection count monitored with alerting configured
@@ -0,0 +1,188 @@
1
+ ---
2
+ name: redis-patterns
3
+ description: Redis patterns for caching, pub/sub, streams, sorted sets, Lua scripts, and cluster configuration.
4
+ ---
5
+
6
+ # Redis Patterns
7
+
8
+ ## When to Use
9
+ Use Redis when you need sub-millisecond reads, ephemeral data structures, or coordination between services. Common use cases: caching expensive queries, rate limiting API endpoints, real-time leaderboards, job queues, session storage, and distributed locking. Choose the right data structure for each problem.
10
+
11
+ ## How It Works
12
+
13
+ ### Cache-Aside with TTL
14
+
15
+ ```typescript
16
+ async function getUser(userId: string): Promise<User> {
17
+ const cacheKey = `user:${userId}`;
18
+ const cached = await redis.get(cacheKey);
19
+ if (cached) return JSON.parse(cached);
20
+
21
+ const user = await db.users.findUnique({ where: { id: userId } });
22
+ if (user) {
23
+ await redis.set(cacheKey, JSON.stringify(user), "EX", 3600);
24
+ }
25
+ return user;
26
+ }
27
+
28
+ // Invalidate on write
29
+ async function updateUser(userId: string, data: Partial<User>): Promise<User> {
30
+ const user = await db.users.update({ where: { id: userId }, data });
31
+ await redis.del(`user:${userId}`);
32
+ return user;
33
+ }
34
+ ```
35
+
36
+ Keep values under 100KB. Use Hash for structured data when you need partial reads.
37
+
38
+ ### Rate Limiting -- Sliding Window
39
+
40
+ ```typescript
41
+ async function isRateLimited(
42
+ key: string, limit: number, windowSec: number
43
+ ): Promise<boolean> {
44
+ const now = Date.now();
45
+ const windowStart = now - windowSec * 1000;
46
+
47
+ const pipeline = redis.pipeline();
48
+ pipeline.zremrangebyscore(key, 0, windowStart);
49
+ pipeline.zadd(key, now, `${now}-${Math.random()}`);
50
+ pipeline.zcard(key);
51
+ pipeline.expire(key, windowSec);
52
+
53
+ const results = await pipeline.exec();
54
+ const count = results![2][1] as number;
55
+ return count > limit;
56
+ }
57
+
58
+ // Usage: 100 requests per minute per user
59
+ const limited = await isRateLimited(`rate:${userId}`, 100, 60);
60
+ ```
61
+
62
+ ### Distributed Lock -- Redlock Pattern
63
+
64
+ ```typescript
65
+ async function acquireLock(resource: string, ttlMs: number): Promise<string | null> {
66
+ const token = crypto.randomUUID();
67
+ const acquired = await redis.set(`lock:${resource}`, token, "PX", ttlMs, "NX");
68
+ return acquired ? token : null;
69
+ }
70
+
71
+ async function releaseLock(resource: string, token: string): Promise<boolean> {
72
+ const script = `
73
+ if redis.call("get", KEYS[1]) == ARGV[1] then
74
+ return redis.call("del", KEYS[1])
75
+ else
76
+ return 0
77
+ end
78
+ `;
79
+ const result = await redis.eval(script, 1, `lock:${resource}`, token);
80
+ return result === 1;
81
+ }
82
+
83
+ // Usage
84
+ const token = await acquireLock("order-123", 30000);
85
+ if (token) {
86
+ try {
87
+ await processOrder("order-123");
88
+ } finally {
89
+ await releaseLock("order-123", token);
90
+ }
91
+ }
92
+ ```
93
+
94
+ ### Pub/Sub -- Event Broadcasting
95
+
96
+ ```typescript
97
+ // Publisher
98
+ await redis.publish("orders:created", JSON.stringify({ orderId, userId }));
99
+
100
+ // Subscriber (must use a separate connection)
101
+ const sub = redis.duplicate();
102
+ await sub.subscribe("orders:created");
103
+ sub.on("message", (channel, message) => {
104
+ const event = JSON.parse(message);
105
+ notifyDashboard(event);
106
+ });
107
+ ```
108
+
109
+ Pub/Sub is fire-and-forget. If subscribers are down, messages are lost. Use Streams for durable messaging.
110
+
111
+ ### Streams -- Durable Event Log
112
+
113
+ ```typescript
114
+ // Produce
115
+ await redis.xadd("events:orders", "*", "type", "created", "orderId", orderId);
116
+
117
+ // Consumer group -- each message delivered to one consumer
118
+ await redis.xgroup("CREATE", "events:orders", "workers", "0", "MKSTREAM").catch(() => {});
119
+
120
+ const messages = await redis.xreadgroup(
121
+ "GROUP", "workers", "worker-1",
122
+ "COUNT", 10, "BLOCK", 5000,
123
+ "STREAMS", "events:orders", ">"
124
+ );
125
+
126
+ // Acknowledge after processing
127
+ if (messages) {
128
+ for (const [, msgs] of messages) {
129
+ for (const [id] of msgs) {
130
+ await redis.xack("events:orders", "workers", id);
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### Sorted Sets -- Leaderboards and Rankings
137
+
138
+ ```typescript
139
+ // Add/update score
140
+ await redis.zadd("leaderboard:weekly", score, `user:${userId}`);
141
+
142
+ // Get top 10 with scores
143
+ const top10 = await redis.zrevrange("leaderboard:weekly", 0, 9, "WITHSCORES");
144
+
145
+ // Get a user's rank (0-indexed)
146
+ const rank = await redis.zrevrank("leaderboard:weekly", `user:${userId}`);
147
+ ```
148
+
149
+ ### Lua Scripts -- Atomic Multi-Step Operations
150
+
151
+ ```typescript
152
+ // Atomic increment with ceiling
153
+ const script = `
154
+ local current = tonumber(redis.call("GET", KEYS[1]) or "0")
155
+ local ceiling = tonumber(ARGV[1])
156
+ if current >= ceiling then
157
+ return -1
158
+ end
159
+ return redis.call("INCR", KEYS[1])
160
+ `;
161
+
162
+ const result = await redis.eval(script, 1, "counter:signups", "1000");
163
+ if (result === -1) {
164
+ throw new Error("Signup limit reached");
165
+ }
166
+ ```
167
+
168
+ ## Examples
169
+
170
+ | Pattern | Data Structure | Use Case |
171
+ |---------|---------------|----------|
172
+ | Cache-aside | String / Hash | DB query results, API responses |
173
+ | Rate limiter | Sorted Set | API throttling per IP/user |
174
+ | Distributed lock | String + NX + Lua | Singleton job execution |
175
+ | Pub/Sub | Channels | Real-time notifications |
176
+ | Streams | Stream | Durable event processing |
177
+ | Leaderboard | Sorted Set | Rankings, top-N queries |
178
+ | Session store | Hash + TTL | User sessions |
179
+
180
+ ## Checklist
181
+ - [ ] Every cache key has a TTL -- no unbounded growth
182
+ - [ ] Cache keys use consistent namespace: `entity:id:field`
183
+ - [ ] Cache invalidation happens in same transaction as write
184
+ - [ ] Rate limiter uses sorted set sliding window
185
+ - [ ] Distributed locks use NX + PX + unique token + Lua release
186
+ - [ ] Pub/Sub subscribers run on dedicated Redis connection
187
+ - [ ] Streams use consumer groups with XACK
188
+ - [ ] Memory policy set (`maxmemory-policy allkeys-lru` for caches)