🎯 MapView v2.0 - Global Deployment Ready

 MAJOR FEATURES:
• Auto-zoom intelligence với smart bounds fitting
• Enhanced 3D GPS markers với pulsing effects
• Professional route display với 6-layer rendering
• Status-based parking icons với availability indicators
• Production-ready build optimizations

🗺️ AUTO-ZOOM FEATURES:
• Smart bounds fitting cho GPS + selected parking
• Adaptive padding (50px) cho visual balance
• Max zoom control (level 16) để tránh quá gần
• Dynamic centering khi không có selection

🎨 ENHANCED VISUALS:
• 3D GPS marker với multi-layer pulse effects
• Advanced parking icons với status colors
• Selection highlighting với animation
• Dimming system cho non-selected items

🛣️ ROUTE SYSTEM:
• OpenRouteService API integration
• Multi-layer route rendering (glow, shadow, main, animated)
• Real-time distance & duration calculation
• Visual route info trong popup

📱 PRODUCTION READY:
• SSR safe với dynamic imports
• Build errors resolved
• Global deployment via Vercel
• Optimized performance

🌍 DEPLOYMENT:
• Vercel: https://whatever-ctk2auuxr-phong12hexdockworks-projects.vercel.app
• Bundle size: 22.8 kB optimized
• Global CDN distribution
• HTTPS enabled

💾 VERSION CONTROL:
• MapView-v2.0.tsx backup created
• MAPVIEW_VERSIONS.md documentation
• Full version history tracking
This commit is contained in:
2025-07-20 19:52:16 +07:00
parent 3203463a6a
commit c65cc97a33
64624 changed files with 7199453 additions and 6462 deletions

View File

@@ -0,0 +1,23 @@
"use strict";
class DefaultEvictor {
evict(config, pooledResource, availableObjectsCount) {
const idleTime = Date.now() - pooledResource.lastIdleTime;
if (
config.softIdleTimeoutMillis > 0 &&
config.softIdleTimeoutMillis < idleTime &&
config.min < availableObjectsCount
) {
return true;
}
if (config.idleTimeoutMillis < idleTime) {
return true;
}
return false;
}
}
module.exports = DefaultEvictor;

49
backend/node_modules/generic-pool/lib/Deferred.js generated vendored Normal file
View File

@@ -0,0 +1,49 @@
"use strict";
/**
* This is apparently a bit like a Jquery deferred, hence the name
*/
class Deferred {
constructor(Promise) {
this._state = Deferred.PENDING;
this._resolve = undefined;
this._reject = undefined;
this._promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
get state() {
return this._state;
}
get promise() {
return this._promise;
}
reject(reason) {
if (this._state !== Deferred.PENDING) {
return;
}
this._state = Deferred.REJECTED;
this._reject(reason);
}
resolve(value) {
if (this._state !== Deferred.PENDING) {
return;
}
this._state = Deferred.FULFILLED;
this._resolve(value);
}
}
// TODO: should these really live here? or be a seperate 'state' enum
Deferred.PENDING = "PENDING";
Deferred.FULFILLED = "FULFILLED";
Deferred.REJECTED = "REJECTED";
module.exports = Deferred;

106
backend/node_modules/generic-pool/lib/Deque.js generated vendored Normal file
View File

@@ -0,0 +1,106 @@
"use strict";
const DoublyLinkedList = require("./DoublyLinkedList");
const DequeIterator = require("./DequeIterator");
/**
* DoublyLinkedList backed double ended queue
* implements just enough to keep the Pool
*/
class Deque {
constructor() {
this._list = new DoublyLinkedList();
}
/**
* removes and returns the first element from the queue
* @return {any} [description]
*/
shift() {
if (this.length === 0) {
return undefined;
}
const node = this._list.head;
this._list.remove(node);
return node.data;
}
/**
* adds one elemts to the beginning of the queue
* @param {any} element [description]
* @return {any} [description]
*/
unshift(element) {
const node = DoublyLinkedList.createNode(element);
this._list.insertBeginning(node);
}
/**
* adds one to the end of the queue
* @param {any} element [description]
* @return {any} [description]
*/
push(element) {
const node = DoublyLinkedList.createNode(element);
this._list.insertEnd(node);
}
/**
* removes and returns the last element from the queue
*/
pop() {
if (this.length === 0) {
return undefined;
}
const node = this._list.tail;
this._list.remove(node);
return node.data;
}
[Symbol.iterator]() {
return new DequeIterator(this._list);
}
iterator() {
return new DequeIterator(this._list);
}
reverseIterator() {
return new DequeIterator(this._list, true);
}
/**
* get a reference to the item at the head of the queue
* @return {any} [description]
*/
get head() {
if (this.length === 0) {
return undefined;
}
const node = this._list.head;
return node.data;
}
/**
* get a reference to the item at the tail of the queue
* @return {any} [description]
*/
get tail() {
if (this.length === 0) {
return undefined;
}
const node = this._list.tail;
return node.data;
}
get length() {
return this._list.length;
}
}
module.exports = Deque;

20
backend/node_modules/generic-pool/lib/DequeIterator.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
"use strict";
const DoublyLinkedListIterator = require("./DoublyLinkedListIterator");
/**
* Thin wrapper around an underlying DDL iterator
*/
class DequeIterator extends DoublyLinkedListIterator {
next() {
const result = super.next();
// unwrap the node...
if (result.value) {
result.value = result.value.data;
}
return result;
}
}
module.exports = DequeIterator;

View File

@@ -0,0 +1,94 @@
"use strict";
/**
* A Doubly Linked List, because there aren't enough in the world...
* this is pretty much a direct JS port of the one wikipedia
* https://en.wikipedia.org/wiki/Doubly_linked_list
*
* For most usage 'insertBeginning' and 'insertEnd' should be enough
*
* nodes are expected to something like a POJSO like
* {
* prev: null,
* next: null,
* something: 'whatever you like'
* }
*/
class DoublyLinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
insertBeginning(node) {
if (this.head === null) {
this.head = node;
this.tail = node;
node.prev = null;
node.next = null;
this.length++;
} else {
this.insertBefore(this.head, node);
}
}
insertEnd(node) {
if (this.tail === null) {
this.insertBeginning(node);
} else {
this.insertAfter(this.tail, node);
}
}
insertAfter(node, newNode) {
newNode.prev = node;
newNode.next = node.next;
if (node.next === null) {
this.tail = newNode;
} else {
node.next.prev = newNode;
}
node.next = newNode;
this.length++;
}
insertBefore(node, newNode) {
newNode.prev = node.prev;
newNode.next = node;
if (node.prev === null) {
this.head = newNode;
} else {
node.prev.next = newNode;
}
node.prev = newNode;
this.length++;
}
remove(node) {
if (node.prev === null) {
this.head = node.next;
} else {
node.prev.next = node.next;
}
if (node.next === null) {
this.tail = node.prev;
} else {
node.next.prev = node.prev;
}
node.prev = null;
node.next = null;
this.length--;
}
// FIXME: this should not live here and has become a dumping ground...
static createNode(data) {
return {
prev: null,
next: null,
data: data
};
}
}
module.exports = DoublyLinkedList;

