| 670 | return false; | 670 | return false; |
| 671 | } | 671 | } |
| 672 | } | 672 | } |
| | 673 | |
| | 674 | /** |
| | 675 | * MemoryLimitedMap class that limits the memory usage of string values. |
| | 676 | */ |
| | 677 | export class MemoryLimitedMap { |
| | 678 | /** |
| | 679 | * Creates an instance of MemoryLimitedMap. |
| | 680 | * @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values. |
| | 681 | */ |
| | 682 | constructor(maxMemoryInBytes) { |
| | 683 | if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0) { |
| | 684 | throw new Error('maxMemoryInBytes must be a positive number'); |
| | 685 | } |
| | 686 | this.maxMemory = maxMemoryInBytes; |
| | 687 | this.currentMemory = 0; |
| | 688 | this.map = new Map(); |
| | 689 | this.queue = []; |
| | 690 | } |
| | 691 | |
| | 692 | /** |
| | 693 | * Estimates the memory usage of a string in bytes. |
| | 694 | * Assumes each character occupies 2 bytes (UTF-16). |
| | 695 | * @param {string} str |
| | 696 | * @returns {number} |
| | 697 | */ |
| | 698 | static estimateStringSize(str) { |
| | 699 | return str ? str.length * 2 : 0; |
| | 700 | } |
| | 701 | |
| | 702 | /** |
| | 703 | * Adds or updates a key-value pair in the map. |
| | 704 | * If adding the new value exceeds the memory limit, evicts oldest entries. |
| | 705 | * @param {string} key |
| | 706 | * @param {string} value |
| | 707 | */ |
| | 708 | set(key, value) { |
| | 709 | if (typeof key !== 'string' || typeof value !== 'string') { |
| | 710 | return; |
| | 711 | } |
| | 712 | |
| | 713 | const newValueSize = MemoryLimitedMap.estimateStringSize(value); |
| | 714 | |
| | 715 | // If the new value itself exceeds the max memory, reject it |
| | 716 | if (newValueSize > this.maxMemory) { |
| | 717 | return; |
| | 718 | } |
| | 719 | |
| | 720 | // Check if the key already exists to adjust memory accordingly |
| | 721 | if (this.map.has(key)) { |
| | 722 | const oldValue = this.map.get(key); |
| | 723 | const oldValueSize = MemoryLimitedMap.estimateStringSize(oldValue); |
| | 724 | this.currentMemory -= oldValueSize; |
| | 725 | // Remove the key from its current position in the queue |
| | 726 | const index = this.queue.indexOf(key); |
| | 727 | if (index > -1) { |
| | 728 | this.queue.splice(index, 1); |
| | 729 | } |
| | 730 | } |
| | 731 | |
| | 732 | // Evict oldest entries until there's enough space |
| | 733 | while (this.currentMemory + newValueSize > this.maxMemory && this.queue.length > 0) { |
| | 734 | const oldestKey = this.queue.shift(); |
| | 735 | const oldestValue = this.map.get(oldestKey); |
| | 736 | const oldestValueSize = MemoryLimitedMap.estimateStringSize(oldestValue); |
| | 737 | this.map.delete(oldestKey); |
| | 738 | this.currentMemory -= oldestValueSize; |
| | 739 | } |
| | 740 | |
| | 741 | // After eviction, check again if there's enough space |
| | 742 | if (this.currentMemory + newValueSize > this.maxMemory) { |
| | 743 | return; |
| | 744 | } |
| | 745 | |
| | 746 | // Add the new key-value pair |
| | 747 | this.map.set(key, value); |
| | 748 | this.queue.push(key); |
| | 749 | this.currentMemory += newValueSize; |
| | 750 | } |
| | 751 | |
| | 752 | /** |
| | 753 | * Retrieves the value associated with the given key. |
| | 754 | * @param {string} key |
| | 755 | * @returns {string | undefined} |
| | 756 | */ |
| | 757 | get(key) { |
| | 758 | return this.map.get(key); |
| | 759 | } |
| | 760 | |
| | 761 | /** |
| | 762 | * Checks if the map contains the given key. |
| | 763 | * @param {string} key |
| | 764 | * @returns {boolean} |
| | 765 | */ |
| | 766 | has(key) { |
| | 767 | return this.map.has(key); |
| | 768 | } |
| | 769 | |
| | 770 | /** |
| | 771 | * Deletes the key-value pair associated with the given key. |
| | 772 | * @param {string} key |
| | 773 | * @returns {boolean} - Returns true if the key was found and deleted, else false. |
| | 774 | */ |
| | 775 | delete(key) { |
| | 776 | if (!this.map.has(key)) { |
| | 777 | return false; |
| | 778 | } |
| | 779 | const value = this.map.get(key); |
| | 780 | const valueSize = MemoryLimitedMap.estimateStringSize(value); |
| | 781 | this.map.delete(key); |
| | 782 | this.currentMemory -= valueSize; |
| | 783 | |
| | 784 | // Remove the key from the queue |
| | 785 | const index = this.queue.indexOf(key); |
| | 786 | if (index > -1) { |
| | 787 | this.queue.splice(index, 1); |
| | 788 | } |
| | 789 | |
| | 790 | return true; |
| | 791 | } |
| | 792 | |
| | 793 | /** |
| | 794 | * Clears all entries from the map. |
| | 795 | */ |
| | 796 | clear() { |
| | 797 | this.map.clear(); |
| | 798 | this.queue = []; |
| | 799 | this.currentMemory = 0; |
| | 800 | } |
| | 801 | |
| | 802 | /** |
| | 803 | * Returns the number of key-value pairs in the map. |
| | 804 | * @returns {number} |
| | 805 | */ |
| | 806 | size() { |
| | 807 | return this.map.size; |
| | 808 | } |
| | 809 | |
| | 810 | /** |
| | 811 | * Returns the current memory usage in bytes. |
| | 812 | * @returns {number} |
| | 813 | */ |
| | 814 | totalMemory() { |
| | 815 | return this.currentMemory; |
| | 816 | } |
| | 817 | |
| | 818 | /** |
| | 819 | * Returns an iterator over the keys in the map. |
| | 820 | * @returns {IterableIterator<string>} |
| | 821 | */ |
| | 822 | keys() { |
| | 823 | return this.map.keys(); |
| | 824 | } |
| | 825 | |
| | 826 | /** |
| | 827 | * Returns an iterator over the values in the map. |
| | 828 | * @returns {IterableIterator<string>} |
| | 829 | */ |
| | 830 | values() { |
| | 831 | return this.map.values(); |
| | 832 | } |
| | 833 | |
| | 834 | /** |
| | 835 | * Iterates over the map in insertion order. |
| | 836 | * @param {Function} callback - Function to execute for each element. |
| | 837 | */ |
| | 838 | forEach(callback) { |
| | 839 | this.map.forEach((value, key) => { |
| | 840 | callback(value, key, this); |
| | 841 | }); |
| | 842 | } |
| | 843 | |
| | 844 | /** |
| | 845 | * Makes the MemoryLimitedMap iterable. |
| | 846 | * @returns {Iterator} - Iterator over [key, value] pairs. |
| | 847 | */ |
| | 848 | [Symbol.iterator]() { |
| | 849 | return this.map[Symbol.iterator](); |
| | 850 | } |
| | 851 | } |