const { ObjectStates } = require('./constants');
const { ErrorMessages } = require('./errors');
/**
* A wrapped object with an updatable state
* @example
* // Create a pooled object and changes it's state to AVAILABLE
* const thingy = new Thingy()
* const pooledObject = new PooledObject(thingy)
* pooledObject.setToAvailable()
*
* @template {object} T
*/
class PooledObject {
/**
* @param {T} object The object being pooled
* @param {function():number} [getTimestamp=Date.now] Function to get the current timestamp. See [Date.now()]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now}
*/
constructor(object, getTimestamp = Date.now) {
if (!object || typeof object !== 'object') throw new TypeError(ErrorMessages.NO_OBJECT);
/**
* Function to get the current timestamp
* @type {function():number}
*/
this._getTimestamp = getTimestamp;
/**
* The wrapped object
* @type {T}
*/
this._object = object;
/**
* The current object state
* @type {number}
*/
this._state = ObjectStates.CREATED;
/**
* Timestamp for when the object was created
* @type {number}
*/
this._createdAt = this._getTimestamp();
/**
* Timestamp for last time state was changed to `AVAILABLE`
* @type {number}
*/
this._availableAt = 0;
/**
* Timestamp for last time state was changed to `BORROWED`
* @type {number}
*/
this._borrowedAt = 0;
/**
* Promise attached to the pooled object when it is borrowed representing a loan
* @type {Promise<void>}
*/
//@ts-ignore
this._loanPromise = null;
/**
* Promise resolve function to be called when the pooled object is returned
* @type {function}
*/
//@ts-ignore
this._loanResolve = null;
}
/**
* Changes state to `AVAILABLE`
* @returns {this}
*/
setToAvailable() {
if (this._state === ObjectStates.AVAILABLE) return this;
if (this._state >= ObjectStates.BORROWED) {
if (this._state === ObjectStates.BORROWED) throw new TypeError(ErrorMessages.MUST_RETURN_BEFORE_AVAILABLE);
if (this._state === ObjectStates.INVALID) throw new TypeError(ErrorMessages.OBJECT_IS_INVALID);
throw new TypeError(ErrorMessages.OBJECT_IS_DESTROYED);
}
this._state = ObjectStates.AVAILABLE;
this._availableAt = this._getTimestamp();
return this;
}
/**
* Changes state to `BORROWED` and attaches a promise to the pooled object which gets resolved on setToReturned
* @returns {this}
*/
setToBorrowed() {
if (this._state !== ObjectStates.AVAILABLE) throw new TypeError(ErrorMessages.CANT_BORROW_NOT_AVAILABLE);
this._loanPromise = new Promise(resolve => {
this._loanResolve = resolve;
});
this._state = ObjectStates.BORROWED;
this._borrowedAt = this._getTimestamp();
return this;
}
/**
* Changes state to `RETURNED` and resolves the loan promise
* @returns {this}
*/
setToReturned() {
if (this._state !== ObjectStates.BORROWED) throw new TypeError(ErrorMessages.CANT_RETURN_NOT_BORROWED);
this._loanResolve();
this._state = ObjectStates.RETURNED;
return this;
}
/**
* Changes state to `VALIDATING`
* @returns {this}
*/
setToValidating() {
if (this._state >= ObjectStates.BORROWED) {
if (this._state === ObjectStates.BORROWED) throw new TypeError(ErrorMessages.CANT_VALIDATE_BORROWED);
if (this._state === ObjectStates.INVALID) throw new TypeError(ErrorMessages.OBJECT_IS_INVALID);
throw new TypeError(ErrorMessages.OBJECT_IS_DESTROYED);
}
this._state = ObjectStates.VALIDATING;
return this;
}
/**
* Changes state to `INVALID`
* @returns {this}
*/
setToInvalid() {
if (this._state === ObjectStates.DESTROYED) throw new TypeError(ErrorMessages.OBJECT_IS_DESTROYED);
this._state = ObjectStates.INVALID;
return this;
}
/**
* Changes state to `DESTROYED`
* @returns {this}
*/
setToDestroyed() {
this._state = ObjectStates.DESTROYED;
return this;
}
/**
* The object that is pooled
* @returns {T}
*/
getObject() {
return this._object;
}
/**
* Current state of the pooled object
* @returns {ObjectState}
*/
getState() {
return ObjectStates[this._state];
}
/**
* Promise that will be resolved when object gets returned. Returns `null` if not `BORROWED`
* @returns {Promise<void>|null}
*/
getLoanPromise() {
if (this._state !== ObjectStates.BORROWED) return null;
return this._loanPromise;
}
/**
* The number of milliseconds since set to `AVAILABLE`. Returns `-1` if not `AVAILABLE`
* @returns {number}
*/
getIdleTime() {
if (this._state !== ObjectStates.AVAILABLE) return -1;
return this._getTimestamp() - this._availableAt;
}
}
module.exports = PooledObject;