View File

@@ -0,0 +1,100 @@
"use strict";
/**
* Creates an interator for a DoublyLinkedList starting at the given node
* It's internal cursor will remains relative to the last "iterated" node as that
* node moves through the list until it either iterates to the end of the list,
* or the the node it's tracking is removed from the list. Until the first 'next'
* call it tracks the head/tail of the linked list. This means that one can create
* an iterator on an empty list, then add nodes, and then the iterator will follow
* those nodes. Because the DoublyLinkedList nodes don't track their owning "list" and
* it's highly inefficient to walk the list for every iteration, the iterator won't know
* if the node has been detached from one List and added to another list, or if the iterator
*
* The created object is an es6 compatible iterator
*/
class DoublyLinkedListIterator {
/**
* @param {Object} doublyLinkedList a node that is part of a doublyLinkedList
* @param {Boolean} [reverse=false] is this a reverse iterator? default: false
*/
constructor(doublyLinkedList, reverse) {
this._list = doublyLinkedList;
// NOTE: these key names are tied to the DoublyLinkedListIterator
this._direction = reverse === true ? "prev" : "next";
this._startPosition = reverse === true ? "tail" : "head";
this._started = false;
this._cursor = null;
this._done = false;
}
_start() {
this._cursor = this._list[this._startPosition];
this._started = true;
}
_advanceCursor() {
if (this._started === false) {
this._started = true;
this._cursor = this._list[this._startPosition];
return;
}
this._cursor = this._cursor[this._direction];
}
reset() {
this._done = false;
this._started = false;
this._cursor = null;
}
remove() {
if (
this._started === false ||
this._done === true ||
this._isCursorDetached()
) {
return false;
}
this._list.remove(this._cursor);
}
next() {
if (this._done === true) {
return { done: true };
}
this._advanceCursor();
// if there is no node at the cursor or the node at the cursor is no longer part of
// a doubly linked list then we are done/finished/kaput
if (this._cursor === null || this._isCursorDetached()) {
this._done = true;
return { done: true };
}
return {
value: this._cursor,
done: false
};
}
/**
* Is the node detached from a list?
* NOTE: you can trick/bypass/confuse this check by removing a node from one DoublyLinkedList
* and adding it to another.
* TODO: We can make this smarter by checking the direction of travel and only checking
* the required next/prev/head/tail rather than all of them
* @return {Boolean} [description]
*/
_isCursorDetached() {
return (
this._cursor.prev === null &&
this._cursor.next === null &&
this._list.tail !== this._cursor &&
this._list.head !== this._cursor
);
}
}
module.exports = DoublyLinkedListIterator;

744
backend/node_modules/generic-pool/lib/Pool.js generated vendored Normal file
View File

