1 | import type { EqualityFn } from './types'
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | const NOT_FOUND = 'NOT_FOUND'
|
7 | type NOT_FOUND_TYPE = typeof NOT_FOUND
|
8 |
|
9 | interface Entry {
|
10 | key: unknown
|
11 | value: unknown
|
12 | }
|
13 |
|
14 | interface Cache {
|
15 | get(key: unknown): unknown | NOT_FOUND_TYPE
|
16 | put(key: unknown, value: unknown): void
|
17 | getEntries(): Entry[]
|
18 | clear(): void
|
19 | }
|
20 |
|
21 | function createSingletonCache(equals: EqualityFn): Cache {
|
22 | let entry: Entry | undefined
|
23 | return {
|
24 | get(key: unknown) {
|
25 | if (entry && equals(entry.key, key)) {
|
26 | return entry.value
|
27 | }
|
28 |
|
29 | return NOT_FOUND
|
30 | },
|
31 |
|
32 | put(key: unknown, value: unknown) {
|
33 | entry = { key, value }
|
34 | },
|
35 |
|
36 | getEntries() {
|
37 | return entry ? [entry] : []
|
38 | },
|
39 |
|
40 | clear() {
|
41 | entry = undefined
|
42 | }
|
43 | }
|
44 | }
|
45 |
|
46 | function createLruCache(maxSize: number, equals: EqualityFn): Cache {
|
47 | let entries: Entry[] = []
|
48 |
|
49 | function get(key: unknown) {
|
50 | const cacheIndex = entries.findIndex(entry => equals(key, entry.key))
|
51 |
|
52 |
|
53 | if (cacheIndex > -1) {
|
54 | const entry = entries[cacheIndex]
|
55 |
|
56 |
|
57 | if (cacheIndex > 0) {
|
58 | entries.splice(cacheIndex, 1)
|
59 | entries.unshift(entry)
|
60 | }
|
61 |
|
62 | return entry.value
|
63 | }
|
64 |
|
65 |
|
66 | return NOT_FOUND
|
67 | }
|
68 |
|
69 | function put(key: unknown, value: unknown) {
|
70 | if (get(key) === NOT_FOUND) {
|
71 |
|
72 | entries.unshift({ key, value })
|
73 | if (entries.length > maxSize) {
|
74 | entries.pop()
|
75 | }
|
76 | }
|
77 | }
|
78 |
|
79 | function getEntries() {
|
80 | return entries
|
81 | }
|
82 |
|
83 | function clear() {
|
84 | entries = []
|
85 | }
|
86 |
|
87 | return { get, put, getEntries, clear }
|
88 | }
|
89 |
|
90 | export const defaultEqualityCheck: EqualityFn = (a, b): boolean => {
|
91 | return a === b
|
92 | }
|
93 |
|
94 | export function createCacheKeyComparator(equalityCheck: EqualityFn) {
|
95 | return function areArgumentsShallowlyEqual(
|
96 | prev: unknown[] | IArguments | null,
|
97 | next: unknown[] | IArguments | null
|
98 | ): boolean {
|
99 | if (prev === null || next === null || prev.length !== next.length) {
|
100 | return false
|
101 | }
|
102 |
|
103 |
|
104 | const length = prev.length
|
105 | for (let i = 0; i < length; i++) {
|
106 | if (!equalityCheck(prev[i], next[i])) {
|
107 | return false
|
108 | }
|
109 | }
|
110 |
|
111 | return true
|
112 | }
|
113 | }
|
114 |
|
115 | export interface DefaultMemoizeOptions {
|
116 | equalityCheck?: EqualityFn
|
117 | resultEqualityCheck?: EqualityFn
|
118 | maxSize?: number
|
119 | }
|
120 |
|
121 |
|
122 |
|
123 | export function defaultMemoize<F extends (...args: any[]) => any>(
|
124 | func: F,
|
125 | equalityCheckOrOptions?: EqualityFn | DefaultMemoizeOptions
|
126 | ) {
|
127 | const providedOptions =
|
128 | typeof equalityCheckOrOptions === 'object'
|
129 | ? equalityCheckOrOptions
|
130 | : { equalityCheck: equalityCheckOrOptions }
|
131 |
|
132 | const {
|
133 | equalityCheck = defaultEqualityCheck,
|
134 | maxSize = 1,
|
135 | resultEqualityCheck
|
136 | } = providedOptions
|
137 |
|
138 | const comparator = createCacheKeyComparator(equalityCheck)
|
139 |
|
140 | const cache =
|
141 | maxSize === 1
|
142 | ? createSingletonCache(comparator)
|
143 | : createLruCache(maxSize, comparator)
|
144 |
|
145 |
|
146 | function memoized() {
|
147 | let value = cache.get(arguments)
|
148 | if (value === NOT_FOUND) {
|
149 |
|
150 | value = func.apply(null, arguments)
|
151 |
|
152 | if (resultEqualityCheck) {
|
153 | const entries = cache.getEntries()
|
154 | const matchingEntry = entries.find(entry =>
|
155 | resultEqualityCheck(entry.value, value)
|
156 | )
|
157 |
|
158 | if (matchingEntry) {
|
159 | value = matchingEntry.value
|
160 | }
|
161 | }
|
162 |
|
163 | cache.put(arguments, value)
|
164 | }
|
165 | return value
|
166 | }
|
167 |
|
168 | memoized.clearCache = () => cache.clear()
|
169 |
|
170 | return memoized as F & { clearCache: () => void }
|
171 | }
|