Untitled
unknown
plain_text
a year ago
4.1 kB
16
Indexable
import { Model, Document, FilterQuery, UpdateQuery, PopulateOptions } from 'mongoose';
import { createClient } from 'redis';
// Initialize Redis client
const redisClient = createClient();
redisClient.on('error', (err) => console.error('Redis Client Error', err));
(async () => {
await redisClient.connect();
})();
interface PaginationOptions {
page?: number;
limit?: number;
}
interface ReadAllOptions<T> {
query?: FilterQuery<T>;
projection?: Record<string, unknown>;
sort?: Record<string, 1 | -1>;
filter?: FilterQuery<T>;
pagination?: PaginationOptions;
includes?: string[] | PopulateOptions[];
}
interface PaginatedResult<T> {
docs: T[];
totalDocs: number;
totalPages: number;
currentPage: number;
hasNextPage: boolean;
hasPrevPage: boolean;
nextPage: number | null;
prevPage: number | null;
}
interface DbOperation<T extends Document> {
create: (model: Model<T>, data: Partial<T>) => Promise<T>;
read: (model: Model<T>, options: { query?: FilterQuery<T>; includes?: string[] | PopulateOptions[] }) => Promise<T | null>;
readAll: (model: Model<T>, options: ReadAllOptions<T>) => Promise<PaginatedResult<T>>;
update: (model: Model<T>, options: { query: FilterQuery<T>; update: UpdateQuery<T> }) => Promise<T | null>;
delete: (model: Model<T>, options: { query: FilterQuery<T> }) => Promise<T | null>;
invalidateCache: (keyPattern: string) => Promise<void>;
}
export const dbOperation: DbOperation<any> = {
async create(model, data) {
const newDocument = await model.create(data);
await this.invalidateCache(`*${model.modelName}*`);
return newDocument;
},
async read(model, { query = {}, includes = [] }) {
const result = await model.findOne(query).populate(includes).exec();
return result;
},
async readAll(model, { query = {}, projection = {}, sort = {}, filter = {}, pagination = {}, includes = [] }) {
const { page = 1, limit = 10 } = pagination;
// Combine query and filter for better optimization
const combinedQuery = { ...query, ...filter };
const cacheKey = `${model.modelName}:${JSON.stringify(combinedQuery)}:page:${page}:limit:${limit}:sort:${JSON.stringify(sort)}`;
// Check cache
const cachedData = await redisClient.get(cacheKey);
if (cachedData) {
console.log('Returning data from Redis cache');
return JSON.parse(cachedData);
}
// Total document count for pagination
const totalDocs = await model.countDocuments(combinedQuery).exec();
const totalPages = Math.ceil(totalDocs / limit);
const hasNextPage = page < totalPages;
const hasPrevPage = page > 1;
const nextPage = hasNextPage ? page + 1 : null;
const prevPage = hasPrevPage ? page - 1 : null;
// Query MongoDB
const docs = await model.find(combinedQuery)
.select(projection)
.sort(sort)
.skip((page - 1) * limit)
.limit(limit)
.populate(includes)
.exec();
const result: PaginatedResult<any> = {
docs,
totalDocs,
totalPages,
currentPage: page,
hasNextPage,
hasPrevPage,
nextPage,
prevPage,
};
// Cache the results
await redisClient.set(cacheKey, JSON.stringify(result), {
EX: 3600, // Cache expiration time (1 hour)
});
console.log('Returning data from MongoDB and caching in Redis');
return result;
},
async update(model, { query, update }) {
const updatedDocument = await model.findOneAndUpdate(query, update, { new: true }).exec();
await this.invalidateCache(`*${model.modelName}*`);
return updatedDocument;
},
async delete(model, { query }) {
const deletedDocument = await model.findOneAndDelete(query).exec();
await this.invalidateCache(`*${model.modelName}*`);
return deletedDocument;
},
async invalidateCache(keyPattern: string) {
const keys = await redisClient.keys(keyPattern);
if (keys.length > 0) {
await redisClient.del(keys);
console.log(`Invalidated cache for keys: ${keys}`);
}
},
};
(async () => {
process.on('exit', async () => {
await redisClient.disconnect();
});
})();
Editor is loading...
Leave a Comment