@@ -0,0 +1,744 @@
"use strict";
const EventEmitter = require("events").EventEmitter;
const factoryValidator = require("./factoryValidator");
const PoolOptions = require("./PoolOptions");
const ResourceRequest = require("./ResourceRequest");
const ResourceLoan = require("./ResourceLoan");
const PooledResource = require("./PooledResource");
const DefaultEvictor = require("./DefaultEvictor");
const Deque = require("./Deque");
const Deferred = require("./Deferred");
const PriorityQueue = require("./PriorityQueue");
const DequeIterator = require("./DequeIterator");
const reflector = require("./utils").reflector;
/**
* TODO: move me
*/
const FACTORY_CREATE_ERROR = "factoryCreateError";
const FACTORY_DESTROY_ERROR = "factoryDestroyError";
class Pool extends EventEmitter {
/**
* Generate an Object pool with a specified `factory` and `config`.
*
* @param {typeof DefaultEvictor} Evictor
* @param {typeof Deque} Deque
* @param {typeof PriorityQueue} PriorityQueue
* @param {Object} factory
* Factory to be used for generating and destroying the items.
* @param {Function} factory.create
* Should create the item to be acquired,
* and call it's first callback argument with the generated item as it's argument.
* @param {Function} factory.destroy
* Should gently close any resources that the item is using.
* Called before the items is destroyed.
* @param {Function} factory.validate
* Test if a resource is still valid .Should return a promise that resolves to a boolean, true if resource is still valid and false
* If it should be removed from pool.
* @param {Object} options
*/
constructor(Evictor, Deque, PriorityQueue, factory, options) {
super();
factoryValidator(factory);
this._config = new PoolOptions(options);
// TODO: fix up this ugly glue-ing
this._Promise = this._config.Promise;
this._factory = factory;
this._draining = false;
this._started = false;
/**
* Holds waiting clients
* @type {PriorityQueue}
*/
this._waitingClientsQueue = new PriorityQueue(this._config.priorityRange);
/**
* Collection of promises for resource creation calls made by the pool to factory.create
* @type {Set}
*/
this._factoryCreateOperations = new Set();
/**
* Collection of promises for resource destruction calls made by the pool to factory.destroy
* @type {Set}
*/
this._factoryDestroyOperations = new Set();
/**
* A queue/stack of pooledResources awaiting acquisition
* TODO: replace with LinkedList backed array
* @type {Deque}
*/
this._availableObjects = new Deque();
/**
* Collection of references for any resource that are undergoing validation before being acquired
* @type {Set}
*/
this._testOnBorrowResources = new Set();
/**
* Collection of references for any resource that are undergoing validation before being returned
* @type {Set}
*/
this._testOnReturnResources = new Set();
/**
* Collection of promises for any validations currently in process
* @type {Set}
*/
this._validationOperations = new Set();
/**
* All objects associated with this pool in any state (except destroyed)
* @type {Set}
*/
this._allObjects = new Set();
/**
* Loans keyed by the borrowed resource
* @type {Map}
*/
this._resourceLoans = new Map();
/**
* Infinitely looping iterator over available object
* @type {DequeIterator}
*/
this._evictionIterator = this._availableObjects.iterator();
this._evictor = new Evictor();
/**
* handle for setTimeout for next eviction run
* @type {(number|null)}
*/
this._scheduledEviction = null;
// create initial resources (if factory.min > 0)
if (this._config.autostart === true) {
this.start();
}
}
_destroy(pooledResource) {
// FIXME: do we need another state for "in destruction"?
pooledResource.invalidate();
this._allObjects.delete(pooledResource);
// NOTE: this maybe very bad promise usage?
const destroyPromise = this._factory.destroy(pooledResource.obj);
const wrappedDestroyPromise = this._config.destroyTimeoutMillis
? this._Promise.resolve(this._applyDestroyTimeout(destroyPromise))
: this._Promise.resolve(destroyPromise);
this._trackOperation(
wrappedDestroyPromise,
this._factoryDestroyOperations
).catch(reason => {
this.emit(FACTORY_DESTROY_ERROR, reason);
});
// TODO: maybe ensuring minimum pool size should live outside here
this._ensureMinimum();
}
_applyDestroyTimeout(promise) {
const timeoutPromise = new this._Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("destroy timed out"));
}, this._config.destroyTimeoutMillis).unref();
});
return this._Promise.race([timeoutPromise, promise]);
}
/**
* Attempt to move an available resource into test and then onto a waiting client
* @return {Boolean} could we move an available resource into test
*/
_testOnBorrow() {
if (this._availableObjects.length < 1) {
return false;
}
const pooledResource = this._availableObjects.shift();
// Mark the resource as in test
pooledResource.test();
this._testOnBorrowResources.add(pooledResource);
const validationPromise = this._factory.validate(pooledResource.obj);
const wrappedValidationPromise = this._Promise.resolve(validationPromise);
this._trackOperation(
wrappedValidationPromise,
this._validationOperations
).then(isValid => {
this._testOnBorrowResources.delete(pooledResource);
if (isValid === false) {
pooledResource.invalidate();
this._destroy(pooledResource);
this._dispense();
return;
}
this._dispatchPooledResourceToNextWaitingClient(pooledResource);
});
return true;
}
/**
* Attempt to move an available resource to a waiting client
* @return {Boolean} [description]
*/
_dispatchResource() {
if (this._availableObjects.length < 1) {
return false;
}
const pooledResource = this._availableObjects.shift();
this._dispatchPooledResourceToNextWaitingClient(pooledResource);
return false;
}
/**
* Attempt to resolve an outstanding resource request using an available resource from
* the pool, or creating new ones
*
* @private
*/
_dispense() {
/**
* Local variables for ease of reading/writing
* these don't (shouldn't) change across the execution of this fn
*/
const numWaitingClients = this._waitingClientsQueue.length;
// If there aren't any waiting requests then there is nothing to do
// so lets short-circuit
if (numWaitingClients < 1) {
return;
}
const resourceShortfall =
numWaitingClients - this._potentiallyAllocableResourceCount;
const actualNumberOfResourcesToCreate = Math.min(
this.spareResourceCapacity,
resourceShortfall
);
for (let i = 0; actualNumberOfResourcesToCreate > i; i++) {
this._createResource();
}
// If we are doing test-on-borrow see how many more resources need to be moved into test
// to help satisfy waitingClients
if (this._config.testOnBorrow === true) {
// how many available resources do we need to shift into test
const desiredNumberOfResourcesToMoveIntoTest =
numWaitingClients - this._testOnBorrowResources.size;
const actualNumberOfResourcesToMoveIntoTest = Math.min(
this._availableObjects.length,
desiredNumberOfResourcesToMoveIntoTest
);
for (let i = 0; actualNumberOfResourcesToMoveIntoTest > i; i++) {
this._testOnBorrow();
}
}
// if we aren't testing-on-borrow then lets try to allocate what we can
if (this._config.testOnBorrow === false) {
const actualNumberOfResourcesToDispatch = Math.min(
this._availableObjects.length,
numWaitingClients
);
for (let i = 0; actualNumberOfResourcesToDispatch > i; i++) {
this._dispatchResource();
}
}
}
/**
* Dispatches a pooledResource to the next waiting client (if any) else
* puts the PooledResource back on the available list
* @param {PooledResource} pooledResource [description]
* @return {Boolean} [description]
*/
_dispatchPooledResourceToNextWaitingClient(pooledResource) {
const clientResourceRequest = this._waitingClientsQueue.dequeue();
if (
clientResourceRequest === undefined ||
clientResourceRequest.state !== Deferred.PENDING
) {
// While we were away either all the waiting clients timed out
// or were somehow fulfilled. put our pooledResource back.
this._addPooledResourceToAvailableObjects(pooledResource);
// TODO: do need to trigger anything before we leave?
return false;
}
const loan = new ResourceLoan(pooledResource, this._Promise);
this._resourceLoans.set(pooledResource.obj, loan);
pooledResource.allocate();
clientResourceRequest.resolve(pooledResource.obj);
return true;
}
/**
* tracks on operation using given set
* handles adding/removing from the set and resolve/rejects the value/reason
* @param {Promise} operation
* @param {Set} set Set holding operations
* @return {Promise} Promise that resolves once operation has been removed from set
*/
_trackOperation(operation, set) {
set.add(operation);
return operation.then(
v => {
set.delete(operation);
return this._Promise.resolve(v);
},
e => {
set.delete(operation);
return this._Promise.reject(e);
}
);
}
/**
* @private
*/
_createResource() {
// An attempt to create a resource
const factoryPromise = this._factory.create();
const wrappedFactoryPromise = this._Promise
.resolve(factoryPromise)
.then(resource => {
const pooledResource = new PooledResource(resource);
this._allObjects.add(pooledResource);
this._addPooledResourceToAvailableObjects(pooledResource);
});
this._trackOperation(wrappedFactoryPromise, this._factoryCreateOperations)
.then(() => {
this._dispense();
// Stop bluebird complaining about this side-effect only handler
// - a promise was created in a handler but was not returned from it
// https://goo.gl/rRqMUw
return null;
})
.catch(reason => {
this.emit(FACTORY_CREATE_ERROR, reason);
this._dispense();
});
}
/**
* @private
*/
_ensureMinimum() {
if (this._draining === true) {
return;
}
const minShortfall = this._config.min - this._count;
for (let i = 0; i < minShortfall; i++) {
this._createResource();
}
}
_evict() {
const testsToRun = Math.min(
this._config.numTestsPerEvictionRun,
this._availableObjects.length
);
const evictionConfig = {
softIdleTimeoutMillis: this._config.softIdleTimeoutMillis,
idleTimeoutMillis: this._config.idleTimeoutMillis,
min: this._config.min
};
for (let testsHaveRun = 0; testsHaveRun < testsToRun; ) {
const iterationResult = this._evictionIterator.next();
// Safety check incase we could get stuck in infinite loop because we
// somehow emptied the array after checking its length.
if (iterationResult.done === true && this._availableObjects.length < 1) {
this._evictionIterator.reset();
return;
}
// If this happens it should just mean we reached the end of the
// list and can reset the cursor.
if (iterationResult.done === true && this._availableObjects.length > 0) {
this._evictionIterator.reset();
continue;
}
const resource = iterationResult.value;
const shouldEvict = this._evictor.evict(
evictionConfig,
resource,
this._availableObjects.length
);
testsHaveRun++;
if (shouldEvict === true) {
// take it out of the _availableObjects list
this._evictionIterator.remove();
this._destroy(resource);
}
}
}
_scheduleEvictorRun() {
// Start eviction if set
if (this._config.evictionRunIntervalMillis > 0) {
// @ts-ignore
this._scheduledEviction = setTimeout(() => {
this._evict();
this._scheduleEvictorRun();
}, this._config.evictionRunIntervalMillis).unref();
}
}
_descheduleEvictorRun() {
if (this._scheduledEviction) {
clearTimeout(this._scheduledEviction);
}
this._scheduledEviction = null;
}
start() {
if (this._draining === true) {
return;
}
if (this._started === true) {
return;
}
this._started = true;
this._scheduleEvictorRun();
this._ensureMinimum();
}
/**
* Request a new resource. The callback will be called,
* when a new resource is available, passing the resource to the callback.
* TODO: should we add a seperate "acquireWithPriority" function
*
* @param {Number} [priority=0]
* Optional. Integer between 0 and (priorityRange - 1). Specifies the priority
* of the caller if there are no available resources. Lower numbers mean higher
* priority.
*
* @returns {Promise}
*/
acquire(priority) {
if (this._started === false && this._config.autostart === false) {
this.start();
}
if (this._draining) {
return this._Promise.reject(
new Error("pool is draining and cannot accept work")
);
}
// TODO: should we defer this check till after this event loop incase "the situation" changes in the meantime
if (
this.spareResourceCapacity < 1 &&
this._availableObjects.length < 1 &&
this._config.maxWaitingClients !== undefined &&
this._waitingClientsQueue.length >= this._config.maxWaitingClients
) {
return this._Promise.reject(
new Error("max waitingClients count exceeded")
);
}
const resourceRequest = new ResourceRequest(
this._config.acquireTimeoutMillis,
this._Promise
);
this._waitingClientsQueue.enqueue(resourceRequest, priority);
this._dispense();
return resourceRequest.promise;
}
/**
* [use method, aquires a resource, passes the resource to a user supplied function and releases it]
* @param {Function} fn [a function that accepts a resource and returns a promise that resolves/rejects once it has finished using the resource]
* @return {Promise} [resolves once the resource is released to the pool]
*/
use(fn, priority) {
return this.acquire(priority).then(resource => {
return fn(resource).then(
result => {
this.release(resource);
return result;
},
err => {
this.destroy(resource);
throw err;
}
);
});
}
/**
* Check if resource is currently on loan from the pool
*
* @param {Function} resource
* Resource for checking.
*
* @returns {Boolean}
* True if resource belongs to this pool and false otherwise
*/
isBorrowedResource(resource) {
return this._resourceLoans.has(resource);
}
/**
* Return the resource to the pool when it is no longer required.
*
* @param {Object} resource
* The acquired object to be put back to the pool.
*/
release(resource) {
// check for an outstanding loan
const loan = this._resourceLoans.get(resource);
if (loan === undefined) {
return this._Promise.reject(
new Error("Resource not currently part of this pool")
);
}
this._resourceLoans.delete(resource);
loan.resolve();
const pooledResource = loan.pooledResource;
pooledResource.deallocate();
this._addPooledResourceToAvailableObjects(pooledResource);
this._dispense();
return this._Promise.resolve();
}
/**
* Request the resource to be destroyed. The factory's destroy handler
* will also be called.
*
* This should be called within an acquire() block as an alternative to release().
*
* @param {Object} resource
* The acquired resource to be destoyed.
*/
destroy(resource) {
// check for an outstanding loan
const loan = this._resourceLoans.get(resource);
if (loan === undefined) {
return this._Promise.reject(
new Error("Resource not currently part of this pool")
);
}
this._resourceLoans.delete(resource);
loan.resolve();
const pooledResource = loan.pooledResource;
pooledResource.deallocate();
this._destroy(pooledResource);
this._dispense();
return this._Promise.resolve();
}
_addPooledResourceToAvailableObjects(pooledResource) {
pooledResource.idle();
if (this._config.fifo === true) {
this._availableObjects.push(pooledResource);
} else {
this._availableObjects.unshift(pooledResource);
}
}
/**
* Disallow any new acquire calls and let the request backlog dissapate.
* The Pool will no longer attempt to maintain a "min" number of resources
* and will only make new resources on demand.
* Resolves once all resource requests are fulfilled and all resources are returned to pool and available...
* Should probably be called "drain work"
* @returns {Promise}
*/
drain() {
this._draining = true;
return this.__allResourceRequestsSettled()
.then(() => {
return this.__allResourcesReturned();
})
.then(() => {
this._descheduleEvictorRun();
});
}
__allResourceRequestsSettled() {
if (this._waitingClientsQueue.length > 0) {
// wait for last waiting client to be settled
// FIXME: what if they can "resolve" out of order....?
return reflector(this._waitingClientsQueue.tail.promise);
}
return this._Promise.resolve();
}
// FIXME: this is a horrific mess
__allResourcesReturned() {
const ps = Array.from(this._resourceLoans.values())
.map(loan => loan.promise)
.map(reflector);
return this._Promise.all(ps);
}
/**
* Forcibly destroys all available resources regardless of timeout. Intended to be
* invoked as part of a drain. Does not prevent the creation of new
* resources as a result of subsequent calls to acquire.
*
* Note that if factory.min > 0 and the pool isn't "draining", the pool will destroy all idle resources
* in the pool, but replace them with newly created resources up to the
* specified factory.min value. If this is not desired, set factory.min
* to zero before calling clear()
*
*/
clear() {
const reflectedCreatePromises = Array.from(
this._factoryCreateOperations
).map(reflector);
// wait for outstanding factory.create to complete
return this._Promise.all(reflectedCreatePromises).then(() => {
// Destroy existing resources
// @ts-ignore
for (const resource of this._availableObjects) {
this._destroy(resource);
}
const reflectedDestroyPromises = Array.from(
this._factoryDestroyOperations
).map(reflector);
return reflector(this._Promise.all(reflectedDestroyPromises));
});
}
/**
* Waits until the pool is ready.
* We define ready by checking if the current resource number is at least
* the minimum number defined.
* @returns {Promise} that resolves when the minimum number is ready.
*/
ready() {
return new this._Promise(resolve => {
const isReady = () => {
if (this.available >= this.min) {
resolve();
} else {
setTimeout(isReady, 100);
}
};
isReady();
});
}
/**
* How many resources are available to allocated
* (includes resources that have not been tested and may faul validation)
* NOTE: internal for now as the name is awful and might not be useful to anyone
* @return {Number} number of resources the pool has to allocate
*/
get _potentiallyAllocableResourceCount() {
return (
this._availableObjects.length +
this._testOnBorrowResources.size +
this._testOnReturnResources.size +
this._factoryCreateOperations.size
);
}
/**
* The combined count of the currently created objects and those in the
* process of being created
* Does NOT include resources in the process of being destroyed
* sort of legacy...
* @return {Number}
*/
get _count() {
return this._allObjects.size + this._factoryCreateOperations.size;
}
/**
* How many more resources does the pool have room for
* @return {Number} number of resources the pool could create before hitting any limits
*/
get spareResourceCapacity() {
return (
this._config.max -
(this._allObjects.size + this._factoryCreateOperations.size)
);
}
/**
* see _count above
* @return {Number} [description]
*/
get size() {
return this._count;
}
/**
* number of available resources
* @return {Number} [description]
*/
get available() {
return this._availableObjects.length;
}
/**
* number of resources that are currently acquired
* @return {Number} [description]
*/
get borrowed() {
return this._resourceLoans.size;
}
/**
* number of waiting acquire calls
* @return {Number} [description]
*/
get pending() {
return this._waitingClientsQueue.length;
}
/**
* maximum size of the pool
* @return {Number} [description]
*/
get max() {
return this._config.max;
}
/**
* minimum size of the pool
* @return {Number} [description]
*/
get min() {
return this._config.min;
}
}
module.exports = Pool;

