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.

238 lines 7.24 kB
/** * @module Lock */ import {} from "mongodb"; import {} from "../../../../execution-context/contracts/_module.js"; import {} from "../../../../lock/contracts/_module.js"; import {} from "../../../../time-span/implementations/_module.js"; import {} from "../../../../utilities/_module.js"; /** * To utilize the `MongodbLockAdapter`, you must install the [`"mongodb"`](https://www.npmjs.com/package/mongodb) package. * * Note in order to use `MongodbLockAdapter` correctly, ensure you use a single, consistent database across all server instances. * * IMPORT_PATH: `"@daiso-tech/core/lock/mongodb-lock-adapter"` * @group Adapters */ export class MongodbLockAdapter { collection; /** * @example * ```ts * import { MongodbLockAdapter } from "@daiso-tech/core/lock/mongodb-lock-adapter"; * import { MongoClient } from "mongodb"; * * const client = await MongoClient.connect("YOUR_MONGODB_CONNECTION_STRING"); * const database = client.db("database"); * const lockAdapter = new MongodbLockAdapter({ * database * }); * // You need initialize the adapter once before using it. * await lockAdapter.init() * ``` */ constructor(settings) { const { collectionName = "lock", collectionSettings, database, } = settings; this.collection = database.collection(collectionName, collectionSettings); } /** * Creates all related indexes. * Note the `init` method needs to be called once before using the adapter. */ async init() { // Should throw if the index already exists thats why the try catch is used. try { await this.collection.createIndex({ key: 1, }, { unique: true, }); } catch { /* EMPTY */ } // Should throw if the index already exists thats why the try catch is used. try { await this.collection.createIndex("expiration", { expireAfterSeconds: 0, }); } catch { /* EMPTY */ } } /** * Removes the collection where the lock keys are stored and all it's related indexes. * Note all lock data will be removed. */ async deInit() { // Should throw if the collection already does not exists thats why the try catch is used. try { await this.collection.dropIndexes(); } catch { /* EMPTY */ } // Should throw if the collection already does not exists thats why the try catch is used. try { await this.collection.drop(); } catch { /* EMPTY */ } } async acquire(_context, key, lockId, ttl) { const expiration = ttl?.toEndDate() ?? null; const isExpiredQuery = { $and: [ { $ne: ["$expiration", null], }, { $lte: ["$expiration", new Date()], }, ], }; const lockData = await this.collection.findOneAndUpdate({ key, }, [ { $set: { key, owner: { $ifNull: ["$owner", lockId], }, expiration: { $ifNull: ["$expiration", expiration], }, }, }, { $set: { owner: { $cond: { if: isExpiredQuery, then: lockId, else: "$owner", }, }, expiration: { $cond: { if: isExpiredQuery, then: expiration, else: "$expiration", }, }, }, }, ], { upsert: true, }); if (lockData === null) { return true; } if (lockData.owner === lockId) { return true; } if (lockData.expiration === null) { return false; } return lockData.expiration <= new Date(); } async release(_context, key, lockId) { const isUnexpirableQuery = { expiration: { $eq: null, }, }; const isUnexpiredQuery = { expiration: { $gt: new Date(), }, }; const lockData = await this.collection.findOneAndDelete({ key, owner: lockId, $or: [isUnexpirableQuery, isUnexpiredQuery], }); if (lockData === null) { return false; } const { expiration } = lockData; const hasNoExpiration = expiration === null; if (hasNoExpiration) { return true; } const { owner: currentOwner } = lockData; const isNotExpired = expiration > new Date(); const isCurrentOwner = lockId === currentOwner; return isNotExpired && isCurrentOwner; } async forceRelease(_context, key) { const lockData = await this.collection.findOneAndDelete({ key }); if (lockData === null) { return false; } if (lockData.expiration === null) { return true; } const isNotExpired = lockData.expiration >= new Date(); return isNotExpired; } async refresh(_context, key, lockId, ttl) { const isUnexpiredQuery = { $and: [ { $ne: ["$expiration", null], }, { $gt: ["$expiration", new Date()], }, ], }; const lockData = await this.collection.findOneAndUpdate({ key, }, [ { $set: { expiration: { $cond: { if: isUnexpiredQuery, then: ttl.toEndDate(), else: "$expiration", }, }, }, }, ]); if (lockData === null) { return false; } if (lockData.owner !== lockId) { return false; } if (lockData.expiration === null) { return false; } if (lockData.expiration <= new Date()) { return false; } return true; } async getState(_context, key) { const lockData = await this.collection.findOne({ key, }); if (lockData === null) { return null; } if (lockData.expiration !== null && lockData.expiration <= new Date()) { return null; } return { owner: lockData.owner, expiration: lockData.expiration, }; } } //# sourceMappingURL=mongodb-lock-adapter.js.map