npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@nolawnchairs/lru

v1.1.0

Published

LRU Map implementation for NodeJS and the browser

Downloads

2

Readme

LRU (Least-Recently Used) Map

A full implementation of an ES6 Map interface that curates an LRU (least recently used) ordering to the entries.

Installation

npm i @nolawnchairs/lru
yarn add @nolawnchairs/lru

LRUMap

class LRUMap<K extends KeyScalar, V>

| TypeParameter | Description | |--|--| | K | The type of key. Must be string, number or symbol | | T | The type of value |

Constructor

constructor(capacity: number, entries?: Iterable<[K, V]>)

| Parameter | Type | Description | |--|--|--| | capacity | number | The entry capacity. Must be greater than 1 or -1 for an unbounded capacity | | entries | Iterable<[K,V]> | An optional iterable list of key-value tuples to initiate data. Elements are added through the LRU system at point of entry, so the 0th item in the iterator becomes the least recently used entry (tail) |

const map = new LRUMap<string, string>(5, [
    ['a', 'A'],
    ['b', 'B'],
    ['c', 'C'],
    ['d', 'D'],
    ['e', 'E'],
])

Unbounded Capacity

To create a collection with unlimited capacity, use the static factory method unbounded and just provide an optional iterable of initial values.

static unbounded<K extends KeyScalar, V>(entries?: Iterable<[K,V]>): LRUMap<K, V>

Properties

| Property | Description | |--|--| | size | The total number of entries in the map | | head | The newest (last used) entry in the map | | tail | The oldest (least recently used) entry in the map |

Methods

get(key: K): Nullable<V>

Gets the value of type V using its key and registers the use. Returns null if no entry was found

peek(key: K): Nullable<V>

Gets the value of type V using its key without registering the use. Returns null if no entry was found

set(key: K, value: V): this

Sets a value with a key of type K and a value of type V and registers use with this new entry being the most recent. Returns the instance of the map for chaining

remove(key: K): Nullable<V>

Removes the entry by key and returns the removed value of type V, or null if no key was found

delete(key: K): boolean

Analog of the remove method, but returns a boolean value upon successful removal of the entry

has(key: K): boolean

Determine if an entry with the key exists

clear(): void

Clears all entries from the map

ES6 Map Methods & Iterators

This LRUMap implementation is interchangeable with the ES6 map, and as such the following methods are implemented:

forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void
entryIterator(): IterableIterator<MapEntry<K, V>>
entries(): IterableIterator<[K,V]>
keys(): IterableIterator<K>
values(): IterableIterator<V>

Since the entries() method in the native ES6 Map interface must return an iterator of map entries in tuple form ([key, value]), the entryIterator() method is provided to get the actual MapEntry object instead:

const map = new LRUMap<string, string>(5, [
    ['a', 'A'],
    ['b', 'B'],
])

for (const entry of map.entryIterator()) {
    console.log(entry.key, entry.value) // 'a', 'A' ... 'b', 'B'
}

The LRUMap itself is iterable and uses the same iterator as the entries() method.

const map = new LRUMap<string, string>(5, [
    ['a', 'A'],
    ['b', 'B'],
    ['c', 'C'],
    ['d', 'D'],
    ['e', 'E'],
])

for (const [key, value] of map) {
    console.log(`Map Entry: ${key}: ${value}`)
}

LRUSizedMap

The standard LRUMap class retains and evicts its entries based on the count of entries. While useful when most entry values are of similar size, the LRUSizedMap gives finer-grained control over the memory footprint by using the actual size, or length of the values to determine the eviction of entries. This is especially useful when dealing with larger quantities of binary data, or when using items with great variations where predicting overall memory usage is not practical.

class LRUSizedMap<K extends KeyScalar, V extends ByteLengthAware>

The ByteLengthAware type is an alias for the following:

type ByteLengthAware = string | Buffer | LRUSizedArray<ByteLengthAware>

the LRUSizedMap will only accept String, Buffer and LRUSizedArray types, as these report the actual byte length they consume, which facilitates the map keeping track of its memory footprint.

The LRUSizedMap class constructor takes the same arguments as the standard LRUMap, but the first argument is the limit on byte length as opposed to the actual count of entries to hold.

import { randomBytes } from 'crypto'

// Create a sized LRU map that will hold one megabyte
const map = new LRUSizedMap<string, Buffer>(1_000_000, [
    ['a', randomBytes(0x10000)],
    ['b', randomBytes(0x20000)],
    ['c', randomBytes(0x10000)],
    ['d', randomBytes(0x7ffff)],
    ['e', randomBytes(0x10000)],
])

The LRUSizedMap comes with the additional property used which reports exactly how many bytes the map's values occupy.

get used(): number

One important thing to note is that the memory footprint's affinity is tied to that of the accumulated size of each value in the map's entries. The size of the keys and the map structure itself bears no impact when calculating the size of the data set.

Enforcing Memory Footprint

Also note that when using the set method, the inserted (or updated) entry in the map is set before the eviction process runs, which means that the memory footprint may exceed your defined capacity during the fraction of time between the new data's insert/update and the completion of the eviction process. To ensure that any least-recently-used entries are evicted prior to inserting/updating new data, the accommodate method can be run to guarantee that enough data from memory is freed beforehand.

accommodate(bytes: number): boolean

To ensure that 8KB worth of data is cleared before setting:

map.accommodate(8192)
map.set('f', randomBytes(8192))

Note that the accommodate method forces the eviction of the amount of bytes passed to it, regardless of whether the data you're setting will actually increase the footprint. This can happen if a value already exists for the key and consists of the same or more bytes than you're attempting to accommodate. This will cause some LRU entries to be prematurely evicted when they needn't be. Therefore it's recommended to omit the use of accommodate unless you must be absolutely certain your memory capacity is not exceeded (even for the insignificant frame of time it takes eviction to run).

The LRUSizedMap class does not allow an unbounded version, as this defeats its purpose.

LRUSizedArray

Arrays cannot be used directly with the LRUSizedMap, as their length properties report the number of items in the collection, and not the actual byte size that the LRU expects.

Therefore, this library includes a simple wrapper around the native Javascript Array type.

It's only limitation is that the types it can hold are constrained by the same ByteLengthAware type that the LRUSizedMap does.

export class LRUSizedArray<T extends ByteLengthAware> 

Wrap a native JS array with LRUSizedArray

const standardOldJsArray = ['foo', 'bar']
const array = new LRUSizedArray<string>(standardOldJsArray)

// Find the byte size of it's elements
console.log(array.length) // 6 instead of 2

// access the underlying native array
const originalArray = array.items