34
backend/node_modules/generic-pool/lib/PoolDefaults.js generated vendored Normal file
View File

@@ -0,0 +1,34 @@
"use strict";
/**
* Create the default settings used by the pool
*
* @class
*/
class PoolDefaults {
constructor() {
this.fifo = true;
this.priorityRange = 1;
this.testOnBorrow = false;
this.testOnReturn = false;
this.autostart = true;
this.evictionRunIntervalMillis = 0;
this.numTestsPerEvictionRun = 3;
this.softIdleTimeoutMillis = -1;
this.idleTimeoutMillis = 30000;
// FIXME: no defaults!
this.acquireTimeoutMillis = null;
this.destroyTimeoutMillis = null;
this.maxWaitingClients = null;
this.min = null;
this.max = null;
// FIXME: this seems odd?
this.Promise = Promise;
}
}
module.exports = PoolDefaults;

109
backend/node_modules/generic-pool/lib/PoolOptions.js generated vendored Normal file
View File

@@ -0,0 +1,109 @@
"use strict";
const PoolDefaults = require("./PoolDefaults");
class PoolOptions {
/**
* @param {Object} opts
* configuration for the pool
* @param {Number} [opts.max=null]
* Maximum number of items that can exist at the same time. Default: 1.
* Any further acquire requests will be pushed to the waiting list.
* @param {Number} [opts.min=null]
* Minimum number of items in pool (including in-use). Default: 0.
* When the pool is created, or a resource destroyed, this minimum will
* be checked. If the pool resource count is below the minimum, a new
* resource will be created and added to the pool.
* @param {Number} [opts.maxWaitingClients=null]
* maximum number of queued requests allowed after which acquire calls will be rejected
* @param {Boolean} [opts.testOnBorrow=false]
* should the pool validate resources before giving them to clients. Requires that
* `factory.validate` is specified.
* @param {Boolean} [opts.testOnReturn=false]
* should the pool validate resources before returning them to the pool.
* @param {Number} [opts.acquireTimeoutMillis=null]
* Delay in milliseconds after which the an `acquire` call will fail. optional.
* Default: undefined. Should be positive and non-zero
* @param {Number} [opts.destroyTimeoutMillis=null]
* Delay in milliseconds after which the an `destroy` call will fail, causing it to emit a factoryDestroyError event. optional.
* Default: undefined. Should be positive and non-zero
* @param {Number} [opts.priorityRange=1]
* The range from 1 to be treated as a valid priority
* @param {Boolean} [opts.fifo=true]
* Sets whether the pool has LIFO (last in, first out) behaviour with respect to idle objects.
* if false then pool has FIFO behaviour
* @param {Boolean} [opts.autostart=true]
* Should the pool start creating resources etc once the constructor is called
* @param {Number} [opts.evictionRunIntervalMillis=0]
* How often to run eviction checks. Default: 0 (does not run).
* @param {Number} [opts.numTestsPerEvictionRun=3]
* Number of resources to check each eviction run. Default: 3.
* @param {Number} [opts.softIdleTimeoutMillis=-1]
* amount of time an object may sit idle in the pool before it is eligible
* for eviction by the idle object evictor (if any), with the extra condition
* that at least "min idle" object instances remain in the pool. Default -1 (nothing can get evicted)
* @param {Number} [opts.idleTimeoutMillis=30000]
* the minimum amount of time that an object may sit idle in the pool before it is eligible for eviction
* due to idle time. Supercedes "softIdleTimeoutMillis" Default: 30000
* @param {typeof Promise} [opts.Promise=Promise]
* What promise implementation should the pool use, defaults to native promises.
*/
constructor(opts) {
const poolDefaults = new PoolDefaults();
opts = opts || {};
this.fifo = typeof opts.fifo === "boolean" ? opts.fifo : poolDefaults.fifo;
this.priorityRange = opts.priorityRange || poolDefaults.priorityRange;
this.testOnBorrow =
typeof opts.testOnBorrow === "boolean"
? opts.testOnBorrow
: poolDefaults.testOnBorrow;
this.testOnReturn =
typeof opts.testOnReturn === "boolean"
? opts.testOnReturn
: poolDefaults.testOnReturn;
this.autostart =
typeof opts.autostart === "boolean"
? opts.autostart
: poolDefaults.autostart;
if (opts.acquireTimeoutMillis) {
// @ts-ignore
this.acquireTimeoutMillis = parseInt(opts.acquireTimeoutMillis, 10);
}
if (opts.destroyTimeoutMillis) {
// @ts-ignore
this.destroyTimeoutMillis = parseInt(opts.destroyTimeoutMillis, 10);
}
if (opts.maxWaitingClients !== undefined) {
// @ts-ignore
this.maxWaitingClients = parseInt(opts.maxWaitingClients, 10);
}
// @ts-ignore
this.max = parseInt(opts.max, 10);
// @ts-ignore
this.min = parseInt(opts.min, 10);
this.max = Math.max(isNaN(this.max) ? 1 : this.max, 1);
this.min = Math.min(isNaN(this.min) ? 0 : this.min, this.max);
this.evictionRunIntervalMillis =
opts.evictionRunIntervalMillis || poolDefaults.evictionRunIntervalMillis;
this.numTestsPerEvictionRun =
opts.numTestsPerEvictionRun || poolDefaults.numTestsPerEvictionRun;
this.softIdleTimeoutMillis =
opts.softIdleTimeoutMillis || poolDefaults.softIdleTimeoutMillis;
this.idleTimeoutMillis =
opts.idleTimeoutMillis || poolDefaults.idleTimeoutMillis;
this.Promise = opts.Promise != null ? opts.Promise : poolDefaults.Promise;
}
}
module.exports = PoolOptions;

View File

@@ -0,0 +1,49 @@
"use strict";
const PooledResourceStateEnum = require("./PooledResourceStateEnum");
/**
* @class
* @private
*/
class PooledResource {
constructor(resource) {
this.creationTime = Date.now();
this.lastReturnTime = null;
this.lastBorrowTime = null;
this.lastIdleTime = null;
this.obj = resource;
this.state = PooledResourceStateEnum.IDLE;
}
// mark the resource as "allocated"
allocate() {
this.lastBorrowTime = Date.now();
this.state = PooledResourceStateEnum.ALLOCATED;
}
// mark the resource as "deallocated"
deallocate() {
this.lastReturnTime = Date.now();
this.state = PooledResourceStateEnum.IDLE;
}
invalidate() {
this.state = PooledResourceStateEnum.INVALID;
}
test() {
this.state = PooledResourceStateEnum.VALIDATION;
}
idle() {
this.lastIdleTime = Date.now();
this.state = PooledResourceStateEnum.IDLE;
}
returning() {
this.state = PooledResourceStateEnum.RETURNING;
}
}
module.exports = PooledResource;

View File

@@ -0,0 +1,11 @@
"use strict";
const PooledResourceStateEnum = {
ALLOCATED: "ALLOCATED", // In use
IDLE: "IDLE", // In the queue, not in use.
INVALID: "INVALID", // Failed validation
RETURNING: "RETURNING", // Resource is in process of returning
VALIDATION: "VALIDATION" // Currently being tested
};
module.exports = PooledResourceStateEnum;

