@rabstack/rab-api 1.11.1 → 1.12.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 (2) hide show
  1. package/README.md +123 -7
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -355,38 +355,154 @@ export class ListProducts {}
355
355
  - **`url-params`**: Cache key is just the resolved path
356
356
  - `/products?page=1&limit=10` → key: `/products`
357
357
 
358
+ ### How Cache Keys Work
359
+
360
+ Cache keys are deterministically generated from the request URL to ensure consistent cache hits. Understanding how keys are built helps you design effective caching and purge strategies.
361
+
362
+ #### Key Generation Process
363
+
364
+ 1. **Extract the path**: The resolved path (with route params filled in) is extracted from the request
365
+ 2. **Apply strategy**: Based on the configured strategy, query parameters may be included
366
+ 3. **Sort & encode**: Query params are sorted alphabetically and URL-encoded to prevent collisions
367
+
368
+ ```
369
+ Request: GET /stores/123/products?page=2&limit=10&sort=name
370
+
371
+ url-params strategy → /stores/123/products
372
+ url-query strategy → /stores/123/products?limit=10&page=2&sort=name
373
+ ↑ params sorted alphabetically
374
+ ```
375
+
376
+ #### Why Sorting Matters
377
+
378
+ Query parameters are sorted alphabetically to ensure the same cache key regardless of parameter order:
379
+
380
+ ```typescript
381
+ // These requests produce the SAME cache key:
382
+ GET /products?limit=10&page=1
383
+ GET /products?page=1&limit=10
384
+ // Both → /products?limit=10&page=1
385
+ ```
386
+
387
+ #### URL Encoding for Safety
388
+
389
+ Special characters in query values are URL-encoded to prevent cache key collisions:
390
+
391
+ ```typescript
392
+ // Different requests, different cache keys:
393
+ GET /search?q=a&b=2 → /search?b=2&q=a
394
+ GET /search?q=a%26b=2 → /search?q=a%26b%3D2
395
+ ```
396
+
397
+ #### Strategy Selection Guide
398
+
399
+ | Strategy | Use When | Cache Key Example |
400
+ |----------|----------|-------------------|
401
+ | `url-query` | Response varies by query params (pagination, filters) | `/products?limit=10&page=1` |
402
+ | `url-params` | Response is the same regardless of query params | `/products/123` |
403
+
358
404
  ### Cache Purge
359
405
 
360
- Purge cache keys after mutations:
406
+ Purge (invalidate) cache keys after mutations to keep cached data fresh.
407
+
408
+ #### How Purge Works
409
+
410
+ 1. **After successful response**: Purge runs only after the handler returns successfully
411
+ 2. **Pattern resolution**: `:param` placeholders are replaced with actual request param values
412
+ 3. **Background execution**: Purge operations run asynchronously (non-blocking)
413
+ 4. **Silent failures**: Purge errors are caught and ignored to avoid breaking the main response
414
+
415
+ #### Purge Patterns
416
+
417
+ **Static patterns** - Exact cache keys to invalidate:
361
418
 
362
419
  ```typescript
363
420
  @Post('/products', {
364
421
  cache: {
365
- purge: ['/products'], // Purge this key after successful response
422
+ purge: ['/products'], // Purge the list endpoint
366
423
  }
367
424
  })
368
425
  export class CreateProduct {}
426
+ ```
369
427
 
370
- // With dynamic params
428
+ **Dynamic patterns** - Use `:param` placeholders resolved from request params:
429
+
430
+ ```typescript
371
431
  @Put('/products/:id', {
372
432
  cache: {
373
433
  purge: [
374
- '/products',
375
- '/products/:id', // :id is resolved from request params
434
+ '/products', // Purge the list
435
+ '/products/:id', // :id resolved to actual value (e.g., /products/123)
376
436
  ]
377
437
  }
378
438
  })
379
439
  export class UpdateProduct {}
440
+ ```
380
441
 
381
- // Function-based purge
442
+ **Function patterns** - Full control with access to the request object:
443
+
444
+ ```typescript
382
445
  @Delete('/products/:id', {
383
446
  cache: {
384
- purge: [(req) => `/products/${req.params.id}`]
447
+ purge: [
448
+ (req) => `/products/${req.params.id}`,
449
+ (req) => `/categories/${req.body.categoryId}/products`, // Access body
450
+ (req) => [ // Return multiple keys
451
+ `/products/${req.params.id}`,
452
+ `/products/${req.params.id}/reviews`,
453
+ ],
454
+ ]
385
455
  }
386
456
  })
387
457
  export class DeleteProduct {}
388
458
  ```
389
459
 
460
+ #### Purge Flow Diagram
461
+
462
+ ```
463
+ Request: DELETE /stores/123/products/456
464
+
465
+ 1. Handler executes successfully
466
+ 2. Purge patterns resolved:
467
+ - '/stores/:storeId/products' → '/stores/123/products'
468
+ - '/stores/:storeId/products/:id' → '/stores/123/products/456'
469
+ 3. cacheAdapter.del() called for each key (async, non-blocking)
470
+ 4. Response returned to client immediately
471
+ ```
472
+
473
+ #### Common Purge Patterns
474
+
475
+ ```typescript
476
+ // List + detail invalidation
477
+ @Put('/products/:id', {
478
+ cache: {
479
+ purge: ['/products', '/products/:id']
480
+ }
481
+ })
482
+
483
+ // Hierarchical invalidation
484
+ @Delete('/stores/:storeId/products/:id', {
485
+ cache: {
486
+ purge: [
487
+ '/stores/:storeId/products', // List
488
+ '/stores/:storeId/products/:id', // Detail
489
+ '/stores/:storeId', // Parent
490
+ ]
491
+ }
492
+ })
493
+
494
+ // Cross-entity invalidation
495
+ @Post('/orders', {
496
+ cache: {
497
+ purge: [
498
+ '/orders',
499
+ (req) => `/users/${req.auth.userId}/orders`, // User's orders
500
+ (req) => req.body.items.map(i => `/products/${i.productId}/stock`),
501
+ ]
502
+ }
503
+ })
504
+ ```
505
+
390
506
  ### Example: Redis Adapter
391
507
 
392
508
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rabstack/rab-api",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "description": "A TypeScript REST API framework built on Express.js with decorator-based routing, dependency injection, and built-in validation",
5
5
  "author": "Softin",
6
6
  "license": "MIT",