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.

347 lines 12.7 kB
/** * @module Lock */ import { resolveLazyable, } from "../../../../utilities/_module-exports.js"; import { resolveOneOrMoreStr } from "../../../../utilities/_module-exports.js"; import { KeyAlreadyAcquiredLockError, LOCK_EVENTS, UnableToAquireLockError, UnableToReleaseLockError, UnownedRefreshLockError, UnownedReleaseLockError, } from "../../../../lock/contracts/_module-exports.js"; import {} from "../../../../lock/contracts/_module-exports.js"; import { LazyPromise } from "../../../../async/_module-exports.js"; /** * IMPORTANT: This class is not intended to be instantiated directly, instead it should be created by the `LockProvider` class instance. * @group Derivables */ export class Lock { /** * @internal */ static serialize(deserializedValue) { return { key: deserializedValue.key.resolved, owner: deserializedValue.owner, ttlInMs: deserializedValue.ttl?.toMilliseconds() ?? null, expirationInMs: deserializedValue.lockState.get()?.getTime() ?? null, }; } createLazyPromise; adapter; lockState; eventDispatcher; key; owner; ttl; defaultBlockingInterval; defaultBlockingTime; defaultRefreshTime; /** * @internal */ constructor(settings) { const { createLazyPromise, adapter, lockState, eventDispatcher, key, owner, ttl, expirationInMs, defaultBlockingInterval, defaultBlockingTime, defaultRefreshTime, } = settings; this.createLazyPromise = createLazyPromise; this.adapter = adapter; this.lockState = lockState; this.lockState.set(expirationInMs); this.eventDispatcher = eventDispatcher; this.key = key; this.owner = resolveOneOrMoreStr(owner); this.ttl = ttl; this.defaultBlockingInterval = defaultBlockingInterval; this.defaultBlockingTime = defaultBlockingTime; this.defaultRefreshTime = defaultRefreshTime; } run(asyncFn) { return this.createLazyPromise(async () => { try { const hasAquired = await this.acquire(); if (!hasAquired) { return [ null, new KeyAlreadyAcquiredLockError(`Key "${this.key.resolved}" already acquired`), ]; } return [await resolveLazyable(asyncFn), null]; } finally { await this.release(); } }); } runOrFail(asyncFn) { return this.createLazyPromise(async () => { try { await this.acquireOrFail(); return await resolveLazyable(asyncFn); } finally { await this.release(); } }); } runBlocking(asyncFn, settings) { return this.createLazyPromise(async () => { try { const hasAquired = await this.acquireBlocking(settings); if (!hasAquired) { return [ null, new KeyAlreadyAcquiredLockError(`Key "${this.key.resolved}" already acquired`), ]; } return [await resolveLazyable(asyncFn), null]; } finally { await this.release(); } }); } runBlockingOrFail(asyncFn, settings) { return new LazyPromise(async () => { try { await this.acquireBlockingOrFail(settings); return await resolveLazyable(asyncFn); } finally { await this.release(); } }); } acquire() { return this.createLazyPromise(async () => { const prevState = this.lockState.get()?.getTime() ?? null; try { this.lockState.remove(); const hasAquired = await this.adapter.acquire(this.key.prefixed, this.owner, this.ttl); if (hasAquired) { this.lockState.set(this.ttl); const event = { key: this.key.resolved, owner: this.owner, ttl: this.ttl, }; this.eventDispatcher .dispatch(LOCK_EVENTS.ACQUIRED, event) .defer(); } else { const event = { key: this.key.resolved, owner: this.owner, }; this.eventDispatcher .dispatch(LOCK_EVENTS.NOT_AVAILABLE, event) .defer(); } return hasAquired; } catch (error) { this.lockState.set(prevState); const event = { key: this.key.resolved, owner: this.owner, ttl: this.ttl, error, }; this.eventDispatcher .dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event) .defer(); throw new UnableToAquireLockError(`A Lock with name of "${this.key.resolved}" could not be acquired.`, error); } }); } acquireOrFail() { return this.createLazyPromise(async () => { const hasAquired = await this.acquire(); if (!hasAquired) { throw new KeyAlreadyAcquiredLockError(`Key "${this.key.resolved}" already acquired`); } }); } acquireBlocking(settings = {}) { return new LazyPromise(async () => { const { time = this.defaultBlockingTime, interval = this.defaultBlockingInterval, } = settings; const endDate = time.toEndDate(); while (endDate.getTime() > new Date().getTime()) { const hasAquired = await this.acquire(); if (hasAquired) { return true; } await LazyPromise.delay(interval); } return false; }); } acquireBlockingOrFail(settings) { return new LazyPromise(async () => { const hasAquired = await this.acquireBlocking(settings); if (!hasAquired) { throw new KeyAlreadyAcquiredLockError(`Key "${this.key.resolved}" already acquired`); } }); } release() { return this.createLazyPromise(async () => { const prevState = this.lockState.get()?.getTime() ?? null; try { const hasReleased = await this.adapter.release(this.key.prefixed, this.owner); if (hasReleased) { this.lockState.remove(); const event = { key: this.key.resolved, owner: this.owner, }; this.eventDispatcher .dispatch(LOCK_EVENTS.RELEASED, event) .defer(); } else { const event = { key: this.key.resolved, owner: this.owner, }; this.eventDispatcher .dispatch(LOCK_EVENTS.UNOWNED_RELEASE_TRY, event) .defer(); } return hasReleased; } catch (error) { this.lockState.set(prevState); const event = { key: this.key.resolved, owner: this.owner, ttl: this.ttl, error, }; this.eventDispatcher .dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event) .defer(); throw new UnableToReleaseLockError(`A Lock with name of "${this.key.resolved}" could not be released.`, error); } }); } releaseOrFail() { return this.createLazyPromise(async () => { const hasRelased = await this.release(); if (!hasRelased) { throw new UnownedReleaseLockError(`Unonwed release on key "${this.key.resolved}" by owner "${this.owner}"`); } }); } forceRelease() { return this.createLazyPromise(async () => { const prevState = this.lockState.get()?.getTime() ?? null; try { await this.adapter.forceRelease(this.key.prefixed); this.lockState.remove(); const event = { key: this.key.resolved, }; this.eventDispatcher .dispatch(LOCK_EVENTS.FORCE_RELEASED, event) .defer(); } catch (error) { this.lockState.set(prevState); const event = { key: this.key.resolved, owner: this.owner, ttl: this.ttl, error, }; this.eventDispatcher .dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event) .defer(); throw new UnableToReleaseLockError(`A Lock with name of "${this.key.resolved}" could not be released.`, error); } }); } isExpired() { // eslint-disable-next-line @typescript-eslint/require-await return this.createLazyPromise(async () => { try { return this.lockState.isExpired(); } catch (error) { const event = { key: this.key.resolved, owner: this.owner, ttl: this.ttl, error, }; this.eventDispatcher .dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event) .defer(); throw error; } }); } isLocked() { return this.createLazyPromise(async () => { const isExpired = await this.isExpired(); return !isExpired; }); } refresh(ttl = this.defaultRefreshTime) { return this.createLazyPromise(async () => { const prevState = this.lockState.get()?.getTime() ?? null; try { const hasRefreshed = await this.adapter.refresh(this.key.prefixed, this.owner, ttl); if (hasRefreshed) { const event = { key: this.key.resolved, owner: this.owner, ttl, }; this.lockState.set(ttl); this.eventDispatcher .dispatch(LOCK_EVENTS.REFRESHED, event) .defer(); } else { const event = { key: this.key.resolved, owner: this.owner, }; this.eventDispatcher .dispatch(LOCK_EVENTS.UNOWNED_REFRESH_TRY, event) .defer(); } return hasRefreshed; } catch (error) { this.lockState.set(prevState); const event = { key: this.key.resolved, owner: this.owner, ttl: this.ttl, error, }; this.eventDispatcher .dispatch(LOCK_EVENTS.UNEXPECTED_ERROR, event) .defer(); throw error; } }); } refreshOrFail(ttl) { return this.createLazyPromise(async () => { const hasRefreshed = await this.refresh(ttl); if (!hasRefreshed) { throw new UnownedRefreshLockError(`Unonwed refresh on key "${this.key.resolved}" by owner "${this.owner}"`); } }); } getRemainingTime() { // eslint-disable-next-line @typescript-eslint/require-await return this.createLazyPromise(async () => { return this.lockState.getRemainingTime(); }); } getOwner() { // eslint-disable-next-line @typescript-eslint/require-await return this.createLazyPromise(async () => { return this.owner; }); } } //# sourceMappingURL=lock.js.map