UNPKG

2.56 kBJavaScriptView Raw
1'use strict';
2
3const Assert = require('@hapi/hoek/lib/assert');
4const Clone = require('@hapi/hoek/lib/clone');
5
6const Common = require('./common');
7
8
9const internals = {
10 max: 1000,
11 supported: new Set(['undefined', 'boolean', 'number', 'string'])
12};
13
14
15exports.provider = {
16
17 provision(options) {
18
19 return new internals.Cache(options);
20 }
21};
22
23
24// Least Recently Used (LRU) Cache
25
26internals.Cache = class {
27
28 constructor(options = {}) {
29
30 Common.assertOptions(options, ['max']);
31 Assert(options.max === undefined || options.max && options.max > 0 && isFinite(options.max), 'Invalid max cache size');
32
33 this._max = options.max || internals.max;
34
35 this._map = new Map(); // Map of nodes by key
36 this._list = new internals.List(); // List of nodes (most recently used in head)
37 }
38
39 get length() {
40
41 return this._map.size;
42 }
43
44 set(key, value) {
45
46 if (key !== null &&
47 !internals.supported.has(typeof key)) {
48
49 return;
50 }
51
52 let node = this._map.get(key);
53 if (node) {
54 node.value = value;
55 this._list.first(node);
56 return;
57 }
58
59 node = this._list.unshift({ key, value });
60 this._map.set(key, node);
61 this._compact();
62 }
63
64 get(key) {
65
66 const node = this._map.get(key);
67 if (node) {
68 this._list.first(node);
69 return Clone(node.value);
70 }
71 }
72
73 _compact() {
74
75 if (this._map.size > this._max) {
76 const node = this._list.pop();
77 this._map.delete(node.key);
78 }
79 }
80};
81
82
83internals.List = class {
84
85 constructor() {
86
87 this.tail = null;
88 this.head = null;
89 }
90
91 unshift(node) {
92
93 node.next = null;
94 node.prev = this.head;
95
96 if (this.head) {
97 this.head.next = node;
98 }
99
100 this.head = node;
101
102 if (!this.tail) {
103 this.tail = node;
104 }
105
106 return node;
107 }
108
109 first(node) {
110
111 if (node === this.head) {
112 return;
113 }
114
115 this._remove(node);
116 this.unshift(node);
117 }
118
119 pop() {
120
121 return this._remove(this.tail);
122 }
123
124 _remove(node) {
125
126 const { next, prev } = node;
127
128 next.prev = prev;
129
130 if (prev) {
131 prev.next = next;
132 }
133
134 if (node === this.tail) {
135 this.tail = next;
136 }
137
138 node.prev = null;
139 node.next = null;
140
141 return node;
142 }
143};