UNPKG

@daiso-tech/core

Version:

The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.

272 lines 8.81 kB
/** * @module Cache */ import { TypeCacheError, UnexpectedCacheError, } from "../../../../cache/contracts/_module-exports.js"; import { MongodbCacheAdapterSerde } from "../../../../cache/implementations/adapters/mongodb-cache-adapter/mongodb-cache-adapter-serde.js"; import { MongoServerError } from "mongodb"; import {} from "mongodb"; import escapeStringRegexp from "escape-string-regexp"; /** * To utilize the `MongodbCacheAdapter`, you must install the `"mongodb"` package and supply a {@link ISerde | `ISerde<string>`}, with an adapter like {@link SuperJsonSerdeAdapter | `SuperJsonSerdeAdapter `}. * * IMPORT_PATH: `"@daiso-tech/core/cache/adapters"` * @group Adapters */ export class MongodbCacheAdapter { static filterUnexpiredKeys(keys) { const hasNoExpiration = { expiration: { $eq: null, }, }; const hasExpiration = { expiration: { $ne: null, }, }; const hasNotExpired = { expiration: { $gt: new Date(), }, }; const keysMatch = { key: { $in: keys, }, }; return { $and: [ keysMatch, { $or: [ hasNoExpiration, { $and: [hasExpiration, hasNotExpired], }, ], }, ], }; } static isMongodbIncrementError(value) { return (value instanceof MongoServerError && value.code !== undefined && (typeof value.code === "string" || typeof value.code === "number") && String(value.code) === "14"); } serde; collection; /** * @example * ```ts * import { MongodbCacheAdapter } from "@daiso-tech/core/cache/adapters"; * import { Serde } from "@daiso-tech/core/serde"; * import { SuperJsonSerdeAdapter } from "@daiso-tech/core/serde/adapters" * import { MongoClient } from "mongodb"; * * const client = await MongoClient.connect("YOUR_MONGODB_CONNECTION_STRING"); * const database = client.db("database"); * const serde = new Serde(new SuperJsonSerdeAdapter()); * const cacheAdapter = new MongodbCacheAdapter({ * database, * serde, * }); * // You need initialize the adapter once before using it. * await cacheAdapter.init(); * ``` */ constructor(settings) { const { collectionName = "cache", collectionSettings, database, serde, } = settings; this.collection = database.collection(collectionName, collectionSettings); this.serde = new MongodbCacheAdapterSerde(serde); } /** * Creates all related indexes. * Note the `init` method needs to be called before using the adapter. */ async init() { try { await this.collection.createIndex({ key: 1, }, { unique: true, }); await this.collection.createIndex("expiration", { expireAfterSeconds: 0, }); } catch { /* Empty */ } } /** * Removes the collection where the cache values are stored and all it's related indexes. * Note all cache data will be removed. */ async deInit() { await this.collection.dropIndexes(); await this.collection.drop(); } getDocValue(document) { if (document === null) { return null; } const { expiration, value } = document; if (expiration === null) { return this.serde.deserialize(value); } const hasExpired = expiration.getTime() <= new Date().getTime(); if (hasExpired) { return null; } return this.serde.deserialize(value); } async get(key) { const document = await this.collection.findOne({ key, }, { projection: { _id: 0, expiration: 1, value: 1, }, }); return this.getDocValue(document); } async getAndRemove(key) { const document = await this.collection.findOneAndDelete({ key, }, { projection: { _id: 0, expiration: 1, value: 1, }, }); return this.getDocValue(document); } isDocExpired(document) { if (document === null) { return true; } const { expiration } = document; if (expiration === null) { return false; } const hasExpired = expiration.getTime() <= new Date().getTime(); return hasExpired; } async add(key, value, ttl) { const hasExpirationQuery = { $ne: ["$expiration", null], }; const hasExpiredQuery = { $lte: ["$expiration", new Date()], }; const hasExpirationAndExpiredQuery = { $and: [hasExpirationQuery, hasExpiredQuery], }; const serializedValue = this.serde.serialize(value); const document = await this.collection.findOneAndUpdate({ key, }, [ { $set: { value: { $cond: { if: hasExpirationAndExpiredQuery, then: serializedValue, else: "$value", }, }, expiration: { $cond: { if: hasExpirationAndExpiredQuery, then: ttl?.toEndDate() ?? null, else: "$expiration", }, }, }, }, ], { upsert: true, projection: { _id: 0, expiration: 1, }, }); return this.isDocExpired(document); } async put(key, value, ttl) { const document = await this.collection.findOneAndUpdate({ key, }, { $set: { value: this.serde.serialize(value), expiration: ttl?.toEndDate() ?? null, }, }, { upsert: true, projection: { _id: 0, expiration: 1, }, }); return !this.isDocExpired(document); } async update(key, value) { const updateResult = await this.collection.updateOne(MongodbCacheAdapter.filterUnexpiredKeys([key]), { $set: { value: this.serde.serialize(value), }, }); if (!updateResult.acknowledged) { throw new UnexpectedCacheError("Mongodb update was not acknowledged"); } return updateResult.modifiedCount > 0; } async increment(key, value) { try { const updateResult = await this.collection.updateOne(MongodbCacheAdapter.filterUnexpiredKeys([key]), { $inc: { value, }, }); if (!updateResult.acknowledged) { throw new UnexpectedCacheError("Mongodb update was not acknowledged"); } return updateResult.modifiedCount > 0; } catch (error) { if (MongodbCacheAdapter.isMongodbIncrementError(error)) { throw new TypeCacheError(`Unable to increment or decrement none number type key "${key}"`); } throw error; } } async removeMany(keys) { const deleteResult = await this.collection.deleteMany(MongodbCacheAdapter.filterUnexpiredKeys(keys)); if (!deleteResult.acknowledged) { throw new UnexpectedCacheError("Mongodb deletion was not acknowledged"); } return deleteResult.deletedCount > 0; } async removeAll() { const mongodbResult = await this.collection.deleteMany(); if (!mongodbResult.acknowledged) { throw new UnexpectedCacheError("Mongodb deletion was not acknowledged"); } } async removeByKeyPrefix(prefix) { const mongodbResult = await this.collection.deleteMany({ key: { $regex: new RegExp(`^${escapeStringRegexp(prefix)}`), }, }); if (!mongodbResult.acknowledged) { throw new UnexpectedCacheError("Mongodb deletion was not acknowledged"); } } } //# sourceMappingURL=mongodb-cache-adapter.js.map