Skip to content

Instantly share code, notes, and snippets.

@adevinwild
Created June 28, 2024 10:09
Show Gist options
  • Save adevinwild/b526fd8f12c9d93b3999547e4a1aee8c to your computer and use it in GitHub Desktop.
Save adevinwild/b526fd8f12c9d93b3999547e4a1aee8c to your computer and use it in GitHub Desktop.
An implementation of the RedisCacheService on the ProductService for Medusa 1.20.x
import type RedisCacheService from '@medusajs/cache-redis/dist/services/redis-cache';
import { ProductService as MedusaProductService, Product } from '@medusajs/medusa';
import { CreateProductInput, FindProductConfig, ProductSelector, UpdateProductInput } from '@medusajs/medusa/dist/types/product';
import type { Redis } from 'ioredis';
type InjectedDependencies = {
cacheService: RedisCacheService
redisClient: Redis
}
class ProductService extends MedusaProductService {
protected readonly cacheService_: RedisCacheService
protected readonly redisClient_: Redis
static cacheKeyPrefix = 'products:listAndCount';
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments);
this.cacheService_ = container.cacheService;
this.redisClient_ = container.redisClient;
}
async listAndCount(selector: ProductSelector, config?: FindProductConfig): Promise<[Product[], number]> {
const cacheKey = this.generateCacheKey(selector, config); // Just to be sure that we have the same keys in the same order
const ttl = 60 // 1 minute, TTL is in seconds (adjust as needed)
// Check if the cache is expired
const cached = await this.cacheService_.get(cacheKey) as { products: Product[], count: number };
if (cached) {
console.log('cached data found');
// Return the cached data if it's not expired
return [cached.products, cached.count];
}
const [products, count] = await super.listAndCount(selector, config);
// Otherwise, set the cache with the new data
await this.cacheService_.set(cacheKey, { products, count }, ttl);
return [products, count];
}
async create(productObject: CreateProductInput): Promise<Product> {
const product = await super.create(productObject);
// Whenever a product is created, we want to invalidate the cache
await this.cacheService_.invalidate(`${ProductService.cacheKeyPrefix}:*`);
return product;
}
async update(productId: string, update: UpdateProductInput): Promise<Product> {
const product = await super.update(productId, update);
// Whenever a product is updated, we want to invalidate the cache
await this.cacheService_.invalidate(`${ProductService.cacheKeyPrefix}:*`);
return product;
}
private generateCacheKey(selector: ProductSelector, config?: FindProductConfig): string {
const sortedSelector = this.sortObject(selector);
const sortedConfig = config ? this.sortObject(config) : undefined;
// No matter how the objects are sorted, we want to keep the same keys in the same order
return `${ProductService.cacheKeyPrefix}:${JSON.stringify(sortedSelector)}:${JSON.stringify(sortedConfig)}`;
}
private sortObject<T>(obj: T): T {
if (Array.isArray(obj)) {
return obj.map(item => this.sortObject(item)).sort() as any;
} else if (typeof obj === 'object' && obj !== null) {
return Object.keys(obj).sort().reduce((result, key) => {
result[key as keyof T] = this.sortObject(obj[key as keyof T]);
return result;
}, {} as T);
}
return obj;
}
}
export default ProductService
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment