@bloomneo/appkit 1.5.1 โ†’ 1.5.2

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 (111) hide show
  1. package/AGENTS.md +195 -0
  2. package/CHANGELOG.md +253 -0
  3. package/README.md +147 -799
  4. package/bin/commands/generate.js +7 -7
  5. package/cookbook/README.md +26 -0
  6. package/cookbook/api-key-service.ts +106 -0
  7. package/cookbook/auth-protected-crud.ts +112 -0
  8. package/cookbook/file-upload-pipeline.ts +113 -0
  9. package/cookbook/multi-tenant-saas.ts +87 -0
  10. package/cookbook/real-time-chat.ts +121 -0
  11. package/dist/auth/auth.d.ts +21 -4
  12. package/dist/auth/auth.d.ts.map +1 -1
  13. package/dist/auth/auth.js +56 -44
  14. package/dist/auth/auth.js.map +1 -1
  15. package/dist/auth/defaults.d.ts +1 -1
  16. package/dist/auth/defaults.js +35 -35
  17. package/dist/cache/cache.d.ts +29 -6
  18. package/dist/cache/cache.d.ts.map +1 -1
  19. package/dist/cache/cache.js +72 -44
  20. package/dist/cache/cache.js.map +1 -1
  21. package/dist/cache/defaults.js +25 -25
  22. package/dist/cache/index.d.ts +19 -10
  23. package/dist/cache/index.d.ts.map +1 -1
  24. package/dist/cache/index.js +21 -18
  25. package/dist/cache/index.js.map +1 -1
  26. package/dist/config/defaults.d.ts +1 -1
  27. package/dist/config/defaults.js +8 -8
  28. package/dist/config/index.d.ts +3 -3
  29. package/dist/config/index.js +4 -4
  30. package/dist/database/adapters/mongoose.js +2 -2
  31. package/dist/database/adapters/prisma.js +2 -2
  32. package/dist/database/defaults.d.ts +1 -1
  33. package/dist/database/defaults.js +4 -4
  34. package/dist/database/index.js +2 -2
  35. package/dist/database/index.js.map +1 -1
  36. package/dist/email/defaults.js +20 -20
  37. package/dist/error/defaults.d.ts +1 -1
  38. package/dist/error/defaults.js +12 -12
  39. package/dist/error/error.d.ts +12 -0
  40. package/dist/error/error.d.ts.map +1 -1
  41. package/dist/error/error.js +19 -0
  42. package/dist/error/error.js.map +1 -1
  43. package/dist/error/index.d.ts +14 -3
  44. package/dist/error/index.d.ts.map +1 -1
  45. package/dist/error/index.js +14 -3
  46. package/dist/error/index.js.map +1 -1
  47. package/dist/event/defaults.js +30 -30
  48. package/dist/logger/defaults.d.ts +1 -1
  49. package/dist/logger/defaults.js +40 -40
  50. package/dist/logger/index.d.ts +1 -0
  51. package/dist/logger/index.d.ts.map +1 -1
  52. package/dist/logger/index.js.map +1 -1
  53. package/dist/logger/logger.d.ts +8 -0
  54. package/dist/logger/logger.d.ts.map +1 -1
  55. package/dist/logger/logger.js +13 -3
  56. package/dist/logger/logger.js.map +1 -1
  57. package/dist/logger/transports/console.js +1 -1
  58. package/dist/logger/transports/http.d.ts +1 -1
  59. package/dist/logger/transports/http.js +1 -1
  60. package/dist/logger/transports/webhook.d.ts +1 -1
  61. package/dist/logger/transports/webhook.js +1 -1
  62. package/dist/queue/defaults.d.ts +2 -2
  63. package/dist/queue/defaults.js +38 -38
  64. package/dist/security/defaults.d.ts +1 -1
  65. package/dist/security/defaults.js +29 -29
  66. package/dist/security/index.d.ts +1 -1
  67. package/dist/security/index.js +3 -3
  68. package/dist/security/security.d.ts +1 -1
  69. package/dist/security/security.js +4 -4
  70. package/dist/storage/defaults.js +19 -19
  71. package/dist/util/defaults.d.ts +1 -1
  72. package/dist/util/defaults.js +34 -34
  73. package/dist/util/env.d.ts +35 -0
  74. package/dist/util/env.d.ts.map +1 -0
  75. package/dist/util/env.js +50 -0
  76. package/dist/util/env.js.map +1 -0
  77. package/dist/util/errors.d.ts +52 -0
  78. package/dist/util/errors.d.ts.map +1 -0
  79. package/dist/util/errors.js +82 -0
  80. package/dist/util/errors.js.map +1 -0
  81. package/examples/.env.example +80 -0
  82. package/examples/README.md +16 -0
  83. package/examples/auth.ts +228 -0
  84. package/examples/cache.ts +36 -0
  85. package/examples/config.ts +45 -0
  86. package/examples/database.ts +69 -0
  87. package/examples/email.ts +53 -0
  88. package/examples/error.ts +50 -0
  89. package/examples/event.ts +42 -0
  90. package/examples/logger.ts +41 -0
  91. package/examples/queue.ts +58 -0
  92. package/examples/security.ts +46 -0
  93. package/examples/storage.ts +44 -0
  94. package/examples/util.ts +47 -0
  95. package/llms.txt +591 -0
  96. package/package.json +19 -10
  97. package/src/auth/README.md +850 -0
  98. package/src/cache/README.md +756 -0
  99. package/src/config/README.md +604 -0
  100. package/src/database/README.md +818 -0
  101. package/src/email/README.md +759 -0
  102. package/src/error/README.md +660 -0
  103. package/src/event/README.md +729 -0
  104. package/src/logger/README.md +435 -0
  105. package/src/queue/README.md +851 -0
  106. package/src/security/README.md +612 -0
  107. package/src/storage/README.md +1008 -0
  108. package/src/util/README.md +955 -0
  109. package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
  110. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
  111. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
