@@ -43,14 +43,19 @@ var (
43
43
44
44
// Cache configuration
45
45
cacheSize int64
46
+
47
+ // Cache TTL settings
48
+ cacheTTLSuccess = 24 * time .Hour // TTL for found entries
49
+ cacheTTLNotFound = 60 * time .Second // TTL for not found entries
46
50
)
47
51
48
52
type CacheEntry struct {
49
- Exists bool // false if not found in S3
50
- IsFile bool // true for file, false for directory
51
- Content []byte // file content (only for files)
52
- Files []string // immediate child files (only for dirs)
53
- Dirs []string // immediate child dirs (only for dirs)
53
+ Exists bool // false if not found in S3
54
+ IsFile bool // true for file, false for directory
55
+ Content []byte // file content (only for files)
56
+ Files []string // immediate child files (only for dirs)
57
+ Dirs []string // immediate child dirs (only for dirs)
58
+ ExpiresAt time.Time // expiration time for this entry
54
59
}
55
60
56
61
type TypecheckRequest struct {
@@ -115,11 +120,21 @@ func getFromCache(ctx context.Context, version, path string) *CacheEntry {
115
120
// Check cache first
116
121
cacheMutex .RLock ()
117
122
if cached , ok := cache .Get (cacheKey ); ok {
123
+ // Check if entry has expired
124
+ if time .Now ().Before (cached .ExpiresAt ) {
125
+ cacheMutex .RUnlock ()
126
+ s3CacheHits .Inc ()
127
+ return cached
128
+ }
129
+ // Entry expired, will refetch
130
+ cacheMutex .RUnlock ()
131
+ // Remove expired entry
132
+ cacheMutex .Lock ()
133
+ cache .Remove (cacheKey )
134
+ cacheMutex .Unlock ()
135
+ } else {
118
136
cacheMutex .RUnlock ()
119
- s3CacheHits .Inc ()
120
- return cached
121
137
}
122
- cacheMutex .RUnlock ()
123
138
124
139
s3CacheMisses .Inc ()
125
140
@@ -137,9 +152,10 @@ func getFromCache(ctx context.Context, version, path string) *CacheEntry {
137
152
if err == nil {
138
153
// log.Printf("S3 file found: %s (%d bytes)", cacheKey, len(content))
139
154
entry := & CacheEntry {
140
- Exists : true ,
141
- IsFile : true ,
142
- Content : content ,
155
+ Exists : true ,
156
+ IsFile : true ,
157
+ Content : content ,
158
+ ExpiresAt : time .Now ().Add (cacheTTLSuccess ),
143
159
}
144
160
cacheMutex .Lock ()
145
161
cache .Add (cacheKey , entry )
@@ -169,8 +185,11 @@ func getFromCache(ctx context.Context, version, path string) *CacheEntry {
169
185
if err != nil {
170
186
s3ListErrors .Inc ()
171
187
// log.Printf("S3 list error for key %s: %v", cacheKey, err)
172
- // Cache as non-existent
173
- entry := & CacheEntry {Exists : false }
188
+ // Cache as non-existent with shorter TTL
189
+ entry := & CacheEntry {
190
+ Exists : false ,
191
+ ExpiresAt : time .Now ().Add (cacheTTLNotFound ),
192
+ }
174
193
cacheMutex .Lock ()
175
194
cache .Add (cacheKey , entry )
176
195
cacheMutex .Unlock ()
@@ -208,6 +227,13 @@ func getFromCache(ctx context.Context, version, path string) *CacheEntry {
208
227
}
209
228
}
210
229
230
+ // Set expiration based on whether entry was found
231
+ if entry .Exists {
232
+ entry .ExpiresAt = time .Now ().Add (cacheTTLSuccess )
233
+ } else {
234
+ entry .ExpiresAt = time .Now ().Add (cacheTTLNotFound )
235
+ }
236
+
211
237
// Cache the result
212
238
cacheMutex .Lock ()
213
239
cache .Add (cacheKey , entry )
@@ -829,6 +855,39 @@ func build(w http.ResponseWriter, req *http.Request) {
829
855
json .NewEncoder (w ).Encode (response )
830
856
}
831
857
858
+ func flushCache (w http.ResponseWriter , req * http.Request ) {
859
+ if req .Method != http .MethodPost {
860
+ http .Error (w , "Method not allowed" , http .StatusMethodNotAllowed )
861
+ return
862
+ }
863
+
864
+ // Get cache metrics before flush
865
+ var entriesBefore int
866
+ cacheMutex .RLock ()
867
+ if cache != nil {
868
+ entriesBefore = cache .Len ()
869
+ }
870
+ cacheMutex .RUnlock ()
871
+
872
+ // Flush the cache
873
+ cacheMutex .Lock ()
874
+ if cache != nil {
875
+ cache .Purge ()
876
+ }
877
+ cacheMutex .Unlock ()
878
+
879
+ // Return flush summary
880
+ response := map [string ]interface {}{
881
+ "status" : "success" ,
882
+ "message" : "Cache flushed successfully" ,
883
+ "entries_cleared" : entriesBefore ,
884
+ "timestamp" : time .Now ().Unix (),
885
+ }
886
+
887
+ w .Header ().Set ("Content-Type" , "application/json" )
888
+ json .NewEncoder (w ).Encode (response )
889
+ }
890
+
832
891
func main () {
833
892
log .Printf ("TypeScript Go Server v%s starting..." , serverVersion )
834
893
@@ -883,13 +942,14 @@ func main() {
883
942
http .HandleFunc ("/health" , loggingMiddleware (health ))
884
943
http .HandleFunc ("/typecheck" , loggingMiddleware (typecheck ))
885
944
http .HandleFunc ("/build" , loggingMiddleware (build ))
945
+ http .HandleFunc ("/flush-cache" , loggingMiddleware (flushCache ))
886
946
http .HandleFunc ("/" , loggingMiddleware (hello ))
887
947
888
948
// Start Prometheus metrics server on port 9091
889
949
go startMetricsServer ()
890
950
891
951
log .Printf ("Server ready! Listening on :8080..." )
892
- log .Printf ("Endpoints: /, /health, /typecheck, /build" )
952
+ log .Printf ("Endpoints: /, /health, /typecheck, /build, /flush-cache " )
893
953
log .Printf ("Metrics available at :9091/metrics" )
894
954
895
955
if err := http .ListenAndServe (":8080" , nil ); err != nil {
0 commit comments