@mark1russell7/collections 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/collections",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "description": "Collections with no dependencies: lists, maps, queues, a summary tree, channels, lazy operations and dependency graphs.",
6
6
  "keywords": [
7
7
  "collections",
@@ -15,6 +15,7 @@
15
15
  * }
16
16
  */
17
17
 
18
+ import { checkCapacity } from "../core/capacity.js";
18
19
  import type { Middleware } from "../core/middleware.js";
19
20
  import type { List } from "../interfaces/list.js";
20
21
  import type { Queue, Deque } from "../interfaces/queue.js";
@@ -66,7 +67,8 @@ export interface BoundedCollection {
66
67
  export function boundedList<T>(
67
68
  options: BoundedOptions<T>
68
69
  ): Middleware<List<T>, List<T> & BoundedCollection> {
69
- const { capacity, policy = "throw", onOverflow } = options;
70
+ const { policy = "throw", onOverflow } = options;
71
+ const capacity = checkCapacity(options.capacity, "boundedList");
70
72
 
71
73
  return (next: List<T>): List<T> & BoundedCollection => {
72
74
  // An unshift puts the element at the front, so drop-oldest drops the last element for it
@@ -235,7 +237,8 @@ export function boundedList<T>(
235
237
  export function boundedQueue<T>(
236
238
  options: BoundedOptions<T>
237
239
  ): Middleware<Queue<T>, Queue<T> & BoundedCollection> {
238
- const { capacity, policy = "throw", onOverflow } = options;
240
+ const { policy = "throw", onOverflow } = options;
241
+ const capacity = checkCapacity(options.capacity, "boundedQueue");
239
242
 
240
243
  return (next: Queue<T>): Queue<T> & BoundedCollection => {
241
244
  const handleOverflow = (element: T): void => {
@@ -306,7 +309,8 @@ export function boundedQueue<T>(
306
309
  export function boundedDeque<T>(
307
310
  options: BoundedOptions<T>
308
311
  ): Middleware<Deque<T>, Deque<T> & BoundedCollection> {
309
- const { capacity, policy = "throw", onOverflow } = options;
312
+ const { policy = "throw", onOverflow } = options;
313
+ const capacity = checkCapacity(options.capacity, "boundedDeque");
310
314
 
311
315
  return (next: Deque<T>): Deque<T> & BoundedCollection => {
312
316
  const handleOverflow = (element: T): void => {
@@ -383,7 +387,8 @@ export function boundedMap<K, V>(
383
387
  onOverflow?: (context: Omit<OverflowContext<V>, "newElement"> & { key: K; value: V }) => void;
384
388
  }
385
389
  ): Middleware<MapLike<K, V>, MapLike<K, V> & BoundedCollection> {
386
- const { capacity, policy = "throw", onOverflow } = options;
390
+ const { policy = "throw", onOverflow } = options;
391
+ const capacity = checkCapacity(options.capacity, "boundedMap");
387
392
 
388
393
  return (next: MapLike<K, V>): MapLike<K, V> & BoundedCollection => {
389
394
  let insertionOrder: K[] = []; // Track insertion order for drop-oldest
@@ -14,6 +14,7 @@
14
14
  * // When full, least recently accessed keys are evicted
15
15
  */
16
16
 
17
+ import { checkCapacity } from "../core/capacity.js";
17
18
  import type { Middleware } from "../core/middleware.js";
18
19
  import type { MapLike } from "../interfaces/map.js";
19
20
  import { compositeMapMethod } from "./composite.js";
@@ -23,7 +24,8 @@ import { compositeMapMethod } from "./composite.js";
23
24
  */
24
25
  export interface LRUOptions {
25
26
  /**
26
- * Maximum capacity. When exceeded, least recently used items are evicted.
27
+ * The maximum number of entries, a positive whole number. When the map is full, a new key
28
+ * evicts the least recently used entry.
27
29
  */
28
30
  capacity: number;
29
31
 
@@ -50,11 +52,14 @@ interface LRUNode<K, V> {
50
52
  * This function maintains a doubly-linked list to track access order.
51
53
  * Most recently accessed items are at the head.
52
54
  * When capacity is exceeded, items at the tail are evicted.
55
+ *
56
+ * @throws RangeError when `options.capacity` is not a positive whole number.
53
57
  */
54
58
  export function lruMap<K, V>(
55
59
  options: LRUOptions
56
60
  ): Middleware<MapLike<K, V>, MapLike<K, V> & { readonly isFull: boolean }> {
57
- const { capacity, onEvict } = options;
61
+ const { onEvict } = options;
62
+ const capacity = checkCapacity(options.capacity, "lruMap");
58
63
 
59
64
  return (next: MapLike<K, V>): MapLike<K, V> & { readonly isFull: boolean } => {
60
65
  // Doubly-linked list for LRU tracking
@@ -290,8 +295,13 @@ export class LRUCache<K, V> {
290
295
  private head: LRUNode<K, V> | null = null;
291
296
  private tail: LRUNode<K, V> | null = null;
292
297
 
298
+ /**
299
+ * @param capacity - The maximum number of entries, a positive whole number.
300
+ * @param onEvict - The function that this cache starts for each entry that it evicts.
301
+ * @throws RangeError when `capacity` is not a positive whole number.
302
+ */
293
303
  constructor(capacity: number, onEvict?: ((key: K, value: V) => void) | undefined) {
294
- this.capacity = capacity;
304
+ this.capacity = checkCapacity(capacity, "LRUCache");
295
305
  this.onEvict = onEvict;
296
306
  }
297
307
 
@@ -359,6 +369,32 @@ export class LRUCache<K, V> {
359
369
  return true;
360
370
  }
361
371
 
372
+ /**
373
+ * This method deletes each entry for which `matches` gives true, and it gives the number of
374
+ * deleted entries. It is not a use: the order of the other entries does not change, and it
375
+ * does not start `onEvict`. It starts `matches` once for each entry, the most recent first. If
376
+ * `matches` throws, the entries that it matched before stay deleted.
377
+ *
378
+ * @param matches - The test of an entry.
379
+ * @returns The number of deleted entries.
380
+ */
381
+ deleteWhere(matches: (value: V, key: K) => boolean): number {
382
+ const nodes: LRUNode<K, V>[] = [];
383
+ for (let node = this.head; node; node = node.next) nodes.push(node);
384
+ let deleted = 0;
385
+ for (const node of nodes) {
386
+ // The function `matches` can change the cache. Skip each node that is not in it now.
387
+ if (this.map.get(node.key)?.node !== node) continue;
388
+ if (!matches(node.value, node.key)) continue;
389
+ // The function `matches` can delete this node itself, so check again.
390
+ if (this.map.get(node.key)?.node !== node) continue;
391
+ this.removeNode(node);
392
+ this.map.delete(node.key);
393
+ deleted++;
394
+ }
395
+ return deleted;
396
+ }
397
+
362
398
  clear(): void {
363
399
  this.map.clear();
364
400
  this.head = null;
@@ -439,7 +475,9 @@ export class LRUCache<K, V> {
439
475
  }
440
476
 
441
477
  /**
442
- * Factory function to make an LRU cache.
478
+ * This function makes an LRU cache.
479
+ *
480
+ * @throws RangeError when `capacity` is not a positive whole number.
443
481
  */
444
482
  export function lruCache<K, V>(
445
483
  capacity: number,
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The check of a capacity, for each layer and class that has a maximum size.
3
+ */
4
+
5
+ /**
6
+ * This function gives the capacity when it is a positive whole number. Otherwise it throws a
7
+ * `RangeError`. A capacity of `NaN` makes a comparison `size >= capacity` always false, so a
8
+ * structure with that capacity grows without a limit. A capacity of 0 or less, or a fraction,
9
+ * has no clear meaning.
10
+ *
11
+ * @param capacity - The capacity to check.
12
+ * @param owner - The name of the layer or class, for the message of the error.
13
+ * @returns The same capacity.
14
+ * @throws RangeError when the capacity is not a positive whole number.
15
+ */
16
+ export function checkCapacity(capacity: number, owner: string): number {
17
+ if (!Number.isSafeInteger(capacity) || capacity < 1) {
18
+ throw new RangeError(`${owner}: the capacity must be a positive whole number, not ${String(capacity)}`);
19
+ }
20
+ return capacity;
21
+ }