@@ -0,0 +1,756 @@
1
+ # @bloomneo/appkit - Cache Module โšก
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@bloomneo/appkit.svg)](https://www.npmjs.com/package/@bloomneo/appkit)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ > Ultra-simple caching that just works - One function, automatic Redis/Memory
7
+ > strategy, zero configuration
8
+
9
+ **One function** returns a cache object with automatic strategy selection. Zero
10
+ configuration needed, production-ready performance by default.
11
+
12
+ ## ๐Ÿš€ Why Choose This?
13
+
14
+ - โšก One Function - Just cacheClass.get() (optional namespace), everything else
15
+ is automatic
16
+ - **๐ŸŽฏ Auto-Strategy** - REDIS_URL = Redis, no URL = Memory
17
+ - **๐Ÿ”ง Zero Configuration** - Smart defaults for everything
18
+ - **๐Ÿ  Namespace Isolation** - `users`, `sessions` - completely separate
19
+ - **โฐ TTL Management** - Automatic expiration
20
+ - **๐Ÿ”’ Production Ready** - Redis clustering, memory limits, graceful
21
+ degradation
22
+ - **๐Ÿค– AI-Ready** - Optimized for LLM code generation
23
+
24
+ ## ๐Ÿ“ฆ Installation
25
+
26
+ ```bash
27
+ npm install @bloomneo/appkit
28
+ ```
29
+
30
+ ## ๐Ÿƒโ€โ™‚๏ธ Quick Start (30 seconds)
31
+
32
+ ```typescript
33
+ import { cacheClass } from '@bloomneo/appkit/cache';
34
+
35
+ const cache = cacheClass.get('users');
36
+
37
+ // Set data with 1 hour expiration
38
+ await cache.set('user:123', { name: 'John' }, 3600);
39
+
40
+ // Get data
41
+ const user = await cache.get('user:123');
42
+ console.log(user); // { name: 'John' }
43
+
44
+ // Delete data
45
+ await cache.delete('user:123');
46
+ ```
47
+
48
+ ## ๐ŸŒ Environment Variables
49
+
50
+ ```bash
51
+ # Development (automatic Memory cache)
52
+ # No environment variables needed!
53
+
54
+ # Production (automatic Redis cache)
55
+ REDIS_URL=redis://localhost:6379
56
+ ```
57
+
58
+ ## ๐Ÿค– LLM Quick Reference - Copy These Patterns
59
+
60
+ ### **Basic Cache Operations (Copy Exactly)**
61
+
62
+ ```typescript
63
+ // โœ… CORRECT - Complete cache setup
64
+ import { cacheClass } from '@bloomneo/appkit/cache';
65
+ const cache = cacheClass.get('namespace');
66
+
67
+ // Cache operations
68
+ await cache.set('key', data, 3600); // Set with TTL
69
+ const data = await cache.get('key'); // Get (null if not found)
70
+ await cache.delete('key'); // Delete key
71
+ await cache.clear(); // Clear namespace
72
+
73
+ // Cache-aside pattern
74
+ const data = await cache.getOrSet(
75
+ 'key',
76
+ async () => {
77
+ return await fetchFromDatabase();
78
+ },
79
+ 3600
80
+ );
81
+ ```
82
+
83
+ ### **Namespace Usage (Copy These)**
84
+
85
+ ```typescript
86
+ // โœ… CORRECT - Separate namespaces for different data
87
+ const userCache = cacheClass.get('users');
88
+ const sessionCache = cacheClass.get('sessions');
89
+ const apiCache = cacheClass.get('external-api');
90
+
91
+ // Each namespace is completely isolated
92
+ await userCache.set('123', userData);
93
+ await sessionCache.set('123', sessionData); // Different from user:123
94
+ ```
95
+
96
+ ### **Error Handling (Copy This Pattern)**
97
+
98
+ ```typescript
99
+ import { cacheClass, CacheError } from '@bloomneo/appkit/cache';
100
+
101
+ // โœ… CORRECT - Distinguish cache failures from other errors
102
+ async function getUser(id: number) {
103
+ try {
104
+ const user = await userCache.get<User>(`user:${id}`);
105
+ if (user) return user;
106
+
107
+ // Cache miss โ€” fetch from DB and populate cache
108
+ const fresh = await database.getUser(id);
109
+ await userCache.set(`user:${id}`, fresh, 3600);
110
+ return fresh;
111
+ } catch (err) {
112
+ if (err instanceof CacheError) {
113
+ // Cache infrastructure failed โ€” fall back silently
114
+ logger.warn('Cache unavailable', { code: err.code, message: err.message });
115
+ return await database.getUser(id);
116
+ }
117
+ throw err; // DB error or other โ€” caller's problem
118
+ }
119
+ }
120
+ ```
121
+
122
+ ## โš ๏ธ Common LLM Mistakes - Avoid These
123
+
124
+ ### **Wrong Cache Usage**
125
+
126
+ ```typescript
127
+ // โŒ WRONG - Don't access strategies directly
128
+ import { RedisStrategy } from '@bloomneo/appkit/cache';
129
+ const redis = new RedisStrategy(); // Wrong!
130
+
131
+ // โŒ WRONG - Missing TTL for temporary data
132
+ await cache.set('temp', data); // Always set TTL for temp data
133
+
134
+ // โŒ WRONG - Using same namespace for different data types
135
+ const cache = cacheClass.get('data'); // Be specific
136
+ await cache.set('user:123', userData);
137
+ await cache.set('session:456', sessionData); // Use separate namespaces
138
+
139
+ // โœ… CORRECT - Use cacheClass.get() with specific namespaces
140
+ const userCache = cacheClass.get('users');
141
+ const sessionCache = cacheClass.get('sessions');
142
+ ```
143
+
144
+ ### **Wrong Error Handling**
145
+
146
+ ```typescript
147
+ // โŒ WRONG - Crashing on cache miss
148
+ const user = await cache.get('user:123');
149
+ console.log(user.name); // Will crash if user is null
150
+
151
+ // โŒ WRONG - Not handling cache failures
152
+ const user = await cache.get('user:123');
153
+ if (!user) {
154
+ throw new Error('User not found'); // Should fallback to database
155
+ }
156
+
157
+ // โœ… CORRECT - Safe cache access with fallback
158
+ const user = await cache.get('user:123');
159
+ if (!user) {
160
+ user = await database.getUser(123); // Fallback to database
161
+ await cache.set('user:123', user, 3600); // Cache result
162
+ }
163
+ ```
164
+
165
+ ### **Wrong Testing**
166
+
167
+ ```typescript
168
+ // โŒ WRONG - No cleanup between tests
169
+ test('should cache user', async () => {
170
+ await cache.set('user:123', userData);
171
+ // Missing: await cacheClass.clear();
172
+ });
173
+
174
+ // โœ… CORRECT - Proper test cleanup
175
+ afterEach(async () => {
176
+ await cacheClass.flushAll(); // flushAll() clears data; clear() disconnects instances
177
+ });
178
+ ```
179
+
180
+ ## ๐Ÿšจ Error Handling Patterns
181
+
182
+ Cache operations throw `CacheError` when the underlying strategy fails (Redis
183
+ down, serialization error, connection timeout). Use `instanceof CacheError` to
184
+ distinguish cache infrastructure failures from your own errors.
185
+
186
+ ```typescript
187
+ import { cacheClass, CacheError } from '@bloomneo/appkit/cache';
188
+ ```
189
+
190
+ ### **Cache-Aside with Fallback**
191
+
192
+ ```typescript
193
+ async function getUserProfile(userId: number) {
194
+ const cache = cacheClass.get('profiles');
195
+ try {
196
+ const profile = await cache.get<UserProfile>(`profile:${userId}`);
197
+ if (profile) return profile;
198
+
199
+ const fresh = await database.getUserProfile(userId);
200
+ if (fresh) await cache.set(`profile:${userId}`, fresh, 1800);
201
+ return fresh;
202
+ } catch (err) {
203
+ if (err instanceof CacheError) {
204
+ logger.warn('Cache unavailable, falling back to DB', { code: err.code });
205
+ return database.getUserProfile(userId);
206
+ }
207
+ throw err;
208
+ }
209
+ }
210
+ ```
211
+
212
+ ### **Session Management**
213
+
214
+ ```typescript
215
+ async function getSession(sessionId: string) {
216
+ const cache = cacheClass.get('sessions');
217
+ try {
218
+ return await cache.get<Session>(`session:${sessionId}`);
219
+ // Returns null if not found โ€” handle that in the caller, not here
220
+ } catch (err) {
221
+ if (err instanceof CacheError) {
222
+ logger.warn('Session cache unavailable', { code: err.code });
223
+ return null; // safe to degrade โ€” caller will redirect to login
224
+ }
225
+ throw err;
226
+ }
227
+ }
228
+
229
+ async function createSession(userId: number): Promise<string> {
230
+ const cache = cacheClass.get('sessions');
231
+ const sessionId = crypto.randomUUID();
232
+ try {
233
+ await cache.set(`session:${sessionId}`, { userId, loginTime: Date.now() }, 7200);
234
+ } catch (err) {
235
+ if (err instanceof CacheError) {
236
+ logger.warn('Session not cached โ€” Redis unavailable', { code: err.code });
237
+ // Session is still valid โ€” just not cached. Return it.
238
+ } else {
239
+ throw err;
240
+ }
241
+ }
242
+ return sessionId;
243
+ }
244
+ ```
245
+
246
+ ### **API Response Caching**
247
+
248
+ ```typescript
249
+ async function getWeather(city: string) {
250
+ const cache = cacheClass.get('weather');
251
+ const key = `weather:${city.toLowerCase()}`;
252
+
253
+ try {
254
+ const cached = await cache.get<WeatherData>(key);
255
+ if (cached) return cached;
256
+
257
+ const data = await fetchWeatherFromApi(city);
258
+ await cache.set(key, data, 1800); // 30 min
259
+ return data;
260
+ } catch (err) {
261
+ if (err instanceof CacheError) {
262
+ logger.warn('Cache unavailable for weather data', { code: err.code });
263
+ return fetchWeatherFromApi(city); // bypass cache entirely
264
+ }
265
+ throw err; // API error โ€” let it propagate
266
+ }
267
+ }
268
+ ```
269
+
270
+ ## ๐Ÿ”’ Security & Production
271
+
272
+ ### **Production Configuration**
273
+
274
+ ```bash
275
+ # โœ… SECURE - Production Redis with auth
276
+ REDIS_URL=redis://username:password@redis-host:6379/0
277
+
278
+ # โœ… SECURE - Redis with TLS
279
+ REDIS_URL=rediss://username:password@redis-host:6380/0
280
+
281
+ # โœ… PERFORMANCE - Custom timeouts
282
+ BLOOM_CACHE_TTL=3600 # 1 hour default TTL
283
+ BLOOM_CACHE_REDIS_CONNECT_TIMEOUT=10000 # 10 second connect timeout
284
+ BLOOM_CACHE_REDIS_COMMAND_TIMEOUT=5000 # 5 second command timeout
285
+ ```
286
+
287
+ ### **Production Checklist**
288
+
289
+ - โœ… **Redis Connection**: Set secure `REDIS_URL` with authentication
290
+ - โœ… **TTL Strategy**: Set appropriate `BLOOM_CACHE_TTL` for your use case
291
+ - โœ… **Error Handling**: Implement fallback logic for cache failures
292
+ - โœ… **Monitoring**: Log cache hit/miss rates and errors
293
+ - โœ… **Memory Limits**: Configure Redis memory limits and eviction policies
294
+ - โœ… **Clustering**: Use Redis Cluster for high availability
295
+
296
+ ### **Security Best Practices**
297
+
298
+ ```typescript
299
+ // โœ… Namespace isolation prevents key collisions
300
+ const userCache = cacheClass.get('users');
301
+ const adminCache = cacheClass.get('admin'); // Completely separate
302
+
303
+ // โœ… TTL prevents indefinite data retention
304
+ await cache.set('temp:token', token, 300); // 5 minutes only
305
+
306
+ // โœ… Safe error handling prevents information leakage
307
+ try {
308
+ const data = await cache.get('sensitive:data');
309
+ return data;
310
+ } catch (error) {
311
+ console.error('Cache error:', error.message);
312
+ return null; // Don't expose cache errors to users
313
+ }
314
+ ```
315
+
316
+ ### **Memory Strategy Security**
317
+
318
+ ```bash
319
+ # โœ… SECURE - Memory limits for development
320
+ BLOOM_CACHE_MEMORY_MAX_ITEMS=10000 # Max items in memory
321
+ BLOOM_CACHE_MEMORY_MAX_SIZE=100000000 # 100MB memory limit
322
+ ```
323
+
324
+ ## ๐Ÿ“– Complete API
325
+
326
+ ### Core Function
327
+
328
+ ```typescript
329
+ const cache = cacheClass.get(namespace); // One function, everything you need
330
+ ```
331
+
332
+ ### Cache Operations
333
+
334
+ ```typescript
335
+ await cache.get(key); // Get value (null if not found)
336
+ await cache.set(key, value, ttl?); // Set value with TTL in seconds
337
+ await cache.delete(key); // Remove key
338
+ await cache.clear(); // Clear entire namespace
339
+ await cache.getOrSet(key, factory, ttl?); // Get cached or compute and cache
340
+ ```
341
+
342
+ ### Utility Methods
343
+
344
+ ```typescript
345
+ cache.getStrategy(); // 'redis' or 'memory'
346
+ cacheClass.hasRedis(); // true if REDIS_URL is set
347
+ cacheClass.getActiveNamespaces(); // List of active namespaces
348
+ cacheClass.getConfig(); // Configuration summary
349
+ ```
350
+
351
+ ## ๐Ÿ’ก Usage Examples
352
+
353
+ ### **Basic User Caching**
354
+
355
+ ```typescript
356
+ import { cacheClass } from '@bloomneo/appkit/cache';
357
+
358
+ const userCache = cacheClass.get('users');
359
+
360
+ async function getUser(id) {
361
+ // Try cache first
362
+ let user = await userCache.get(`user:${id}`);
363
+
364
+ if (!user) {
365
+ // Get from database
366
+ user = await db.users.findById(id);
367
+
368
+ // Cache for 1 hour
369
+ await userCache.set(`user:${id}`, user, 3600);
370
+ }
371
+
372
+ return user;
373
+ }
374
+ ```
375
+
376
+ ### **API Response Caching**
377
+
378
+ ```typescript
379
+ import { cacheClass } from '@bloomneo/appkit/cache';
380
+
381
+ const apiCache = cacheClass.get('external-api');
382
+
383
+ async function getWeather(city) {
384
+ return await apiCache.getOrSet(
385
+ `weather:${city}`,
386
+ async () => {
387
+ // This only runs on cache miss
388
+ const response = await fetch(
389
+ `https://api.weather.com/v1/weather?q=${city}`
390
+ );
391
+ return await response.json();
392
+ },
393
+ 1800 // Cache for 30 minutes
394
+ );
395
+ }
396
+
397
+ // First call: hits API
398
+ const weather1 = await getWeather('london');
399
+
400
+ // Second call: returns cached result (fast!)
401
+ const weather2 = await getWeather('london');
402
+ ```
403
+
404
+ ### **Session Management**
405
+
406
+ ```typescript
407
+ import { cacheClass } from '@bloomneo/appkit/cache';
408
+
409
+ const sessionCache = cacheClass.get('sessions');
410
+
411
+ // Store session
412
+ async function createSession(userId) {
413
+ const sessionId = crypto.randomUUID();
414
+ const sessionData = { userId, loginTime: Date.now() };
415
+
416
+ // Store for 2 hours
417
+ await sessionCache.set(`session:${sessionId}`, sessionData, 7200);
418
+
419
+ return sessionId;
420
+ }
421
+
422
+ // Get session
423
+ async function getSession(sessionId) {
424
+ return await sessionCache.get(`session:${sessionId}`);
425
+ }
426
+
427
+ // Remove session
428
+ async function logout(sessionId) {
429
+ await sessionCache.delete(`session:${sessionId}`);
430
+ }
431
+ ```
432
+
433
+ ### **Shopping Cart**
434
+
435
+ ```typescript
436
+ import { cacheClass } from '@bloomneo/appkit/cache';
437
+
438
+ const cartCache = cacheClass.get('shopping-carts');
439
+
440
+ // Add item to cart
441
+ async function addToCart(userId, item) {
442
+ const cart = (await cartCache.get(`cart:${userId}`)) || [];
443
+ cart.push(item);
444
+
445
+ // Cart expires in 24 hours
446
+ await cartCache.set(`cart:${userId}`, cart, 86400);
447
+ }
448
+
449
+ // Get cart
450
+ async function getCart(userId) {
451
+ return (await cartCache.get(`cart:${userId}`)) || [];
452
+ }
453
+
454
+ // Clear cart
455
+ async function clearCart(userId) {
456
+ await cartCache.delete(`cart:${userId}`);
457
+ }
458
+ ```
459
+
460
+ ### **Rate Limiting Cache**
461
+
462
+ ```typescript
463
+ import { cacheClass } from '@bloomneo/appkit/cache';
464
+
465
+ const rateLimitCache = cacheClass.get('rate-limits');
466
+
467
+ async function checkRateLimit(userId, maxRequests = 100, windowSeconds = 3600) {
468
+ const key = `rate:${userId}:${Math.floor(Date.now() / 1000 / windowSeconds)}`;
469
+
470
+ const current = (await rateLimitCache.get(key)) || 0;
471
+
472
+ if (current >= maxRequests) {
473
+ throw new Error('Rate limit exceeded');
474
+ }
475
+
476
+ // Increment counter
477
+ await rateLimitCache.set(key, current + 1, windowSeconds);
478
+
479
+ return {
480
+ remaining: maxRequests - current - 1,
481
+ resetTime: Math.ceil(Date.now() / 1000 / windowSeconds) * windowSeconds,
482
+ };
483
+ }
484
+ ```
485
+
486
+ ## ๐Ÿ”ง Platform Setup
487
+
488
+ ### **Local Development**
489
+
490
+ ```bash
491
+ # No setup needed - uses memory automatically
492
+ npm start
493
+ ```
494
+
495
+ ### **Production with Docker**
496
+
497
+ ```yaml
498
+ version: '3.8'
499
+ services:
500
+ app:
501
+ image: my-app
502
+ environment:
503
+ REDIS_URL: redis://redis:6379
504
+ redis:
505
+ image: redis:alpine
506
+ command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
507
+ ```
508
+
509
+ ### **Production with Redis Cloud**
510
+
511
+ ```bash
512
+ # Redis Cloud / AWS ElastiCache / Azure Redis
513
+ REDIS_URL=redis://username:password@your-redis-host:6379
514
+
515
+ # Redis Cluster
516
+ REDIS_URL=redis://user:pass@cluster.cache.amazonaws.com:6379
517
+ ```
518
+
519
+ ### **Vercel/Railway/Heroku**
520
+
521
+ ```bash
522
+ # Just add Redis URL in dashboard
523
+ REDIS_URL=redis://your-redis-provider.com:6379
524
+ ```
525
+
526
+ ## ๐Ÿ”„ Development vs Production
527
+
528
+ ### **Development Mode**
529
+
530
+ ```bash
531
+ # No environment variables needed
532
+ NODE_ENV=development
533
+ ```
534
+
535
+ ```typescript
536
+ const cache = cacheClass.get('users');
537
+ // Strategy: Memory (in-process)
538
+ // Features: LRU eviction, TTL cleanup, memory limits
539
+ ```
540
+
541
+ ### **Production Mode**
542
+
543
+ ```bash
544
+ # Redis required for scaling
545
+ NODE_ENV=production
546
+ REDIS_URL=redis://your-redis-host:6379
547
+ ```
548
+
549
+ ```typescript
550
+ const cache = cacheClass.get('users');
551
+ // Strategy: Redis (distributed)
552
+ // Features: Clustering, persistence, atomic operations
553
+ ```
554
+
555
+ ### **Scaling Pattern**
556
+
557
+ ```typescript
558
+ // Week 1: Local development
559
+ // No Redis needed - works immediately
560
+
561
+ // Month 1: Add Redis
562
+ // Set REDIS_URL - zero code changes
563
+
564
+ // Year 1: Redis clustering
565
+ // Update REDIS_URL to cluster - automatic scaling
566
+ ```
567
+
568
+ ## ๐Ÿงช Testing
569
+
570
+ ```typescript
571
+ import { cacheClass } from '@bloomneo/appkit/cache';
572
+
573
+ describe('Cache Tests', () => {
574
+ afterEach(async () => {
575
+ // flushAll() clears cached data. clear() disconnects all instances
576
+ // (use only for full teardown, not between individual tests).
577
+ await cacheClass.flushAll();
578
+ });
579
+
580
+ test('basic caching', async () => {
581
+ const cache = cacheClass.get('test');
582
+
583
+ await cache.set('key', 'value', 60);
584
+ const result = await cache.get('key');
585
+
586
+ expect(result).toBe('value');
587
+ });
588
+
589
+ test('cache miss returns null', async () => {
590
+ const cache = cacheClass.get('test');
591
+ const result = await cache.get('nonexistent');
592
+ expect(result).toBeNull();
593
+ });
594
+
595
+ test('namespace isolation', async () => {
596
+ const cache1 = cacheClass.get('namespace1');
597
+ const cache2 = cacheClass.get('namespace2');
598
+
599
+ await cache1.set('key', 'value1');
600
+ await cache2.set('key', 'value2');
601
+
602
+ expect(await cache1.get('key')).toBe('value1');
603
+ expect(await cache2.get('key')).toBe('value2');
604
+ });
605
+ });
606
+ ```
607
+
608
+ ### **Force Memory Strategy for Tests**
609
+
610
+ ```typescript
611
+ describe('Cache with Memory Strategy', () => {
612
+ beforeEach(async () => {
613
+ // Force memory strategy โ€” no Redis required in CI
614
+ await cacheClass.reset({
615
+ strategy: 'memory',
616
+ memory: {
617
+ maxItems: 1000,
618
+ maxSizeBytes: 1048576, // 1MB
619
+ checkInterval: 60000,
620
+ },
621
+ });
622
+ });
623
+
624
+ afterEach(async () => {
625
+ await cacheClass.flushAll(); // clear data, keep instances alive
626
+ });
627
+ });
628
+ ```
629
+
630
+ ## ๐Ÿ“ˆ Performance
631
+
632
+ - **Memory Strategy**: ~0.1ms per operation
633
+ - **Redis Strategy**: ~1-5ms per operation (network dependent)
634
+ - **Automatic Strategy**: Zero overhead detection
635
+ - **TTL Cleanup**: Background cleanup with minimal impact
636
+ - **Memory Usage**: Configurable limits with LRU eviction
637
+ - **Redis Clustering**: Horizontal scaling support
638
+
639
+ ## ๐Ÿ’ฐ Cost Comparison
640
+
641
+ | Strategy | Speed | Persistence | Scaling | Best For |
642
+ | ---------- | ---------------- | ----------- | ------------- | ---------------------------- |
643
+ | **Memory** | Fastest (~0.1ms) | No | Single server | Development, testing |
644
+ | **Redis** | Fast (~1-5ms) | Yes | Multi-server | Production, distributed apps |
645
+
646
+ ## ๐Ÿ” TypeScript Support
647
+
648
+ ```typescript
649
+ import { cacheClass, CacheError } from '@bloomneo/appkit/cache';
650
+ import type { Cache } from '@bloomneo/appkit/cache';
651
+
652
+ // Generics โ€” no casting needed
653
+ const cache: Cache = cacheClass.get('users');
654
+ const user = await cache.get<User>('user:123'); // User | null
655
+ const ok = await cache.set<User>('user:123', userData); // boolean
656
+ const list = await cache.getOrSet<User[]>('all', fetchUsers, 60); // User[]
657
+
658
+ // Type-narrow infrastructure errors
659
+ try {
660
+ await cache.set('key', value);
661
+ } catch (err) {
662
+ if (err instanceof CacheError) {
663
+ console.error(err.code); // 'CACHE_SET_FAILED' | 'CACHE_INVALID_KEY' | ...
664
+ console.error(err.message); // '[@bloomneo/appkit/cache] set failed for key ...'
665
+ }
666
+ }
667
+ ```
668
+
669
+ ### **CacheError codes**
670
+
671
+ | Code | When |
672
+ |---|---|
673
+ | `CACHE_CONNECT_FAILED` | Strategy failed to connect (Redis unreachable) |
674
+ | `CACHE_GET_FAILED` | Strategy threw during `get` |
675
+ | `CACHE_SET_FAILED` | Strategy threw during `set` |
676
+ | `CACHE_DELETE_FAILED` | Strategy threw during `delete` |
677
+ | `CACHE_CLEAR_FAILED` | Strategy threw during `clear` |
678
+ | `CACHE_INVALID_KEY` | Key is empty, too long, has colons or newlines |
679
+ | `CACHE_INVALID_VALUE` | Value is `undefined` or not JSON-serializable |
680
+ | `CACHE_ERROR` | Generic fallback (use when no specific code fits) |
681
+
682
+ ## ๐Ÿ†š Why Not Redis directly?
683
+
684
+ **Other approaches:**
685
+
686
+ ```javascript
687
+ // Redis directly: Complex setup, manual serialization
688
+ const redis = require('redis');
689
+ const client = redis.createClient(process.env.REDIS_URL);
690
+
691
+ await client.connect();
692
+ const user = JSON.parse(await client.get('user:123'));
693
+ await client.setEx('user:123', 3600, JSON.stringify(userData));
694
+ ```
695
+
696
+ **This library:**
697
+
698
+ ```typescript
699
+ // 3 lines, automatic Redis/Memory, built-in serialization
700
+ import { cacheClass } from '@bloomneo/appkit/cache';
701
+ const cache = cacheClass.get('users');
702
+ await cache.set('user:123', userData, 3600);
703
+ ```
704
+
705
+ **Same features, 90% less code, automatic strategy selection.**
706
+
707
+ ## Agent-Dev Friendliness Score
708
+
709
+ **Score: 75.3/100 โ€” ๐ŸŸก Solid** *(no cap)*
710
+ *Scored 2026-04-11 by Claude ยท Rubric [`AGENT_DEV_SCORING_ALGORITHM.md`](../../AGENT_DEV_SCORING_ALGORITHM.md) v1.1*
711
+
712
+ | # | Dimension | Score | Notes |
713
+ |---|---|---:|---|
714
+ | 1 | API correctness | **9** | `examples/cache.ts` had `cache.has()` (not public) and `cache.del()` (wrong name) โ€” both fixed. |
715
+ | 2 | Doc consistency | **9** | README now matches source after fixing test-cleanup pattern (`flushAll` vs `clear`). |
716
+ | 3 | Runtime verification | **9** | 49 vitest tests covering all public methods + drift-check section. |
717
+ | 4 | Type safety | **5** | `Cache` interface uses `any` for all values โ€” no generics like `cache.get<T>()`. |
718
+ | 5 | Discoverability | **8** | Quick Start is clear and concise. |
719
+ | 6 | Example completeness | **7** | `examples/cache.ts` covers main patterns. Utility methods (`getConfig`, `getActiveNamespaces`) not shown. |
720
+ | 7 | Composability | **8** | `examples/cache.ts` now compiles. Patterns compose naturally. |
721
+ | 8 | Educational errors | **5** | Errors are plain English but missing `[@bloomneo/appkit/cache]` prefix + DOCS_URL anchor format used by auth module. |
722
+ | 9 | Convention enforcement | **9** | One canonical entry point: `cacheClass.get(namespace)`. Consistent across all examples. |
723
+ | 10 | Drift prevention | **5** | Drift-check section in test catches runtime drift. No scripted doc-vs-source checker. |
724
+ | 11 | Reading order | **8** | Quick Start โ†’ LLM ref โ†’ errors โ†’ API โ†’ examples โ†’ testing โ€” no dead ends. |
725
+ | **12** | **Simplicity** | **8** | 5 core ops on instance + 9 class utilities. Dual surface is minimal. |
726
+ | **13** | **Clarity** | **6** | `cacheClass.clear()` (disconnects all instances) vs `cache.clear()` (clears namespace data) โ€” same name, different effects. Confusing. |
727
+ | **14** | **Unambiguity** | **5** | The `clear()` collision is the dominant ambiguity โ€” easy to call the wrong one in teardown. Also: `getOrSet` miss-then-throw behavior (does not cache error path) could surprise users. |
728
+ | **15** | **Learning curve** | **9** | Zero config. `cacheClass.get(ns) โ†’ cache.set/get/delete`. First call in < 2 minutes. |
729
+
730
+ ### Weighted (v1.1)
731
+
732
+ ```
733
+ (9ร—.12)+(9ร—.08)+(9ร—.09)+(5ร—.06)+(8ร—.06)+(7ร—.08)+(8ร—.06)+(5ร—.05)+(9ร—.05)+(5ร—.04)+(8ร—.03)
734
+ +(8ร—.09)+(6ร—.09)+(5ร—.05)+(9ร—.05) = 7.53 โ†’ 75.3/100
735
+ No anti-pattern cap (examples compile after fixes).
736
+ ```
737
+
738
+ ### Gaps to reach ๐ŸŸข 90+
739
+
740
+ 1. **D13/D14 โ†’ 9**: Rename `cacheClass.clear()` โ†’ `cacheClass.disconnect()` (or `shutdown()` โ€” already exists) to eliminate the collision with `cache.clear()`. The two `clear()` callsites should behave consistently.
741
+ 2. **D4 Type safety โ†’ 9**: Add generic overload `get<T>(key: string): Promise<T | null>` and `set<T>(key, value: T, ttl?)`.
742
+ 3. **D8 Educational errors โ†’ 9**: Adopt `[@bloomneo/appkit/cache] message + DOCS_URL#anchor` format from auth module.
743
+ 4. **D10 Drift prevention โ†’ 8**: Scripted doc-vs-source drift checker.
744
+
745
+ **Realistic ceiling:** ~88/100 with fixes 1โ€“4.
746
+
747
+ ## ๐Ÿ“„ License
748
+
749
+ MIT ยฉ [Bloomneo](https://github.com/bloomneo)
750
+
751
+ ---
752
+
753
+ <p align="center">
754
+ <strong>Built with โค๏ธ by the <a href="https://github.com/bloomneo">Bloomneo Team</a></strong><br>
755
+ Because caching should be simple, not a Redis nightmare.
756
+ </p>