autoworkflow 3.1.5 → 3.6.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/.claude/commands/analyze.md +19 -0
- package/.claude/commands/audit.md +26 -0
- package/.claude/commands/build.md +39 -0
- package/.claude/commands/commit.md +25 -0
- package/.claude/commands/fix.md +23 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/suggest.md +23 -0
- package/.claude/commands/verify.md +18 -0
- package/.claude/hooks/post-bash-router.sh +20 -0
- package/.claude/hooks/post-commit.sh +140 -0
- package/.claude/hooks/post-edit.sh +190 -17
- package/.claude/hooks/pre-edit.sh +221 -0
- package/.claude/hooks/session-check.sh +90 -0
- package/.claude/settings.json +56 -6
- package/.claude/settings.local.json +5 -1
- package/.claude/skills/actix.md +337 -0
- package/.claude/skills/alembic.md +504 -0
- package/.claude/skills/angular.md +237 -0
- package/.claude/skills/api-design.md +187 -0
- package/.claude/skills/aspnet-core.md +377 -0
- package/.claude/skills/astro.md +245 -0
- package/.claude/skills/auth-clerk.md +327 -0
- package/.claude/skills/auth-firebase.md +367 -0
- package/.claude/skills/auth-nextauth.md +359 -0
- package/.claude/skills/auth-supabase.md +368 -0
- package/.claude/skills/axum.md +386 -0
- package/.claude/skills/blazor.md +456 -0
- package/.claude/skills/chi.md +348 -0
- package/.claude/skills/code-review.md +133 -0
- package/.claude/skills/csharp.md +296 -0
- package/.claude/skills/css-modules.md +325 -0
- package/.claude/skills/cypress.md +343 -0
- package/.claude/skills/debugging.md +133 -0
- package/.claude/skills/diesel.md +392 -0
- package/.claude/skills/django.md +301 -0
- package/.claude/skills/docker.md +319 -0
- package/.claude/skills/doctrine.md +473 -0
- package/.claude/skills/documentation.md +182 -0
- package/.claude/skills/dotnet.md +409 -0
- package/.claude/skills/drizzle.md +293 -0
- package/.claude/skills/echo.md +321 -0
- package/.claude/skills/eloquent.md +256 -0
- package/.claude/skills/emotion.md +426 -0
- package/.claude/skills/entity-framework.md +370 -0
- package/.claude/skills/express.md +316 -0
- package/.claude/skills/fastapi.md +329 -0
- package/.claude/skills/fastify.md +299 -0
- package/.claude/skills/fiber.md +315 -0
- package/.claude/skills/flask.md +322 -0
- package/.claude/skills/gin.md +342 -0
- package/.claude/skills/git.md +116 -0
- package/.claude/skills/github-actions.md +353 -0
- package/.claude/skills/go.md +377 -0
- package/.claude/skills/gorm.md +409 -0
- package/.claude/skills/graphql.md +478 -0
- package/.claude/skills/hibernate.md +379 -0
- package/.claude/skills/hono.md +306 -0
- package/.claude/skills/java.md +400 -0
- package/.claude/skills/jest.md +313 -0
- package/.claude/skills/jpa.md +282 -0
- package/.claude/skills/kotlin.md +347 -0
- package/.claude/skills/kubernetes.md +363 -0
- package/.claude/skills/laravel.md +414 -0
- package/.claude/skills/mcp-browser.md +320 -0
- package/.claude/skills/mcp-database.md +219 -0
- package/.claude/skills/mcp-fetch.md +241 -0
- package/.claude/skills/mcp-filesystem.md +204 -0
- package/.claude/skills/mcp-github.md +217 -0
- package/.claude/skills/mcp-memory.md +240 -0
- package/.claude/skills/mcp-search.md +218 -0
- package/.claude/skills/mcp-slack.md +262 -0
- package/.claude/skills/micronaut.md +388 -0
- package/.claude/skills/mongodb.md +319 -0
- package/.claude/skills/mongoose.md +355 -0
- package/.claude/skills/mysql.md +281 -0
- package/.claude/skills/nestjs.md +335 -0
- package/.claude/skills/nextjs-app-router.md +260 -0
- package/.claude/skills/nextjs-pages.md +172 -0
- package/.claude/skills/nuxt.md +202 -0
- package/.claude/skills/openapi.md +489 -0
- package/.claude/skills/performance.md +199 -0
- package/.claude/skills/php.md +398 -0
- package/.claude/skills/playwright.md +371 -0
- package/.claude/skills/postgresql.md +257 -0
- package/.claude/skills/prisma.md +293 -0
- package/.claude/skills/pydantic.md +304 -0
- package/.claude/skills/pytest.md +313 -0
- package/.claude/skills/python.md +272 -0
- package/.claude/skills/quarkus.md +377 -0
- package/.claude/skills/react.md +230 -0
- package/.claude/skills/redis.md +391 -0
- package/.claude/skills/refactoring.md +143 -0
- package/.claude/skills/remix.md +246 -0
- package/.claude/skills/rest-api.md +490 -0
- package/.claude/skills/rocket.md +366 -0
- package/.claude/skills/rust.md +341 -0
- package/.claude/skills/sass.md +380 -0
- package/.claude/skills/sea-orm.md +382 -0
- package/.claude/skills/security.md +167 -0
- package/.claude/skills/sequelize.md +395 -0
- package/.claude/skills/spring-boot.md +416 -0
- package/.claude/skills/sqlalchemy.md +269 -0
- package/.claude/skills/sqlx-rust.md +408 -0
- package/.claude/skills/state-jotai.md +346 -0
- package/.claude/skills/state-mobx.md +353 -0
- package/.claude/skills/state-pinia.md +431 -0
- package/.claude/skills/state-redux.md +337 -0
- package/.claude/skills/state-tanstack-query.md +434 -0
- package/.claude/skills/state-zustand.md +340 -0
- package/.claude/skills/styled-components.md +403 -0
- package/.claude/skills/svelte.md +238 -0
- package/.claude/skills/sveltekit.md +207 -0
- package/.claude/skills/symfony.md +437 -0
- package/.claude/skills/tailwind.md +279 -0
- package/.claude/skills/terraform.md +394 -0
- package/.claude/skills/testing-library.md +371 -0
- package/.claude/skills/trpc.md +426 -0
- package/.claude/skills/typeorm.md +368 -0
- package/.claude/skills/vitest.md +330 -0
- package/.claude/skills/vue.md +202 -0
- package/.claude/skills/warp.md +365 -0
- package/README.md +163 -52
- package/package.json +1 -1
- package/system/triggers.md +256 -17
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Redis Skill
|
|
2
|
+
|
|
3
|
+
## Connection Setup
|
|
4
|
+
\`\`\`typescript
|
|
5
|
+
import Redis from 'ioredis';
|
|
6
|
+
|
|
7
|
+
// Single instance
|
|
8
|
+
const redis = new Redis({
|
|
9
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
10
|
+
port: parseInt(process.env.REDIS_PORT || '6379'),
|
|
11
|
+
password: process.env.REDIS_PASSWORD,
|
|
12
|
+
db: 0,
|
|
13
|
+
retryDelayOnFailover: 100,
|
|
14
|
+
maxRetriesPerRequest: 3,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Cluster mode
|
|
18
|
+
const cluster = new Redis.Cluster([
|
|
19
|
+
{ host: 'node1', port: 6379 },
|
|
20
|
+
{ host: 'node2', port: 6379 },
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
// Connection events
|
|
24
|
+
redis.on('connect', () => console.log('Redis connected'));
|
|
25
|
+
redis.on('error', (err) => console.error('Redis error:', err));
|
|
26
|
+
redis.on('reconnecting', () => console.log('Redis reconnecting'));
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
## Data Structures
|
|
30
|
+
|
|
31
|
+
### Strings (Key-Value)
|
|
32
|
+
\`\`\`typescript
|
|
33
|
+
// Basic operations
|
|
34
|
+
await redis.set('key', 'value');
|
|
35
|
+
await redis.set('key', 'value', 'EX', 3600); // Expires in 1 hour
|
|
36
|
+
await redis.set('key', 'value', 'NX'); // Only if not exists
|
|
37
|
+
await redis.set('key', 'value', 'XX'); // Only if exists
|
|
38
|
+
|
|
39
|
+
const value = await redis.get('key');
|
|
40
|
+
const values = await redis.mget('key1', 'key2', 'key3');
|
|
41
|
+
|
|
42
|
+
// Atomic operations
|
|
43
|
+
await redis.incr('counter');
|
|
44
|
+
await redis.incrby('counter', 5);
|
|
45
|
+
await redis.decr('counter');
|
|
46
|
+
await redis.incrbyfloat('price', 0.5);
|
|
47
|
+
|
|
48
|
+
// Expiration
|
|
49
|
+
await redis.expire('key', 3600); // Set TTL
|
|
50
|
+
await redis.ttl('key'); // Get remaining TTL
|
|
51
|
+
await redis.persist('key'); // Remove expiration
|
|
52
|
+
\`\`\`
|
|
53
|
+
|
|
54
|
+
### Hashes (Objects)
|
|
55
|
+
\`\`\`typescript
|
|
56
|
+
// Store object fields
|
|
57
|
+
await redis.hset('user:123', 'name', 'John', 'email', 'john@example.com');
|
|
58
|
+
await redis.hset('user:123', { name: 'John', email: 'john@example.com', age: '30' });
|
|
59
|
+
|
|
60
|
+
// Get fields
|
|
61
|
+
const name = await redis.hget('user:123', 'name');
|
|
62
|
+
const user = await redis.hgetall('user:123'); // All fields
|
|
63
|
+
const fields = await redis.hmget('user:123', 'name', 'email');
|
|
64
|
+
|
|
65
|
+
// Atomic increment
|
|
66
|
+
await redis.hincrby('user:123', 'loginCount', 1);
|
|
67
|
+
|
|
68
|
+
// Check/delete fields
|
|
69
|
+
await redis.hexists('user:123', 'name');
|
|
70
|
+
await redis.hdel('user:123', 'tempField');
|
|
71
|
+
\`\`\`
|
|
72
|
+
|
|
73
|
+
### Lists (Queues)
|
|
74
|
+
\`\`\`typescript
|
|
75
|
+
// Add to list
|
|
76
|
+
await redis.lpush('queue', 'item1', 'item2'); // Add to front
|
|
77
|
+
await redis.rpush('queue', 'item3'); // Add to back
|
|
78
|
+
|
|
79
|
+
// Remove from list
|
|
80
|
+
const item = await redis.lpop('queue'); // Remove from front
|
|
81
|
+
const item = await redis.rpop('queue'); // Remove from back
|
|
82
|
+
const item = await redis.blpop('queue', 30); // Blocking pop (30s timeout)
|
|
83
|
+
|
|
84
|
+
// Access elements
|
|
85
|
+
const items = await redis.lrange('queue', 0, -1); // All items
|
|
86
|
+
const length = await redis.llen('queue');
|
|
87
|
+
|
|
88
|
+
// Trim list (keep recent N)
|
|
89
|
+
await redis.ltrim('recent:views', 0, 99); // Keep last 100
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
### Sets (Unique Collections)
|
|
93
|
+
\`\`\`typescript
|
|
94
|
+
// Add/remove members
|
|
95
|
+
await redis.sadd('tags:post:123', 'redis', 'database', 'cache');
|
|
96
|
+
await redis.srem('tags:post:123', 'cache');
|
|
97
|
+
|
|
98
|
+
// Check membership
|
|
99
|
+
const isMember = await redis.sismember('tags:post:123', 'redis');
|
|
100
|
+
const members = await redis.smembers('tags:post:123');
|
|
101
|
+
const count = await redis.scard('tags:post:123');
|
|
102
|
+
|
|
103
|
+
// Set operations
|
|
104
|
+
const common = await redis.sinter('user:1:skills', 'user:2:skills'); // Intersection
|
|
105
|
+
const all = await redis.sunion('user:1:skills', 'user:2:skills'); // Union
|
|
106
|
+
const diff = await redis.sdiff('user:1:skills', 'user:2:skills'); // Difference
|
|
107
|
+
|
|
108
|
+
// Random member
|
|
109
|
+
const random = await redis.srandmember('tags:post:123');
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
### Sorted Sets (Ranked Data)
|
|
113
|
+
\`\`\`typescript
|
|
114
|
+
// Add with scores
|
|
115
|
+
await redis.zadd('leaderboard', 100, 'player1', 85, 'player2', 120, 'player3');
|
|
116
|
+
|
|
117
|
+
// Get rankings
|
|
118
|
+
const top10 = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES'); // Highest first
|
|
119
|
+
const bottom10 = await redis.zrange('leaderboard', 0, 9, 'WITHSCORES'); // Lowest first
|
|
120
|
+
|
|
121
|
+
// Get rank
|
|
122
|
+
const rank = await redis.zrevrank('leaderboard', 'player1'); // 0-indexed from top
|
|
123
|
+
const score = await redis.zscore('leaderboard', 'player1');
|
|
124
|
+
|
|
125
|
+
// Range by score
|
|
126
|
+
const players = await redis.zrangebyscore('leaderboard', 50, 100, 'WITHSCORES');
|
|
127
|
+
|
|
128
|
+
// Increment score
|
|
129
|
+
await redis.zincrby('leaderboard', 10, 'player1');
|
|
130
|
+
|
|
131
|
+
// Remove
|
|
132
|
+
await redis.zrem('leaderboard', 'player1');
|
|
133
|
+
await redis.zremrangebyrank('leaderboard', 0, 9); // Remove bottom 10
|
|
134
|
+
\`\`\`
|
|
135
|
+
|
|
136
|
+
## Common Patterns
|
|
137
|
+
|
|
138
|
+
### Caching
|
|
139
|
+
\`\`\`typescript
|
|
140
|
+
async function getCachedUser(userId: string): Promise<User | null> {
|
|
141
|
+
const cacheKey = \`user:\${userId}\`;
|
|
142
|
+
|
|
143
|
+
// Try cache first
|
|
144
|
+
const cached = await redis.get(cacheKey);
|
|
145
|
+
if (cached) {
|
|
146
|
+
return JSON.parse(cached);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Fetch from database
|
|
150
|
+
const user = await db.users.findById(userId);
|
|
151
|
+
if (user) {
|
|
152
|
+
// Cache for 1 hour
|
|
153
|
+
await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return user;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Cache invalidation
|
|
160
|
+
async function updateUser(userId: string, data: Partial<User>) {
|
|
161
|
+
await db.users.update(userId, data);
|
|
162
|
+
await redis.del(\`user:\${userId}\`); // Invalidate cache
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Cache aside with lock (prevent stampede)
|
|
166
|
+
async function getCachedWithLock(key: string, fetchFn: () => Promise<any>) {
|
|
167
|
+
const cached = await redis.get(key);
|
|
168
|
+
if (cached) return JSON.parse(cached);
|
|
169
|
+
|
|
170
|
+
const lockKey = \`lock:\${key}\`;
|
|
171
|
+
const acquired = await redis.set(lockKey, '1', 'NX', 'EX', 10);
|
|
172
|
+
|
|
173
|
+
if (acquired) {
|
|
174
|
+
const data = await fetchFn();
|
|
175
|
+
await redis.set(key, JSON.stringify(data), 'EX', 3600);
|
|
176
|
+
await redis.del(lockKey);
|
|
177
|
+
return data;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Wait and retry if lock not acquired
|
|
181
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
182
|
+
return getCachedWithLock(key, fetchFn);
|
|
183
|
+
}
|
|
184
|
+
\`\`\`
|
|
185
|
+
|
|
186
|
+
### Rate Limiting
|
|
187
|
+
\`\`\`typescript
|
|
188
|
+
// Fixed window rate limiting
|
|
189
|
+
async function rateLimit(userId: string, limit: number, windowSec: number): Promise<boolean> {
|
|
190
|
+
const key = \`ratelimit:\${userId}:\${Math.floor(Date.now() / (windowSec * 1000))}\`;
|
|
191
|
+
|
|
192
|
+
const count = await redis.incr(key);
|
|
193
|
+
if (count === 1) {
|
|
194
|
+
await redis.expire(key, windowSec);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return count <= limit;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Sliding window rate limiting (more accurate)
|
|
201
|
+
async function slidingWindowRateLimit(
|
|
202
|
+
userId: string,
|
|
203
|
+
limit: number,
|
|
204
|
+
windowMs: number
|
|
205
|
+
): Promise<{ allowed: boolean; remaining: number }> {
|
|
206
|
+
const key = \`ratelimit:\${userId}\`;
|
|
207
|
+
const now = Date.now();
|
|
208
|
+
const windowStart = now - windowMs;
|
|
209
|
+
|
|
210
|
+
// Remove old entries and add new one
|
|
211
|
+
await redis.zremrangebyscore(key, 0, windowStart);
|
|
212
|
+
const count = await redis.zcard(key);
|
|
213
|
+
|
|
214
|
+
if (count < limit) {
|
|
215
|
+
await redis.zadd(key, now, \`\${now}-\${Math.random()}\`);
|
|
216
|
+
await redis.expire(key, Math.ceil(windowMs / 1000));
|
|
217
|
+
return { allowed: true, remaining: limit - count - 1 };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return { allowed: false, remaining: 0 };
|
|
221
|
+
}
|
|
222
|
+
\`\`\`
|
|
223
|
+
|
|
224
|
+
### Session Storage
|
|
225
|
+
\`\`\`typescript
|
|
226
|
+
// Store session
|
|
227
|
+
async function createSession(userId: string, data: SessionData): Promise<string> {
|
|
228
|
+
const sessionId = crypto.randomUUID();
|
|
229
|
+
const key = \`session:\${sessionId}\`;
|
|
230
|
+
|
|
231
|
+
await redis.hset(key, {
|
|
232
|
+
userId,
|
|
233
|
+
...data,
|
|
234
|
+
createdAt: Date.now().toString(),
|
|
235
|
+
});
|
|
236
|
+
await redis.expire(key, 86400); // 24 hours
|
|
237
|
+
|
|
238
|
+
return sessionId;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Get session
|
|
242
|
+
async function getSession(sessionId: string): Promise<SessionData | null> {
|
|
243
|
+
const key = \`session:\${sessionId}\`;
|
|
244
|
+
const data = await redis.hgetall(key);
|
|
245
|
+
|
|
246
|
+
if (!data || Object.keys(data).length === 0) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Refresh TTL on access
|
|
251
|
+
await redis.expire(key, 86400);
|
|
252
|
+
return data as SessionData;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Destroy session
|
|
256
|
+
async function destroySession(sessionId: string): Promise<void> {
|
|
257
|
+
await redis.del(\`session:\${sessionId}\`);
|
|
258
|
+
}
|
|
259
|
+
\`\`\`
|
|
260
|
+
|
|
261
|
+
### Pub/Sub
|
|
262
|
+
\`\`\`typescript
|
|
263
|
+
// Publisher
|
|
264
|
+
const publisher = new Redis();
|
|
265
|
+
await publisher.publish('notifications', JSON.stringify({
|
|
266
|
+
type: 'message',
|
|
267
|
+
userId: '123',
|
|
268
|
+
content: 'Hello!'
|
|
269
|
+
}));
|
|
270
|
+
|
|
271
|
+
// Subscriber
|
|
272
|
+
const subscriber = new Redis();
|
|
273
|
+
subscriber.subscribe('notifications', 'alerts');
|
|
274
|
+
|
|
275
|
+
subscriber.on('message', (channel, message) => {
|
|
276
|
+
console.log(\`Received on \${channel}:\`, JSON.parse(message));
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Pattern subscription
|
|
280
|
+
subscriber.psubscribe('events:*');
|
|
281
|
+
subscriber.on('pmessage', (pattern, channel, message) => {
|
|
282
|
+
console.log(\`Pattern \${pattern}, channel \${channel}:\`, message);
|
|
283
|
+
});
|
|
284
|
+
\`\`\`
|
|
285
|
+
|
|
286
|
+
### Distributed Locking
|
|
287
|
+
\`\`\`typescript
|
|
288
|
+
// Acquire lock with Redlock algorithm (simplified)
|
|
289
|
+
async function acquireLock(
|
|
290
|
+
resource: string,
|
|
291
|
+
ttlMs: number
|
|
292
|
+
): Promise<string | null> {
|
|
293
|
+
const lockKey = \`lock:\${resource}\`;
|
|
294
|
+
const lockValue = crypto.randomUUID();
|
|
295
|
+
|
|
296
|
+
const acquired = await redis.set(lockKey, lockValue, 'NX', 'PX', ttlMs);
|
|
297
|
+
|
|
298
|
+
return acquired ? lockValue : null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Release lock (only if we own it)
|
|
302
|
+
async function releaseLock(resource: string, lockValue: string): Promise<boolean> {
|
|
303
|
+
const script = \`
|
|
304
|
+
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
305
|
+
return redis.call("del", KEYS[1])
|
|
306
|
+
else
|
|
307
|
+
return 0
|
|
308
|
+
end
|
|
309
|
+
\`;
|
|
310
|
+
|
|
311
|
+
const result = await redis.eval(script, 1, \`lock:\${resource}\`, lockValue);
|
|
312
|
+
return result === 1;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Usage
|
|
316
|
+
const lockValue = await acquireLock('order:123', 30000);
|
|
317
|
+
if (lockValue) {
|
|
318
|
+
try {
|
|
319
|
+
// Do critical work
|
|
320
|
+
} finally {
|
|
321
|
+
await releaseLock('order:123', lockValue);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
\`\`\`
|
|
325
|
+
|
|
326
|
+
### Streams (Event Log)
|
|
327
|
+
\`\`\`typescript
|
|
328
|
+
// Add to stream
|
|
329
|
+
await redis.xadd('events', '*', 'type', 'order_created', 'orderId', '123');
|
|
330
|
+
|
|
331
|
+
// Read from stream
|
|
332
|
+
const events = await redis.xrange('events', '-', '+', 'COUNT', 100);
|
|
333
|
+
|
|
334
|
+
// Consumer group
|
|
335
|
+
await redis.xgroup('CREATE', 'events', 'workers', '$', 'MKSTREAM');
|
|
336
|
+
|
|
337
|
+
// Read as consumer
|
|
338
|
+
const messages = await redis.xreadgroup(
|
|
339
|
+
'GROUP', 'workers', 'worker-1',
|
|
340
|
+
'COUNT', 10,
|
|
341
|
+
'BLOCK', 5000,
|
|
342
|
+
'STREAMS', 'events', '>'
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Acknowledge processed message
|
|
346
|
+
await redis.xack('events', 'workers', messageId);
|
|
347
|
+
\`\`\`
|
|
348
|
+
|
|
349
|
+
## Transactions & Pipelines
|
|
350
|
+
\`\`\`typescript
|
|
351
|
+
// Pipeline (batch commands, no atomicity)
|
|
352
|
+
const pipeline = redis.pipeline();
|
|
353
|
+
pipeline.set('key1', 'value1');
|
|
354
|
+
pipeline.set('key2', 'value2');
|
|
355
|
+
pipeline.get('key1');
|
|
356
|
+
const results = await pipeline.exec();
|
|
357
|
+
|
|
358
|
+
// Transaction (atomic)
|
|
359
|
+
const multi = redis.multi();
|
|
360
|
+
multi.incr('counter');
|
|
361
|
+
multi.set('lastUpdated', Date.now().toString());
|
|
362
|
+
const results = await multi.exec();
|
|
363
|
+
|
|
364
|
+
// Watch for optimistic locking
|
|
365
|
+
await redis.watch('balance');
|
|
366
|
+
const balance = parseInt(await redis.get('balance') || '0');
|
|
367
|
+
if (balance >= 100) {
|
|
368
|
+
const multi = redis.multi();
|
|
369
|
+
multi.decrby('balance', 100);
|
|
370
|
+
multi.incr('purchases');
|
|
371
|
+
await multi.exec(); // Returns null if balance changed
|
|
372
|
+
}
|
|
373
|
+
\`\`\`
|
|
374
|
+
|
|
375
|
+
## ❌ DON'T
|
|
376
|
+
- Store large values (>100KB)
|
|
377
|
+
- Use KEYS command in production (use SCAN)
|
|
378
|
+
- Forget to set TTL on cache keys
|
|
379
|
+
- Store sensitive data without encryption
|
|
380
|
+
- Use blocking operations on main connection
|
|
381
|
+
- Ignore connection errors/reconnection
|
|
382
|
+
|
|
383
|
+
## ✅ DO
|
|
384
|
+
- Use appropriate data structures for use case
|
|
385
|
+
- Set TTL for all cache/temporary keys
|
|
386
|
+
- Use pipelines for multiple operations
|
|
387
|
+
- Use connection pooling in high-traffic apps
|
|
388
|
+
- Monitor memory usage
|
|
389
|
+
- Use Lua scripts for complex atomic operations
|
|
390
|
+
- Design clear key naming conventions (namespace:id:field)
|
|
391
|
+
- Use separate connections for pub/sub
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Refactoring Skill
|
|
2
|
+
|
|
3
|
+
## When to Refactor
|
|
4
|
+
- Before adding new features
|
|
5
|
+
- After getting tests passing
|
|
6
|
+
- When you see duplication
|
|
7
|
+
- When code is hard to understand
|
|
8
|
+
- During code review feedback
|
|
9
|
+
|
|
10
|
+
## Code Smells to Watch For
|
|
11
|
+
|
|
12
|
+
### Complexity Smells
|
|
13
|
+
- **Long Method**: > 20 lines, hard to understand
|
|
14
|
+
- **Large Class**: Too many responsibilities
|
|
15
|
+
- **Long Parameter List**: > 3 parameters
|
|
16
|
+
- **Deep Nesting**: > 3 levels of indentation
|
|
17
|
+
- **Complex Conditionals**: Hard to follow logic
|
|
18
|
+
|
|
19
|
+
### Duplication Smells
|
|
20
|
+
- **Repeated Code**: Same logic in multiple places
|
|
21
|
+
- **Similar Classes**: Near-identical structures
|
|
22
|
+
- **Magic Numbers**: Unexplained numeric values
|
|
23
|
+
|
|
24
|
+
### Coupling Smells
|
|
25
|
+
- **Feature Envy**: Method uses another class's data excessively
|
|
26
|
+
- **Inappropriate Intimacy**: Classes too dependent on each other
|
|
27
|
+
- **Message Chains**: a.getB().getC().getD()
|
|
28
|
+
|
|
29
|
+
## Common Refactorings
|
|
30
|
+
|
|
31
|
+
### Extract Function
|
|
32
|
+
\`\`\`typescript
|
|
33
|
+
// ❌ BEFORE
|
|
34
|
+
function processOrder(order) {
|
|
35
|
+
// 30 lines of validation
|
|
36
|
+
// 20 lines of calculation
|
|
37
|
+
// 15 lines of notification
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ✅ AFTER
|
|
41
|
+
function processOrder(order) {
|
|
42
|
+
validateOrder(order);
|
|
43
|
+
const total = calculateTotal(order);
|
|
44
|
+
notifyCustomer(order, total);
|
|
45
|
+
}
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
### Replace Conditional with Polymorphism
|
|
49
|
+
\`\`\`typescript
|
|
50
|
+
// ❌ BEFORE
|
|
51
|
+
function getPrice(type) {
|
|
52
|
+
if (type === 'basic') return 10;
|
|
53
|
+
if (type === 'premium') return 20;
|
|
54
|
+
if (type === 'enterprise') return 50;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ✅ AFTER
|
|
58
|
+
const PRICES = { basic: 10, premium: 20, enterprise: 50 };
|
|
59
|
+
const getPrice = (type) => PRICES[type];
|
|
60
|
+
\`\`\`
|
|
61
|
+
|
|
62
|
+
### Early Return (Guard Clauses)
|
|
63
|
+
\`\`\`typescript
|
|
64
|
+
// ❌ BEFORE
|
|
65
|
+
function process(user) {
|
|
66
|
+
if (user) {
|
|
67
|
+
if (user.isActive) {
|
|
68
|
+
// deep nesting...
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ✅ AFTER
|
|
74
|
+
function process(user) {
|
|
75
|
+
if (!user) return;
|
|
76
|
+
if (!user.isActive) return;
|
|
77
|
+
// flat code...
|
|
78
|
+
}
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
81
|
+
### Introduce Parameter Object
|
|
82
|
+
\`\`\`typescript
|
|
83
|
+
// ❌ BEFORE
|
|
84
|
+
function createUser(name, email, age, role, department) { }
|
|
85
|
+
|
|
86
|
+
// ✅ AFTER
|
|
87
|
+
interface CreateUserParams {
|
|
88
|
+
name: string;
|
|
89
|
+
email: string;
|
|
90
|
+
age?: number;
|
|
91
|
+
role: string;
|
|
92
|
+
department: string;
|
|
93
|
+
}
|
|
94
|
+
function createUser(params: CreateUserParams) { }
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
### Replace Magic Numbers
|
|
98
|
+
\`\`\`typescript
|
|
99
|
+
// ❌ BEFORE
|
|
100
|
+
if (user.age >= 18) { }
|
|
101
|
+
setTimeout(fn, 86400000);
|
|
102
|
+
|
|
103
|
+
// ✅ AFTER
|
|
104
|
+
const MINIMUM_AGE = 18;
|
|
105
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
106
|
+
if (user.age >= MINIMUM_AGE) { }
|
|
107
|
+
setTimeout(fn, ONE_DAY_MS);
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
### Strangler Fig Pattern (Large Refactors)
|
|
111
|
+
\`\`\`
|
|
112
|
+
1. Create new implementation alongside old
|
|
113
|
+
2. Route traffic gradually to new code
|
|
114
|
+
3. Monitor for issues
|
|
115
|
+
4. Remove old code when confident
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
118
|
+
## IDE Shortcuts (VS Code)
|
|
119
|
+
|
|
120
|
+
| Action | Windows/Linux | Mac |
|
|
121
|
+
|--------|---------------|-----|
|
|
122
|
+
| Rename Symbol | F2 | F2 |
|
|
123
|
+
| Extract Function | Ctrl+Shift+R | Cmd+Shift+R |
|
|
124
|
+
| Extract Variable | Ctrl+Shift+R | Cmd+Shift+R |
|
|
125
|
+
| Move Line Up/Down | Alt+↑/↓ | Opt+↑/↓ |
|
|
126
|
+
| Duplicate Line | Shift+Alt+↓ | Shift+Opt+↓ |
|
|
127
|
+
| Delete Line | Ctrl+Shift+K | Cmd+Shift+K |
|
|
128
|
+
| Quick Fix | Ctrl+. | Cmd+. |
|
|
129
|
+
| Go to Definition | F12 | F12 |
|
|
130
|
+
| Find All References | Shift+F12 | Shift+F12 |
|
|
131
|
+
|
|
132
|
+
## ❌ DON'T
|
|
133
|
+
- Refactor without tests
|
|
134
|
+
- Refactor and add features at same time
|
|
135
|
+
- Make huge changes in one commit
|
|
136
|
+
- Refactor code you don't understand
|
|
137
|
+
|
|
138
|
+
## ✅ DO
|
|
139
|
+
- Small, incremental changes
|
|
140
|
+
- Run tests after each change
|
|
141
|
+
- Commit frequently
|
|
142
|
+
- Use IDE refactoring tools (safer than manual)
|
|
143
|
+
- Leave code cleaner than you found it
|