69
backend/node_modules/generic-pool/lib/PriorityQueue.js generated vendored Normal file
View File

@@ -0,0 +1,69 @@
"use strict";
const Queue = require("./Queue");
/**
* @class
* @private
*/
class PriorityQueue {
constructor(size) {
this._size = Math.max(+size | 0, 1);
/** @type {Queue[]} */
this._slots = [];
// initialize arrays to hold queue elements
for (let i = 0; i < this._size; i++) {
this._slots.push(new Queue());
}
}
get length() {
let _length = 0;
for (let i = 0, slots = this._slots.length; i < slots; i++) {
_length += this._slots[i].length;
}
return _length;
}
enqueue(obj, priority) {
// Convert to integer with a default value of 0.
priority = (priority && +priority | 0) || 0;
if (priority) {
if (priority < 0 || priority >= this._size) {
priority = this._size - 1;
// put obj at the end of the line
}
}
this._slots[priority].push(obj);
}
dequeue() {
for (let i = 0, sl = this._slots.length; i < sl; i += 1) {
if (this._slots[i].length) {
return this._slots[i].shift();
}
}
return;
}
get head() {
for (let i = 0, sl = this._slots.length; i < sl; i += 1) {
if (this._slots[i].length > 0) {
return this._slots[i].head;
}
}
return;
}
get tail() {
for (let i = this._slots.length - 1; i >= 0; i--) {
if (this._slots[i].length > 0) {
return this._slots[i].tail;
}
}
return;
}
}
module.exports = PriorityQueue;

