@forcecalendar/core 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/calendar/Calendar.js +715 -0
- package/core/calendar/DateUtils.js +553 -0
- package/core/conflicts/ConflictDetector.js +517 -0
- package/core/events/Event.js +914 -0
- package/core/events/EventStore.js +1198 -0
- package/core/events/RRuleParser.js +420 -0
- package/core/events/RecurrenceEngine.js +382 -0
- package/core/ics/ICSHandler.js +389 -0
- package/core/ics/ICSParser.js +475 -0
- package/core/performance/AdaptiveMemoryManager.js +333 -0
- package/core/performance/LRUCache.js +118 -0
- package/core/performance/PerformanceOptimizer.js +523 -0
- package/core/search/EventSearch.js +476 -0
- package/core/state/StateManager.js +546 -0
- package/core/timezone/TimezoneDatabase.js +294 -0
- package/core/timezone/TimezoneManager.js +419 -0
- package/core/types.js +366 -0
- package/package.json +11 -9
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimezoneManager - Comprehensive timezone handling for global calendar operations
|
|
3
|
+
* Handles timezone conversions, DST transitions, and IANA timezone database
|
|
4
|
+
*
|
|
5
|
+
* Critical for Salesforce orgs spanning multiple timezones
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { TimezoneDatabase } from './TimezoneDatabase.js';
|
|
9
|
+
|
|
10
|
+
export class TimezoneManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
// Initialize comprehensive timezone database
|
|
13
|
+
this.database = new TimezoneDatabase();
|
|
14
|
+
|
|
15
|
+
// Cache timezone offsets for performance
|
|
16
|
+
this.offsetCache = new Map();
|
|
17
|
+
this.dstCache = new Map();
|
|
18
|
+
|
|
19
|
+
// Cache size management
|
|
20
|
+
this.maxCacheSize = 1000;
|
|
21
|
+
this.cacheHits = 0;
|
|
22
|
+
this.cacheMisses = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Convert date from one timezone to another
|
|
27
|
+
* @param {Date} date - Date to convert
|
|
28
|
+
* @param {string} fromTimezone - Source timezone (IANA identifier)
|
|
29
|
+
* @param {string} toTimezone - Target timezone (IANA identifier)
|
|
30
|
+
* @returns {Date} Converted date
|
|
31
|
+
*/
|
|
32
|
+
convertTimezone(date, fromTimezone, toTimezone) {
|
|
33
|
+
if (!date) return null;
|
|
34
|
+
if (fromTimezone === toTimezone) return new Date(date);
|
|
35
|
+
|
|
36
|
+
// Get offset difference
|
|
37
|
+
const fromOffset = this.getTimezoneOffset(date, fromTimezone);
|
|
38
|
+
const toOffset = this.getTimezoneOffset(date, toTimezone);
|
|
39
|
+
const offsetDiff = (toOffset - fromOffset) * 60 * 1000; // Convert to milliseconds
|
|
40
|
+
|
|
41
|
+
return new Date(date.getTime() + offsetDiff);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Convert date to UTC
|
|
46
|
+
* @param {Date} date - Date in local timezone
|
|
47
|
+
* @param {string} timezone - Source timezone
|
|
48
|
+
* @returns {Date} Date in UTC
|
|
49
|
+
*/
|
|
50
|
+
toUTC(date, timezone) {
|
|
51
|
+
if (!date) return null;
|
|
52
|
+
if (timezone === 'UTC') return new Date(date);
|
|
53
|
+
|
|
54
|
+
const offset = this.getTimezoneOffset(date, timezone);
|
|
55
|
+
return new Date(date.getTime() - (offset * 60 * 1000));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Convert UTC date to timezone
|
|
60
|
+
* @param {Date} utcDate - Date in UTC
|
|
61
|
+
* @param {string} timezone - Target timezone
|
|
62
|
+
* @returns {Date} Date in specified timezone
|
|
63
|
+
*/
|
|
64
|
+
fromUTC(utcDate, timezone) {
|
|
65
|
+
if (!utcDate) return null;
|
|
66
|
+
if (timezone === 'UTC') return new Date(utcDate);
|
|
67
|
+
|
|
68
|
+
const offset = this.getTimezoneOffset(utcDate, timezone);
|
|
69
|
+
return new Date(utcDate.getTime() + (offset * 60 * 1000));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get timezone offset in minutes
|
|
74
|
+
* @param {Date} date - Date to check (for DST calculation)
|
|
75
|
+
* @param {string} timezone - Timezone identifier
|
|
76
|
+
* @returns {number} Offset in minutes from UTC
|
|
77
|
+
*/
|
|
78
|
+
getTimezoneOffset(date, timezone) {
|
|
79
|
+
// Resolve any aliases
|
|
80
|
+
timezone = this.database.resolveAlias(timezone);
|
|
81
|
+
|
|
82
|
+
// Check cache first
|
|
83
|
+
const cacheKey = `${timezone}_${date.getFullYear()}_${date.getMonth()}_${date.getDate()}`;
|
|
84
|
+
if (this.offsetCache.has(cacheKey)) {
|
|
85
|
+
this.cacheHits++;
|
|
86
|
+
this._manageCacheSize();
|
|
87
|
+
return this.offsetCache.get(cacheKey);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.cacheMisses++;
|
|
91
|
+
|
|
92
|
+
// Try using Intl API if available (best option for browser/Node.js environments)
|
|
93
|
+
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
|
|
94
|
+
try {
|
|
95
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
96
|
+
timeZone: timezone,
|
|
97
|
+
year: 'numeric',
|
|
98
|
+
month: '2-digit',
|
|
99
|
+
day: '2-digit',
|
|
100
|
+
hour: '2-digit',
|
|
101
|
+
minute: '2-digit',
|
|
102
|
+
second: '2-digit',
|
|
103
|
+
hour12: false
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Create same date in target timezone
|
|
107
|
+
const parts = formatter.formatToParts(date);
|
|
108
|
+
const tzDate = new Date(
|
|
109
|
+
parts.find(p => p.type === 'year').value,
|
|
110
|
+
parts.find(p => p.type === 'month').value - 1,
|
|
111
|
+
parts.find(p => p.type === 'day').value,
|
|
112
|
+
parts.find(p => p.type === 'hour').value,
|
|
113
|
+
parts.find(p => p.type === 'minute').value,
|
|
114
|
+
parts.find(p => p.type === 'second').value
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const offset = (tzDate.getTime() - date.getTime()) / (1000 * 60);
|
|
118
|
+
this.offsetCache.set(cacheKey, -offset);
|
|
119
|
+
this._manageCacheSize();
|
|
120
|
+
return -offset;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// Fallback to database calculation
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fallback: Use timezone database
|
|
127
|
+
const tzData = this.database.getTimezone(timezone);
|
|
128
|
+
if (!tzData) {
|
|
129
|
+
throw new Error(`Unknown timezone: ${timezone}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let offset = tzData.offset;
|
|
133
|
+
|
|
134
|
+
// Apply DST if applicable
|
|
135
|
+
if (tzData.dst && this.isDST(date, timezone, tzData.dst)) {
|
|
136
|
+
offset += tzData.dst.offset;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.offsetCache.set(cacheKey, offset);
|
|
140
|
+
this._manageCacheSize();
|
|
141
|
+
return offset;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if date is in DST for given timezone
|
|
146
|
+
* @param {Date} date - Date to check
|
|
147
|
+
* @param {string} timezone - Timezone identifier
|
|
148
|
+
* @param {Object} [dstRule] - DST rule object (optional, will fetch if not provided)
|
|
149
|
+
* @returns {boolean} True if in DST
|
|
150
|
+
*/
|
|
151
|
+
isDST(date, timezone, dstRule = null) {
|
|
152
|
+
// Get DST rule if not provided
|
|
153
|
+
if (!dstRule) {
|
|
154
|
+
const tzData = this.database.getTimezone(timezone);
|
|
155
|
+
if (!tzData || !tzData.dst) return false;
|
|
156
|
+
dstRule = tzData.dst;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const year = date.getFullYear();
|
|
160
|
+
const dstStart = this.getNthWeekdayOfMonth(year, dstRule.start.month, dstRule.start.week, dstRule.start.day);
|
|
161
|
+
const dstEnd = this.getNthWeekdayOfMonth(year, dstRule.end.month, dstRule.end.week, dstRule.end.day);
|
|
162
|
+
|
|
163
|
+
// Handle Southern Hemisphere (DST crosses year boundary)
|
|
164
|
+
if (dstStart > dstEnd) {
|
|
165
|
+
return date >= dstStart || date < dstEnd;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return date >= dstStart && date < dstEnd;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get nth weekday of month
|
|
173
|
+
* @private
|
|
174
|
+
*/
|
|
175
|
+
getNthWeekdayOfMonth(year, month, week, dayOfWeek) {
|
|
176
|
+
const date = new Date(year, month, 1);
|
|
177
|
+
const firstDay = date.getDay();
|
|
178
|
+
|
|
179
|
+
let dayOffset = dayOfWeek - firstDay;
|
|
180
|
+
if (dayOffset < 0) dayOffset += 7;
|
|
181
|
+
|
|
182
|
+
if (week > 0) {
|
|
183
|
+
// Nth occurrence from start
|
|
184
|
+
date.setDate(1 + dayOffset + (week - 1) * 7);
|
|
185
|
+
} else {
|
|
186
|
+
// Nth occurrence from end
|
|
187
|
+
const lastDay = new Date(year, month + 1, 0).getDate();
|
|
188
|
+
date.setDate(lastDay);
|
|
189
|
+
const lastDayOfWeek = date.getDay();
|
|
190
|
+
let offset = lastDayOfWeek - dayOfWeek;
|
|
191
|
+
if (offset < 0) offset += 7;
|
|
192
|
+
date.setDate(lastDay - offset + (week + 1) * 7);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return date;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get list of common timezones
|
|
200
|
+
* @returns {Array<{value: string, label: string, offset: string}>}
|
|
201
|
+
*/
|
|
202
|
+
getCommonTimezones() {
|
|
203
|
+
const now = new Date();
|
|
204
|
+
const timezones = [
|
|
205
|
+
{ value: 'America/New_York', label: 'Eastern Time (New York)', region: 'Americas' },
|
|
206
|
+
{ value: 'America/Chicago', label: 'Central Time (Chicago)', region: 'Americas' },
|
|
207
|
+
{ value: 'America/Denver', label: 'Mountain Time (Denver)', region: 'Americas' },
|
|
208
|
+
{ value: 'America/Phoenix', label: 'Mountain Time - Arizona (Phoenix)', region: 'Americas' },
|
|
209
|
+
{ value: 'America/Los_Angeles', label: 'Pacific Time (Los Angeles)', region: 'Americas' },
|
|
210
|
+
{ value: 'America/Anchorage', label: 'Alaska Time (Anchorage)', region: 'Americas' },
|
|
211
|
+
{ value: 'Pacific/Honolulu', label: 'Hawaii Time (Honolulu)', region: 'Pacific' },
|
|
212
|
+
{ value: 'America/Toronto', label: 'Eastern Time (Toronto)', region: 'Americas' },
|
|
213
|
+
{ value: 'America/Vancouver', label: 'Pacific Time (Vancouver)', region: 'Americas' },
|
|
214
|
+
{ value: 'America/Mexico_City', label: 'Central Time (Mexico City)', region: 'Americas' },
|
|
215
|
+
{ value: 'America/Sao_Paulo', label: 'Brasilia Time (São Paulo)', region: 'Americas' },
|
|
216
|
+
{ value: 'Europe/London', label: 'GMT/BST (London)', region: 'Europe' },
|
|
217
|
+
{ value: 'Europe/Paris', label: 'Central European Time (Paris)', region: 'Europe' },
|
|
218
|
+
{ value: 'Europe/Berlin', label: 'Central European Time (Berlin)', region: 'Europe' },
|
|
219
|
+
{ value: 'Europe/Moscow', label: 'Moscow Time', region: 'Europe' },
|
|
220
|
+
{ value: 'Asia/Dubai', label: 'Gulf Time (Dubai)', region: 'Asia' },
|
|
221
|
+
{ value: 'Asia/Kolkata', label: 'India Time (Mumbai)', region: 'Asia' },
|
|
222
|
+
{ value: 'Asia/Shanghai', label: 'China Time (Shanghai)', region: 'Asia' },
|
|
223
|
+
{ value: 'Asia/Tokyo', label: 'Japan Time (Tokyo)', region: 'Asia' },
|
|
224
|
+
{ value: 'Asia/Seoul', label: 'Korea Time (Seoul)', region: 'Asia' },
|
|
225
|
+
{ value: 'Asia/Singapore', label: 'Singapore Time', region: 'Asia' },
|
|
226
|
+
{ value: 'Australia/Sydney', label: 'Australian Eastern Time (Sydney)', region: 'Oceania' },
|
|
227
|
+
{ value: 'Australia/Melbourne', label: 'Australian Eastern Time (Melbourne)', region: 'Oceania' },
|
|
228
|
+
{ value: 'Pacific/Auckland', label: 'New Zealand Time (Auckland)', region: 'Oceania' },
|
|
229
|
+
{ value: 'UTC', label: 'UTC', region: 'UTC' }
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
// Add current offset to each timezone
|
|
233
|
+
return timezones.map(tz => {
|
|
234
|
+
const offset = this.getTimezoneOffset(now, tz.value);
|
|
235
|
+
const offsetHours = -offset / 60; // Convert to hours from UTC
|
|
236
|
+
const hours = Math.floor(Math.abs(offsetHours));
|
|
237
|
+
const minutes = Math.round(Math.abs(offsetHours % 1) * 60);
|
|
238
|
+
const sign = offsetHours >= 0 ? '+' : '-';
|
|
239
|
+
const offsetStr = `UTC${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
...tz,
|
|
243
|
+
offset: offsetStr,
|
|
244
|
+
offsetMinutes: -offset // Store in minutes for sorting
|
|
245
|
+
};
|
|
246
|
+
}).sort((a, b) => a.offsetMinutes - b.offsetMinutes);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Format date in specific timezone
|
|
251
|
+
* @param {Date} date - Date to format
|
|
252
|
+
* @param {string} timezone - Timezone for formatting
|
|
253
|
+
* @param {Object} options - Formatting options
|
|
254
|
+
* @returns {string} Formatted date string
|
|
255
|
+
*/
|
|
256
|
+
formatInTimezone(date, timezone, options = {}) {
|
|
257
|
+
if (!date) return '';
|
|
258
|
+
|
|
259
|
+
const defaultOptions = {
|
|
260
|
+
year: 'numeric',
|
|
261
|
+
month: '2-digit',
|
|
262
|
+
day: '2-digit',
|
|
263
|
+
hour: '2-digit',
|
|
264
|
+
minute: '2-digit',
|
|
265
|
+
hour12: true,
|
|
266
|
+
timeZone: timezone
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const formatOptions = { ...defaultOptions, ...options };
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
return new Intl.DateTimeFormat('en-US', formatOptions).format(date);
|
|
273
|
+
} catch (e) {
|
|
274
|
+
// Fallback to basic formatting
|
|
275
|
+
const tzDate = this.fromUTC(this.toUTC(date, 'UTC'), timezone);
|
|
276
|
+
return tzDate.toLocaleString('en-US', options);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get timezone from browser/system
|
|
282
|
+
* @returns {string} IANA timezone identifier
|
|
283
|
+
*/
|
|
284
|
+
getSystemTimezone() {
|
|
285
|
+
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
|
|
286
|
+
try {
|
|
287
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
288
|
+
} catch (e) {
|
|
289
|
+
// Fallback
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Fallback based on offset
|
|
294
|
+
const offset = new Date().getTimezoneOffset();
|
|
295
|
+
const offsetHours = -offset / 60;
|
|
296
|
+
|
|
297
|
+
// Try to match offset to known timezone
|
|
298
|
+
for (const [tz, tzOffset] of Object.entries(this.timezoneOffsets)) {
|
|
299
|
+
if (tzOffset === offsetHours) {
|
|
300
|
+
return tz;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return 'UTC';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Parse timezone from string (handles abbreviations)
|
|
309
|
+
* @param {string} tzString - Timezone string
|
|
310
|
+
* @returns {string} IANA timezone identifier
|
|
311
|
+
*/
|
|
312
|
+
parseTimezone(tzString) {
|
|
313
|
+
if (!tzString) return 'UTC';
|
|
314
|
+
|
|
315
|
+
// Check if it's already an IANA identifier
|
|
316
|
+
if (this.timezoneOffsets.hasOwnProperty(tzString)) {
|
|
317
|
+
return tzString;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Check abbreviations
|
|
321
|
+
const upperTz = tzString.toUpperCase();
|
|
322
|
+
if (this.timezoneAbbreviations.hasOwnProperty(upperTz)) {
|
|
323
|
+
return this.timezoneAbbreviations[upperTz];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Try to parse offset format (e.g., "+05:30", "-08:00")
|
|
327
|
+
const offsetMatch = tzString.match(/^([+-])(\d{2}):?(\d{2})$/);
|
|
328
|
+
if (offsetMatch) {
|
|
329
|
+
const sign = offsetMatch[1] === '+' ? 1 : -1;
|
|
330
|
+
const hours = parseInt(offsetMatch[2], 10);
|
|
331
|
+
const minutes = parseInt(offsetMatch[3], 10);
|
|
332
|
+
const totalOffset = sign * (hours + minutes / 60);
|
|
333
|
+
|
|
334
|
+
// Find matching timezone
|
|
335
|
+
for (const [tz, offset] of Object.entries(this.timezoneOffsets)) {
|
|
336
|
+
if (offset === totalOffset) {
|
|
337
|
+
return tz;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return 'UTC';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Calculate timezone difference in hours
|
|
347
|
+
* @param {string} timezone1 - First timezone
|
|
348
|
+
* @param {string} timezone2 - Second timezone
|
|
349
|
+
* @param {Date} [date] - Date for DST calculation
|
|
350
|
+
* @returns {number} Hour difference
|
|
351
|
+
*/
|
|
352
|
+
getTimezoneDifference(timezone1, timezone2, date = new Date()) {
|
|
353
|
+
const offset1 = this.getTimezoneOffset(date, timezone1);
|
|
354
|
+
const offset2 = this.getTimezoneOffset(date, timezone2);
|
|
355
|
+
return (offset2 - offset1) / 60;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Clear caches (useful when date changes significantly)
|
|
360
|
+
*/
|
|
361
|
+
clearCache() {
|
|
362
|
+
this.offsetCache.clear();
|
|
363
|
+
this.dstCache.clear();
|
|
364
|
+
this.cacheHits = 0;
|
|
365
|
+
this.cacheMisses = 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Validate timezone identifier
|
|
370
|
+
* @param {string} timezone - Timezone to validate
|
|
371
|
+
* @returns {boolean} True if valid
|
|
372
|
+
*/
|
|
373
|
+
isValidTimezone(timezone) {
|
|
374
|
+
return this.database.isValidTimezone(timezone);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Get cache statistics
|
|
379
|
+
* @returns {Object} Cache stats
|
|
380
|
+
*/
|
|
381
|
+
getCacheStats() {
|
|
382
|
+
const hitRate = this.cacheHits + this.cacheMisses > 0
|
|
383
|
+
? (this.cacheHits / (this.cacheHits + this.cacheMisses) * 100).toFixed(2)
|
|
384
|
+
: 0;
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
offsetCacheSize: this.offsetCache.size,
|
|
388
|
+
dstCacheSize: this.dstCache.size,
|
|
389
|
+
maxCacheSize: this.maxCacheSize,
|
|
390
|
+
cacheHits: this.cacheHits,
|
|
391
|
+
cacheMisses: this.cacheMisses,
|
|
392
|
+
hitRate: `${hitRate}%`
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Manage cache size - evict old entries if needed
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
400
|
+
_manageCacheSize() {
|
|
401
|
+
// Clear caches if they get too large
|
|
402
|
+
if (this.offsetCache.size > this.maxCacheSize) {
|
|
403
|
+
// Remove first half of entries (oldest)
|
|
404
|
+
const entriesToRemove = Math.floor(this.offsetCache.size / 2);
|
|
405
|
+
const keys = Array.from(this.offsetCache.keys());
|
|
406
|
+
for (let i = 0; i < entriesToRemove; i++) {
|
|
407
|
+
this.offsetCache.delete(keys[i]);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (this.dstCache.size > this.maxCacheSize / 2) {
|
|
412
|
+
const entriesToRemove = Math.floor(this.dstCache.size / 2);
|
|
413
|
+
const keys = Array.from(this.dstCache.keys());
|
|
414
|
+
for (let i = 0; i < entriesToRemove; i++) {
|
|
415
|
+
this.dstCache.delete(keys[i]);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|