mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into webpack-memory
This commit is contained in:
@ -419,30 +419,35 @@ export class SlashCommandHandler {
|
|||||||
namedArgumentList: [
|
namedArgumentList: [
|
||||||
SlashCommandNamedArgument.fromProps({
|
SlashCommandNamedArgument.fromProps({
|
||||||
name: 'set',
|
name: 'set',
|
||||||
description: 'QR set name',
|
description: 'Name of QR set to add the context menu to',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: localEnumProviders.qrSets,
|
enumProvider: localEnumProviders.qrSets,
|
||||||
}),
|
}),
|
||||||
SlashCommandNamedArgument.fromProps({
|
SlashCommandNamedArgument.fromProps({
|
||||||
name: 'label',
|
name: 'label',
|
||||||
description: 'Quick Reply label',
|
description: 'Label of Quick Reply to add the context menu to',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
enumProvider: localEnumProviders.qrEntries,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
}),
|
}),
|
||||||
SlashCommandNamedArgument.fromProps({
|
SlashCommandNamedArgument.fromProps({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
description: 'numeric ID of the QR, e.g., id=42',
|
description: 'Numeric ID of Quick Reply to add the context menu to, e.g. id=42',
|
||||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
enumProvider: localEnumProviders.qrIds,
|
enumProvider: localEnumProviders.qrIds,
|
||||||
}),
|
}),
|
||||||
new SlashCommandNamedArgument(
|
new SlashCommandNamedArgument(
|
||||||
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
'chain',
|
||||||
|
'If true, button QR is sent together with (before) the clicked QR from the context menu',
|
||||||
|
[ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
'false',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
description: 'QR set name',
|
description: 'Name of QR set to add as a context menu',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: localEnumProviders.qrSets,
|
enumProvider: localEnumProviders.qrSets,
|
||||||
@ -450,13 +455,16 @@ export class SlashCommandHandler {
|
|||||||
],
|
],
|
||||||
helpString: `
|
helpString: `
|
||||||
<div>
|
<div>
|
||||||
Add context menu preset to a QR.
|
Add a context menu preset to a QR.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
If <code>id</code> and <code>label</code> are both provided, <code>id</code> will be used.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Example:</strong>
|
<strong>Example:</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<pre><code>/qr-contextadd set=MyPreset label=MyButton chain=true MyOtherPreset</code></pre>
|
<pre><code>/qr-contextadd set=MyQRSetWithTheButton label=MyButton chain=true MyQRSetWithContextItems</code></pre>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -470,27 +478,27 @@ export class SlashCommandHandler {
|
|||||||
namedArgumentList: [
|
namedArgumentList: [
|
||||||
SlashCommandNamedArgument.fromProps({
|
SlashCommandNamedArgument.fromProps({
|
||||||
name: 'set',
|
name: 'set',
|
||||||
description: 'QR set name',
|
description: 'Name of QR set to remove the context menu from',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: localEnumProviders.qrSets,
|
enumProvider: localEnumProviders.qrSets,
|
||||||
}),
|
}),
|
||||||
SlashCommandNamedArgument.fromProps({
|
SlashCommandNamedArgument.fromProps({
|
||||||
name: 'label',
|
name: 'label',
|
||||||
description: 'Quick Reply label',
|
description: 'Label of Quick Reply to remove the context menu from',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
enumProvider: localEnumProviders.qrEntries,
|
enumProvider: localEnumProviders.qrEntries,
|
||||||
}),
|
}),
|
||||||
SlashCommandNamedArgument.fromProps({
|
SlashCommandNamedArgument.fromProps({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
description: 'numeric ID of the QR, e.g., id=42',
|
description: 'Numeric ID of Quick Reply to remove the context menu from, e.g. id=42',
|
||||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||||
enumProvider: localEnumProviders.qrIds,
|
enumProvider: localEnumProviders.qrIds,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
description: 'QR set name',
|
description: 'Name of QR set to remove',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
enumProvider: localEnumProviders.qrSets,
|
enumProvider: localEnumProviders.qrSets,
|
||||||
@ -500,6 +508,9 @@ export class SlashCommandHandler {
|
|||||||
<div>
|
<div>
|
||||||
Remove context menu preset from a QR.
|
Remove context menu preset from a QR.
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
If <code>id</code> and <code>label</code> are both provided, <code>id</code> will be used.
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Example:</strong>
|
<strong>Example:</strong>
|
||||||
<ul>
|
<ul>
|
||||||
@ -541,6 +552,9 @@ export class SlashCommandHandler {
|
|||||||
<div>
|
<div>
|
||||||
Remove all context menu presets from a QR.
|
Remove all context menu presets from a QR.
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
If <code>id</code> and a label are both provided, <code>id</code> will be used.
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Example:</strong>
|
<strong>Example:</strong>
|
||||||
<ul>
|
<ul>
|
||||||
@ -908,12 +922,11 @@ export class SlashCommandHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
createContextItem(args, name) {
|
createContextItem(args, name) {
|
||||||
try {
|
try {
|
||||||
this.api.createContextItem(
|
this.api.createContextItem(
|
||||||
args.set,
|
args.set,
|
||||||
args.label,
|
args.id !== undefined ? Number(args.id) : args.label,
|
||||||
name,
|
name,
|
||||||
isTrueBoolean(args.chain),
|
isTrueBoolean(args.chain),
|
||||||
);
|
);
|
||||||
@ -923,14 +936,14 @@ export class SlashCommandHandler {
|
|||||||
}
|
}
|
||||||
deleteContextItem(args, name) {
|
deleteContextItem(args, name) {
|
||||||
try {
|
try {
|
||||||
this.api.deleteContextItem(args.set, args.label, name);
|
this.api.deleteContextItem(args.set, args.id !== undefined ? Number(args.id) : args.label, name);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clearContextMenu(args, label) {
|
clearContextMenu(args, label) {
|
||||||
try {
|
try {
|
||||||
this.api.clearContextMenu(args.set, args.label ?? label);
|
this.api.clearContextMenu(args.set, args.id !== undefined ? Number(args.id) : args.label ?? label);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
toastr.error(ex.message);
|
toastr.error(ex.message);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ export class ContextMenu {
|
|||||||
this.itemList = this.build(qr).children;
|
this.itemList = this.build(qr).children;
|
||||||
this.itemList.forEach(item => {
|
this.itemList.forEach(item => {
|
||||||
item.onExpand = () => {
|
item.onExpand = () => {
|
||||||
this.itemList.filter(it => it != item)
|
this.itemList.filter(it => it !== item)
|
||||||
.forEach(it => it.collapse());
|
.forEach(it => it.collapse());
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -36,7 +36,9 @@ export class ContextMenu {
|
|||||||
icon: qr.icon,
|
icon: qr.icon,
|
||||||
showLabel: qr.showLabel,
|
showLabel: qr.showLabel,
|
||||||
label: qr.label,
|
label: qr.label,
|
||||||
|
title: qr.title,
|
||||||
message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
|
message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
|
||||||
|
isHidden: qr.isHidden,
|
||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
qr.contextList.forEach((cl) => {
|
qr.contextList.forEach((cl) => {
|
||||||
@ -51,7 +53,9 @@ export class ContextMenu {
|
|||||||
subTree.icon,
|
subTree.icon,
|
||||||
subTree.showLabel,
|
subTree.showLabel,
|
||||||
subTree.label,
|
subTree.label,
|
||||||
|
subTree.title,
|
||||||
subTree.message,
|
subTree.message,
|
||||||
|
subTree.isHidden,
|
||||||
(evt) => {
|
(evt) => {
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
const finalQr = Object.assign(new QuickReply(), subQr);
|
const finalQr = Object.assign(new QuickReply(), subQr);
|
||||||
|
@ -2,7 +2,7 @@ import { MenuItem } from './MenuItem.js';
|
|||||||
|
|
||||||
export class MenuHeader extends MenuItem {
|
export class MenuHeader extends MenuItem {
|
||||||
constructor(/**@type {String}*/label) {
|
constructor(/**@type {String}*/label) {
|
||||||
super(null, null, label, null, null);
|
super(null, null, label, null, null, false, null, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,11 +4,12 @@ export class MenuItem {
|
|||||||
/**@type {string}*/ icon;
|
/**@type {string}*/ icon;
|
||||||
/**@type {boolean}*/ showLabel;
|
/**@type {boolean}*/ showLabel;
|
||||||
/**@type {string}*/ label;
|
/**@type {string}*/ label;
|
||||||
|
/**@type {string}*/ title;
|
||||||
/**@type {object}*/ value;
|
/**@type {object}*/ value;
|
||||||
|
/**@type {boolean}*/ isHidden = false;
|
||||||
/**@type {function}*/ callback;
|
/**@type {function}*/ callback;
|
||||||
/**@type {MenuItem[]}*/ childList = [];
|
/**@type {MenuItem[]}*/ childList = [];
|
||||||
/**@type {SubMenu}*/ subMenu;
|
/**@type {SubMenu}*/ subMenu;
|
||||||
/**@type {boolean}*/ isForceExpanded = false;
|
|
||||||
|
|
||||||
/**@type {HTMLElement}*/ root;
|
/**@type {HTMLElement}*/ root;
|
||||||
|
|
||||||
@ -19,35 +20,67 @@ export class MenuItem {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} icon
|
* @param {?string} icon
|
||||||
* @param {boolean} showLabel
|
* @param {?boolean} showLabel
|
||||||
* @param {string} label
|
* @param {string} label
|
||||||
|
* @param {?string} title Tooltip
|
||||||
* @param {object} value
|
* @param {object} value
|
||||||
|
* @param {boolean} isHidden QR is Invisible (auto-execute only)
|
||||||
* @param {function} callback
|
* @param {function} callback
|
||||||
* @param {MenuItem[]} children
|
* @param {MenuItem[]} children
|
||||||
*/
|
*/
|
||||||
constructor(icon, showLabel, label, value, callback, children = []) {
|
constructor(icon, showLabel, label, title, value, isHidden, callback, children = []) {
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
this.showLabel = showLabel;
|
this.showLabel = showLabel;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
|
this.title = title;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.isHidden = isHidden;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.childList = children;
|
this.childList = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the MenuItem
|
||||||
|
*
|
||||||
|
* A .qr--hidden class is added to:
|
||||||
|
* - the item if it is "Invisible (auto-execute only)"
|
||||||
|
* - the icon if no icon is set
|
||||||
|
* - the label if an icon is set and showLabel is false
|
||||||
|
*
|
||||||
|
* There is no .qr--hidden class defined in default CSS, since having items
|
||||||
|
* that are invisible on the QR bar but visible in the context menu,
|
||||||
|
* or icon-only on the QR bar but labelled in the context menu, is a valid use case.
|
||||||
|
*
|
||||||
|
* To hide optional labels when icons are present, add this user CSS:
|
||||||
|
* .ctx-menu .ctx-item .qr--button-label.qr--hidden {display: none;}
|
||||||
|
* To hide icons when no icon is present (removes unwanted padding):
|
||||||
|
* .ctx-menu .ctx-item .qr--button-icon.qr--hidden {display: none;}
|
||||||
|
* To hide items that are set "invisible":
|
||||||
|
* .ctx-menu .ctx-item.qr--hidden {display: none;}
|
||||||
|
* To target submenus only, use .ctx-menu .ctx-sub-menu .qr--hidden {display: none;}
|
||||||
|
*
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
render() {
|
render() {
|
||||||
if (!this.root) {
|
if (!this.root) {
|
||||||
const item = document.createElement('li'); {
|
const item = document.createElement('li'); {
|
||||||
this.root = item;
|
this.root = item;
|
||||||
item.classList.add('list-group-item');
|
item.classList.add('list-group-item');
|
||||||
item.classList.add('ctx-item');
|
item.classList.add('ctx-item');
|
||||||
item.title = this.value;
|
|
||||||
|
// if this item is Invisible, add the hidden class
|
||||||
|
if (this.isHidden) item.classList.add('qr--hidden');
|
||||||
|
|
||||||
|
// if a title/tooltip is set, add it, otherwise use the QR content
|
||||||
|
// same as for the main QR list
|
||||||
|
item.title = this.title || this.value;
|
||||||
|
|
||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
item.addEventListener('click', (evt) => this.callback(evt, this));
|
item.addEventListener('click', (evt) => this.callback(evt, this));
|
||||||
}
|
}
|
||||||
const icon = document.createElement('div'); {
|
const icon = document.createElement('div'); {
|
||||||
this.domIcon = icon;
|
|
||||||
icon.classList.add('qr--button-icon');
|
icon.classList.add('qr--button-icon');
|
||||||
icon.classList.add('fa-solid');
|
icon.classList.add('fa-solid');
|
||||||
if (!this.icon) icon.classList.add('qr--hidden');
|
if (!this.icon) icon.classList.add('qr--hidden');
|
||||||
@ -55,7 +88,6 @@ export class MenuItem {
|
|||||||
item.append(icon);
|
item.append(icon);
|
||||||
}
|
}
|
||||||
const lbl = document.createElement('div'); {
|
const lbl = document.createElement('div'); {
|
||||||
this.domLabel = lbl;
|
|
||||||
lbl.classList.add('qr--button-label');
|
lbl.classList.add('qr--button-label');
|
||||||
if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden');
|
if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden');
|
||||||
lbl.textContent = this.label;
|
lbl.textContent = this.label;
|
||||||
|
@ -14,7 +14,7 @@ import jimp from 'jimp';
|
|||||||
|
|
||||||
import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
|
import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
|
||||||
import { jsonParser, urlencodedParser } from '../express-common.js';
|
import { jsonParser, urlencodedParser } from '../express-common.js';
|
||||||
import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer } from '../util.js';
|
import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap } from '../util.js';
|
||||||
import { TavernCardValidator } from '../validator/TavernCardValidator.js';
|
import { TavernCardValidator } from '../validator/TavernCardValidator.js';
|
||||||
import { parse, write } from '../character-card-parser.js';
|
import { parse, write } from '../character-card-parser.js';
|
||||||
import { readWorldInfoFile } from './worldinfo.js';
|
import { readWorldInfoFile } from './worldinfo.js';
|
||||||
@ -23,7 +23,8 @@ import { importRisuSprites } from './sprites.js';
|
|||||||
const defaultAvatarPath = './public/img/ai4.png';
|
const defaultAvatarPath = './public/img/ai4.png';
|
||||||
|
|
||||||
// KV-store for parsed character data
|
// KV-store for parsed character data
|
||||||
const characterDataCache = new Map();
|
// 100 MB limit. Would take roughly 3000 characters to reach this limit
|
||||||
|
const characterDataCache = new MemoryLimitedMap(1024 * 1024 * 100);
|
||||||
// Some Android devices require tighter memory management
|
// Some Android devices require tighter memory management
|
||||||
const isAndroid = process.platform === 'android';
|
const isAndroid = process.platform === 'android';
|
||||||
|
|
||||||
@ -58,6 +59,9 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u
|
|||||||
try {
|
try {
|
||||||
// Reset the cache
|
// Reset the cache
|
||||||
for (const key of characterDataCache.keys()) {
|
for (const key of characterDataCache.keys()) {
|
||||||
|
if (Buffer.isBuffer(inputFile)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (key.startsWith(inputFile)) {
|
if (key.startsWith(inputFile)) {
|
||||||
characterDataCache.delete(key);
|
characterDataCache.delete(key);
|
||||||
break;
|
break;
|
||||||
|
179
src/util.js
179
src/util.js
@ -670,3 +670,182 @@ export function isValidUrl(url) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MemoryLimitedMap class that limits the memory usage of string values.
|
||||||
|
*/
|
||||||
|
export class MemoryLimitedMap {
|
||||||
|
/**
|
||||||
|
* Creates an instance of MemoryLimitedMap.
|
||||||
|
* @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values.
|
||||||
|
*/
|
||||||
|
constructor(maxMemoryInBytes) {
|
||||||
|
if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0) {
|
||||||
|
throw new Error('maxMemoryInBytes must be a positive number');
|
||||||
|
}
|
||||||
|
this.maxMemory = maxMemoryInBytes;
|
||||||
|
this.currentMemory = 0;
|
||||||
|
this.map = new Map();
|
||||||
|
this.queue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimates the memory usage of a string in bytes.
|
||||||
|
* Assumes each character occupies 2 bytes (UTF-16).
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
static estimateStringSize(str) {
|
||||||
|
return str ? str.length * 2 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds or updates a key-value pair in the map.
|
||||||
|
* If adding the new value exceeds the memory limit, evicts oldest entries.
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
set(key, value) {
|
||||||
|
if (typeof key !== 'string' || typeof value !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValueSize = MemoryLimitedMap.estimateStringSize(value);
|
||||||
|
|
||||||
|
// If the new value itself exceeds the max memory, reject it
|
||||||
|
if (newValueSize > this.maxMemory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the key already exists to adjust memory accordingly
|
||||||
|
if (this.map.has(key)) {
|
||||||
|
const oldValue = this.map.get(key);
|
||||||
|
const oldValueSize = MemoryLimitedMap.estimateStringSize(oldValue);
|
||||||
|
this.currentMemory -= oldValueSize;
|
||||||
|
// Remove the key from its current position in the queue
|
||||||
|
const index = this.queue.indexOf(key);
|
||||||
|
if (index > -1) {
|
||||||
|
this.queue.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evict oldest entries until there's enough space
|
||||||
|
while (this.currentMemory + newValueSize > this.maxMemory && this.queue.length > 0) {
|
||||||
|
const oldestKey = this.queue.shift();
|
||||||
|
const oldestValue = this.map.get(oldestKey);
|
||||||
|
const oldestValueSize = MemoryLimitedMap.estimateStringSize(oldestValue);
|
||||||
|
this.map.delete(oldestKey);
|
||||||
|
this.currentMemory -= oldestValueSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After eviction, check again if there's enough space
|
||||||
|
if (this.currentMemory + newValueSize > this.maxMemory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new key-value pair
|
||||||
|
this.map.set(key, value);
|
||||||
|
this.queue.push(key);
|
||||||
|
this.currentMemory += newValueSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the value associated with the given key.
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
|
get(key) {
|
||||||
|
return this.map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the map contains the given key.
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
has(key) {
|
||||||
|
return this.map.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the key-value pair associated with the given key.
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {boolean} - Returns true if the key was found and deleted, else false.
|
||||||
|
*/
|
||||||
|
delete(key) {
|
||||||
|
if (!this.map.has(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const value = this.map.get(key);
|
||||||
|
const valueSize = MemoryLimitedMap.estimateStringSize(value);
|
||||||
|
this.map.delete(key);
|
||||||
|
this.currentMemory -= valueSize;
|
||||||
|
|
||||||
|
// Remove the key from the queue
|
||||||
|
const index = this.queue.indexOf(key);
|
||||||
|
if (index > -1) {
|
||||||
|
this.queue.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all entries from the map.
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
this.map.clear();
|
||||||
|
this.queue = [];
|
||||||
|
this.currentMemory = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of key-value pairs in the map.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
size() {
|
||||||
|
return this.map.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current memory usage in bytes.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
totalMemory() {
|
||||||
|
return this.currentMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator over the keys in the map.
|
||||||
|
* @returns {IterableIterator<string>}
|
||||||
|
*/
|
||||||
|
keys() {
|
||||||
|
return this.map.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator over the values in the map.
|
||||||
|
* @returns {IterableIterator<string>}
|
||||||
|
*/
|
||||||
|
values() {
|
||||||
|
return this.map.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates over the map in insertion order.
|
||||||
|
* @param {Function} callback - Function to execute for each element.
|
||||||
|
*/
|
||||||
|
forEach(callback) {
|
||||||
|
this.map.forEach((value, key) => {
|
||||||
|
callback(value, key, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the MemoryLimitedMap iterable.
|
||||||
|
* @returns {Iterator} - Iterator over [key, value] pairs.
|
||||||
|
*/
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return this.map[Symbol.iterator]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user