cache.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. 'use strict';
  2. const Assert = require('@hapi/hoek/lib/assert');
  3. const Clone = require('@hapi/hoek/lib/clone');
  4. const Common = require('./common');
  5. const internals = {
  6. max: 1000,
  7. supported: new Set(['undefined', 'boolean', 'number', 'string'])
  8. };
  9. exports.provider = {
  10. provision(options) {
  11. return new internals.Cache(options);
  12. }
  13. };
  14. // Least Recently Used (LRU) Cache
  15. internals.Cache = class {
  16. constructor(options = {}) {
  17. Common.assertOptions(options, ['max']);
  18. Assert(options.max === undefined || options.max && options.max > 0 && isFinite(options.max), 'Invalid max cache size');
  19. this._max = options.max || internals.max;
  20. this._map = new Map(); // Map of nodes by key
  21. this._list = new internals.List(); // List of nodes (most recently used in head)
  22. }
  23. get length() {
  24. return this._map.size;
  25. }
  26. set(key, value) {
  27. if (key !== null &&
  28. !internals.supported.has(typeof key)) {
  29. return;
  30. }
  31. let node = this._map.get(key);
  32. if (node) {
  33. node.value = value;
  34. this._list.first(node);
  35. return;
  36. }
  37. node = this._list.unshift({ key, value });
  38. this._map.set(key, node);
  39. this._compact();
  40. }
  41. get(key) {
  42. const node = this._map.get(key);
  43. if (node) {
  44. this._list.first(node);
  45. return Clone(node.value);
  46. }
  47. }
  48. _compact() {
  49. if (this._map.size > this._max) {
  50. const node = this._list.pop();
  51. this._map.delete(node.key);
  52. }
  53. }
  54. };
  55. internals.List = class {
  56. constructor() {
  57. this.tail = null;
  58. this.head = null;
  59. }
  60. unshift(node) {
  61. node.next = null;
  62. node.prev = this.head;
  63. if (this.head) {
  64. this.head.next = node;
  65. }
  66. this.head = node;
  67. if (!this.tail) {
  68. this.tail = node;
  69. }
  70. return node;
  71. }
  72. first(node) {
  73. if (node === this.head) {
  74. return;
  75. }
  76. this._remove(node);
  77. this.unshift(node);
  78. }
  79. pop() {
  80. return this._remove(this.tail);
  81. }
  82. _remove(node) {
  83. const { next, prev } = node;
  84. next.prev = prev;
  85. if (prev) {
  86. prev.next = next;
  87. }
  88. if (node === this.tail) {
  89. this.tail = next;
  90. }
  91. node.prev = null;
  92. node.next = null;
  93. return node;
  94. }
  95. };