35
backend/node_modules/generic-pool/lib/Queue.js generated vendored Normal file
View File

@@ -0,0 +1,35 @@
"use strict";
const DoublyLinkedList = require("./DoublyLinkedList");
const Deque = require("./Deque");
/**
* Sort of a internal queue for holding the waiting
* resource requets for a given "priority".
* Also handles managing timeouts rejections on items (is this the best place for this?)
* This is the last point where we know which queue a resourceRequest is in
*
*/
class Queue extends Deque {
/**
* Adds the obj to the end of the list for this slot
* we completely override the parent method because we need access to the
* node for our rejection handler
* @param {any} resourceRequest [description]
*/
push(resourceRequest) {
const node = DoublyLinkedList.createNode(resourceRequest);
resourceRequest.promise.catch(this._createTimeoutRejectionHandler(node));
this._list.insertEnd(node);
}
_createTimeoutRejectionHandler(node) {
return reason => {
if (reason.name === "TimeoutError") {
this._list.remove(node);
}
};
}
}
module.exports = Queue;

29
backend/node_modules/generic-pool/lib/ResourceLoan.js generated vendored Normal file
View File

@@ -0,0 +1,29 @@
"use strict";
const Deferred = require("./Deferred");
/**
* Plan is to maybe add tracking via Error objects
* and other fun stuff!
*/
class ResourceLoan extends Deferred {
/**
*
* @param {any} pooledResource the PooledResource this loan belongs to
* @return {any} [description]
*/
constructor(pooledResource, Promise) {
super(Promise);
this._creationTimestamp = Date.now();
this.pooledResource = pooledResource;
}
reject() {
/**
* Loans can only be resolved at the moment
*/
}
}
module.exports = ResourceLoan;

