2
2
* @copyright Copyright (c) 2022 Adam Josefus
3
3
*/
4
4
5
+ import { type DependenciesType } from "./DependenciesType.ts" ;
6
+
5
7
6
8
export type GeneratorType < T > = ( ) => T ;
7
- type LoadOnlyEntryType = [ key : string ] ;
8
- type LoadAndGenerateEntryType < T > = [ key : string , generator : GeneratorType < T > ] ;
9
- type LoadEntryType < T > = LoadOnlyEntryType | LoadAndGenerateEntryType < T > ;
9
+
10
+ type LoadOnlyEntryType =
11
+ | [ key : string ]
12
+ | [ key : string , dependencies : DependenciesType ] ;
13
+
14
+ type LoadAndGenerateEntryType < T > =
15
+ | [ key : string , generator : GeneratorType < T > ]
16
+ | [ key : string , generator : GeneratorType < T > , dependencies ?: DependenciesType ] ;
17
+
18
+ type LoadEntryType < T > =
19
+ | LoadOnlyEntryType | LoadAndGenerateEntryType < T > ;
20
+
21
+ type StateType = {
22
+ timestamp : number ;
23
+ files : Map < string , number > ;
24
+ } ;
10
25
11
26
12
27
export class Cache < T > {
13
28
14
- readonly #storage: Map < string , T > = new Map ( ) ;
29
+ readonly #storage: Map < string , {
30
+ value : T ,
31
+ dependencies ?: DependenciesType ,
32
+ state : StateType ,
33
+ } > = new Map ( ) ;
15
34
16
35
17
36
/**
@@ -20,15 +39,46 @@ export class Cache<T> {
20
39
* @returns `<T>` or `<T> | undefined` – depending on whether the generator has been set.
21
40
*/
22
41
load < E extends LoadEntryType < T > > ( ...args : E ) : E extends LoadAndGenerateEntryType < T > ? T : T | undefined {
23
- const [ key , generator ] = args ;
24
-
25
- if ( this . #storage. has ( key ) ) {
26
- return this . #storage. get ( key ) ! ;
42
+ const { key, generator, dependencies } = ( ( ) => {
43
+ const [ key , a , b ] = args ;
44
+
45
+ if ( key !== undefined && a !== undefined && b !== undefined ) {
46
+ return {
47
+ key,
48
+ generator : a as GeneratorType < T > ,
49
+ dependencies : b as DependenciesType ,
50
+ }
51
+
52
+ } else if ( typeof a === 'function' ) {
53
+ return {
54
+ key,
55
+ generator : a as GeneratorType < T > ,
56
+ dependencies : undefined ,
57
+ }
58
+
59
+ } else if ( typeof a === 'object' ) {
60
+ return {
61
+ key,
62
+ generator : undefined ,
63
+ dependencies : a as DependenciesType ,
64
+ }
65
+
66
+ } else {
67
+ return {
68
+ key,
69
+ generator : undefined ,
70
+ dependencies : undefined ,
71
+ }
72
+ }
73
+ } ) ( ) ;
74
+
75
+ if ( this . has ( key ) ) {
76
+ return this . #load( key ) ! ;
27
77
}
28
78
29
79
if ( generator ) {
30
80
const value = generator ( ) ;
31
- this . save ( key , value ) ;
81
+ this . save ( key , value , dependencies ) ;
32
82
33
83
return value ;
34
84
}
@@ -38,13 +88,27 @@ export class Cache<T> {
38
88
}
39
89
40
90
91
+ #load( key : string ) : T | undefined {
92
+ if ( ! this . #storage. has ( key ) ) return undefined ;
93
+
94
+ this . #update( key , true ) ;
95
+
96
+ const { value } = this . #storage. get ( key ) ! ;
97
+ return value ;
98
+ }
99
+
100
+
41
101
/**
42
102
* Save value to cache by key.
43
103
* @param key
44
104
* @param value
45
105
*/
46
- save ( key : string , value : T ) : void {
47
- this . #storage. set ( key , value ) ;
106
+ save ( key : string , value : T , dependencies ?: DependenciesType ) : void {
107
+ this . #storage. set ( key , {
108
+ value,
109
+ dependencies,
110
+ state : Cache . #createState( dependencies ) ,
111
+ } ) ;
48
112
}
49
113
50
114
@@ -54,15 +118,101 @@ export class Cache<T> {
54
118
* @param value
55
119
*/
56
120
has ( key : string ) : boolean {
121
+ this . #update( key , false ) ;
122
+
57
123
return this . #storage. has ( key ) ;
58
124
}
59
125
60
126
61
127
/**
128
+ * Remove value from cache.
129
+ * @param key
130
+ */
131
+ remove ( key : string ) : void {
132
+ this . #storage. delete ( key ) ;
133
+ }
134
+
135
+
136
+ /**
137
+ * @deprecated Use `remove` instead.
138
+ *
62
139
* Delete value from cache.
63
140
* @param key
64
141
*/
65
142
delete ( key : string ) : void {
66
- this . #storage. delete ( key ) ;
143
+ this . remove ( key ) ;
144
+ }
145
+
146
+
147
+ #isValid( state : StateType , dependencies : DependenciesType ) {
148
+ if ( dependencies . expire ) {
149
+ const expired = Date . now ( ) > state . timestamp + dependencies . expire ;
150
+ if ( expired ) return false ;
151
+ }
152
+
153
+ if ( dependencies . callbacks ) {
154
+ const callbacks = [ dependencies . callbacks ] . flat ( ) ;
155
+ const invalid = callbacks . some ( callback => ! callback ( ) ) ;
156
+
157
+ if ( invalid ) return false ;
158
+ }
159
+
160
+ if ( dependencies . files ) {
161
+ const current = Cache . #computeFileModificationMap( [ dependencies . files ] . flat ( ) ) ;
162
+
163
+ const invalid = [ ...state . files . entries ( ) ] . some ( ( [ file , modifed ] ) => {
164
+ return ! current . has ( file ) || current . get ( file ) !== modifed ;
165
+ } ) ;
166
+
167
+ if ( invalid ) return false ;
168
+ }
169
+
170
+ return true ;
171
+ }
172
+
173
+
174
+ #update( key : string , refreshState : boolean ) : void {
175
+ if ( ! this . #storage. has ( key ) ) return ;
176
+
177
+ const { dependencies, state } = this . #storage. get ( key ) ! ;
178
+
179
+ if ( ! dependencies ) return ;
180
+
181
+ if ( ! this . #isValid( state , dependencies ) ) {
182
+ this . remove ( key ) ;
183
+ return
184
+ }
185
+
186
+ if ( refreshState ) {
187
+ if ( dependencies . sliding ) state . timestamp = Date . now ( ) ;
188
+ }
189
+ }
190
+
191
+
192
+ static #createState( dependencies ?: DependenciesType ) : StateType {
193
+ const files = [ dependencies ?. files ?? [ ] ] . flat ( ) ;
194
+
195
+ return {
196
+ timestamp : Date . now ( ) ,
197
+ files : Cache . #computeFileModificationMap( files ) ,
198
+ } ;
199
+ }
200
+
201
+
202
+ static #computeFileModificationMap( files : string [ ] ) : Map < string , number > {
203
+ const result = new Map < string , number > ( ) ;
204
+
205
+ files . forEach ( file => {
206
+ try {
207
+ const modified = Deno . statSync ( file ) . mtime ?. getTime ( ) ?? null ;
208
+ if ( modified === null ) return ;
209
+
210
+ result . set ( file , modified ) ;
211
+ } catch ( _err ) {
212
+ return ;
213
+ }
214
+ } ) ;
215
+
216
+ return result ;
67
217
}
68
218
}
0 commit comments