|
1 | 1 | import { ContestSiteApiClient } from '$lib/clients/common'; |
2 | 2 | import { AOJ_API_BASE_URL } from '$lib/constants/urls'; |
| 3 | +import { Cache, type ApiClientConfig } from '$lib/clients/cache'; |
| 4 | + |
3 | 5 | import type { ContestForImport, ContestsForImport } from '$lib/types/contest'; |
4 | 6 | import type { TasksForImport } from '$lib/types/task'; |
5 | 7 |
|
@@ -105,191 +107,6 @@ type ChallengeRoundMap = { |
105 | 107 | */ |
106 | 108 | const PENDING = -1; |
107 | 109 |
|
108 | | -/** |
109 | | - * The time-to-live (TTL) for the cache, specified in milliseconds. |
110 | | - * This value represents 1 hour. |
111 | | - */ |
112 | | -const DEFAULT_CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds |
113 | | -const DEFAULT_MAX_CACHE_SIZE = 50; |
114 | | - |
115 | | -/** |
116 | | - * Configuration options for caching. |
117 | | - * |
118 | | - * @property {number} [timeToLive] - The duration (in milliseconds) for which a cache entry should remain valid. |
119 | | - * @property {number} [maxSize] - The maximum number of entries that the cache can hold. |
120 | | - */ |
121 | | -interface CacheConfig { |
122 | | - timeToLive?: number; |
123 | | - maxSize?: number; |
124 | | -} |
125 | | - |
126 | | -/** |
127 | | - * Represents a cache entry with data and a timestamp. |
128 | | - * |
129 | | - * @template T - The type of the cached data. |
130 | | - * @property {T} data - The cached data. |
131 | | - * @property {number} timestamp - The timestamp when the data was cached. |
132 | | - */ |
133 | | -type CacheEntry<T> = { |
134 | | - data: T; |
135 | | - timestamp: number; |
136 | | -}; |
137 | | - |
138 | | -/** |
139 | | - * A generic cache class that stores data with a timestamp and provides methods to set, get, and delete cache entries. |
140 | | - * The cache automatically removes the oldest entry when the maximum cache size is reached. |
141 | | - * Entries are also automatically invalidated and removed if they exceed a specified time-to-live (TTL). |
142 | | - * |
143 | | - * @template T - The type of data to be stored in the cache. |
144 | | - */ |
145 | | -class Cache<T> { |
146 | | - private cache: Map<string, CacheEntry<T>> = new Map(); |
147 | | - private cleanupInterval: NodeJS.Timeout; |
148 | | - |
149 | | - /** |
150 | | - * Constructs an instance of the class with the specified cache time-to-live (TTL) and maximum cache size. |
151 | | - * |
152 | | - * @param timeToLive - The time-to-live for the cache entries, in milliseconds. Defaults to `CACHE_TTL`. |
153 | | - * @param maxSize - The maximum number of entries the cache can hold. Defaults to `MAX_CACHE_SIZE`. |
154 | | - */ |
155 | | - constructor( |
156 | | - private readonly timeToLive: number = DEFAULT_CACHE_TTL, |
157 | | - private readonly maxSize: number = DEFAULT_MAX_CACHE_SIZE, |
158 | | - ) { |
159 | | - if (timeToLive <= 0) { |
160 | | - throw new Error('TTL must be positive'); |
161 | | - } |
162 | | - if (maxSize <= 0) { |
163 | | - throw new Error('Max size must be positive'); |
164 | | - } |
165 | | - |
166 | | - this.cleanupInterval = setInterval(() => this.cleanup(), timeToLive); |
167 | | - } |
168 | | - |
169 | | - /** |
170 | | - * Gets the size of the cache. |
171 | | - * |
172 | | - * @returns {number} The number of items in the cache. |
173 | | - */ |
174 | | - get size(): number { |
175 | | - return this.cache.size; |
176 | | - } |
177 | | - |
178 | | - /** |
179 | | - * Retrieves the health status of the cache. |
180 | | - * |
181 | | - * @returns An object containing the size of the cache and the timestamp of the oldest entry. |
182 | | - * @property {number} size - The number of entries in the cache. |
183 | | - * @property {number} oldestEntry - The timestamp of the oldest entry in the cache. |
184 | | - */ |
185 | | - get health(): { size: number; oldestEntry: number } { |
186 | | - const oldestEntry = Math.min( |
187 | | - ...Array.from(this.cache.values()).map((entry) => entry.timestamp), |
188 | | - ); |
189 | | - return { size: this.cache.size, oldestEntry }; |
190 | | - } |
191 | | - |
192 | | - /** |
193 | | - * Sets a new entry in the cache with the specified key and data. |
194 | | - * If the cache size exceeds the maximum limit, the oldest entry is removed. |
195 | | - * |
196 | | - * @param key - The key associated with the data to be cached. |
197 | | - * @param data - The data to be cached. |
198 | | - */ |
199 | | - set(key: string, data: T): void { |
200 | | - if (!key || typeof key !== 'string' || key.length > 255) { |
201 | | - throw new Error('Invalid cache key'); |
202 | | - } |
203 | | - |
204 | | - if (this.cache.size >= this.maxSize) { |
205 | | - const oldestKey = this.findOldestEntry(); |
206 | | - |
207 | | - if (oldestKey) { |
208 | | - this.cache.delete(oldestKey); |
209 | | - } |
210 | | - } |
211 | | - |
212 | | - this.cache.set(key, { data, timestamp: Date.now() }); |
213 | | - } |
214 | | - |
215 | | - /** |
216 | | - * Retrieves an entry from the cache. |
217 | | - * |
218 | | - * @param key - The key associated with the cache entry. |
219 | | - * @returns The cached data if it exists and is not expired, otherwise `undefined`. |
220 | | - */ |
221 | | - get(key: string): T | undefined { |
222 | | - const entry = this.cache.get(key); |
223 | | - |
224 | | - if (!entry) { |
225 | | - return undefined; |
226 | | - } |
227 | | - |
228 | | - if (Date.now() - entry.timestamp > this.timeToLive) { |
229 | | - this.cache.delete(key); |
230 | | - return undefined; |
231 | | - } |
232 | | - |
233 | | - return entry.data; |
234 | | - } |
235 | | - |
236 | | - /** |
237 | | - * Disposes of resources used by the Aizu Online Judge client. |
238 | | - * |
239 | | - * This method clears the interval used for cleanup and clears the cache. |
240 | | - * It should be called when the client is no longer needed to prevent memory leaks. |
241 | | - */ |
242 | | - dispose(): void { |
243 | | - clearInterval(this.cleanupInterval); |
244 | | - this.cache.clear(); |
245 | | - } |
246 | | - |
247 | | - /** |
248 | | - * Clears all entries from the cache. |
249 | | - */ |
250 | | - clear(): void { |
251 | | - this.cache.clear(); |
252 | | - } |
253 | | - |
254 | | - /** |
255 | | - * Deletes an entry from the cache. |
256 | | - * |
257 | | - * @param key - The key of the entry to delete. |
258 | | - */ |
259 | | - delete(key: string): void { |
260 | | - this.cache.delete(key); |
261 | | - } |
262 | | - |
263 | | - private cleanup(): void { |
264 | | - const now = Date.now(); |
265 | | - |
266 | | - for (const [key, entry] of this.cache.entries()) { |
267 | | - if (now - entry.timestamp > this.timeToLive) { |
268 | | - this.cache.delete(key); |
269 | | - } |
270 | | - } |
271 | | - } |
272 | | - |
273 | | - private findOldestEntry(): string | undefined { |
274 | | - let oldestKey: string | undefined; |
275 | | - let oldestTime = Infinity; |
276 | | - |
277 | | - for (const [key, entry] of this.cache.entries()) { |
278 | | - if (entry.timestamp < oldestTime) { |
279 | | - oldestTime = entry.timestamp; |
280 | | - oldestKey = key; |
281 | | - } |
282 | | - } |
283 | | - |
284 | | - return oldestKey; |
285 | | - } |
286 | | -} |
287 | | - |
288 | | -interface ApiClientConfig { |
289 | | - contestCache: CacheConfig; |
290 | | - taskCache: CacheConfig; |
291 | | -} |
292 | | - |
293 | 110 | /** |
294 | 111 | * AojApiClient is a client for interacting with the Aizu Online Judge (AOJ) API. |
295 | 112 | * It extends the ContestSiteApiClient and provides methods to fetch contests and tasks |
|
0 commit comments