View File

@@ -0,0 +1,76 @@
"use strict";
const Deferred = require("./Deferred");
const errors = require("./errors");
function fbind(fn, ctx) {
return function bound() {
return fn.apply(ctx, arguments);
};
}
/**
* Wraps a users request for a resource
* Basically a promise mashed in with a timeout
* @private
*/
class ResourceRequest extends Deferred {
/**
* [constructor description]
* @param {Number} ttl timeout
*/
constructor(ttl, Promise) {
super(Promise);
this._creationTimestamp = Date.now();
this._timeout = null;
if (ttl !== undefined) {
this.setTimeout(ttl);
}
}
setTimeout(delay) {
if (this._state !== ResourceRequest.PENDING) {
return;
}
const ttl = parseInt(delay, 10);
if (isNaN(ttl) || ttl <= 0) {
throw new Error("delay must be a positive int");
}
const age = Date.now() - this._creationTimestamp;
if (this._timeout) {
this.removeTimeout();
}
this._timeout = setTimeout(
fbind(this._fireTimeout, this),
Math.max(ttl - age, 0)
);
}
removeTimeout() {
if (this._timeout) {
clearTimeout(this._timeout);
}
this._timeout = null;
}
_fireTimeout() {
this.reject(new errors.TimeoutError("ResourceRequest timed out"));
}
reject(reason) {
this.removeTimeout();
super.reject(reason);
}
resolve(value) {
this.removeTimeout();
super.resolve(value);
}
}
module.exports = ResourceRequest;

27
backend/node_modules/generic-pool/lib/errors.js generated vendored Normal file
View File

@@ -0,0 +1,27 @@
"use strict";
class ExtendableError extends Error {
constructor(message) {
super(message);
// @ts-ignore
this.name = this.constructor.name;
this.message = message;
if (typeof Error.captureStackTrace === "function") {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = new Error(message).stack;
}
}
}
/* eslint-disable no-useless-constructor */
class TimeoutError extends ExtendableError {
constructor(m) {
super(m);
}
}
/* eslint-enable no-useless-constructor */
module.exports = {
TimeoutError: TimeoutError
};

View File

@@ -0,0 +1,16 @@
module.exports = function(factory) {
if (typeof factory.create !== "function") {
throw new TypeError("factory.create must be a function");
}
if (typeof factory.destroy !== "function") {
throw new TypeError("factory.destroy must be a function");
}
if (
typeof factory.validate !== "undefined" &&
typeof factory.validate !== "function"
) {
throw new TypeError("factory.validate must be a function");
}
};

13
backend/node_modules/generic-pool/lib/utils.js generated vendored Normal file
View File

@@ -0,0 +1,13 @@
"use strict";
function noop() {}
/**
* Reflects a promise but does not expose any
* underlying value or rejection from that promise.
* @param {Promise} promise [description]
* @return {Promise} [description]
*/
exports.reflector = function(promise) {
return promise.then(noop, noop);
};