···
16
+
* Analytics data type definitions
18
+
interface EndpointMetrics {
21
+
averageResponseTime: number;
24
+
interface StatusMetrics {
27
+
averageResponseTime: number;
30
+
interface DayMetrics {
33
+
averageResponseTime: number;
36
+
interface UserAgentMetrics {
41
+
interface LatencyPercentiles {
49
+
interface LatencyDistribution {
55
+
interface LatencyOverTimeMetrics {
57
+
averageResponseTime: number;
62
+
interface LatencyAnalytics {
63
+
percentiles: LatencyPercentiles;
64
+
distribution: Array<LatencyDistribution>;
65
+
slowestEndpoints: Array<EndpointMetrics>;
66
+
latencyOverTime: Array<LatencyOverTimeMetrics>;
69
+
interface PerformanceMetrics {
74
+
cacheHitRate: number;
77
+
interface PeakTraffic {
79
+
peakRequests: number;
81
+
peakDayRequests: number;
84
+
interface DashboardMetrics {
85
+
statsRequests: number;
86
+
totalWithStats: number;
89
+
interface TrafficOverview {
91
+
routes: Record<string, number>;
96
+
* Analytics method return types
98
+
interface FullAnalyticsData {
99
+
totalRequests: number;
100
+
requestsByEndpoint: Array<EndpointMetrics>;
101
+
requestsByStatus: Array<StatusMetrics>;
102
+
requestsByDay: Array<DayMetrics>;
103
+
averageResponseTime: number | null;
104
+
topUserAgents: Array<UserAgentMetrics>;
105
+
latencyAnalytics: LatencyAnalytics;
106
+
performanceMetrics: PerformanceMetrics;
107
+
peakTraffic: PeakTraffic;
108
+
dashboardMetrics: DashboardMetrics;
109
+
trafficOverview: Array<TrafficOverview>;
112
+
interface EssentialStatsData {
113
+
totalRequests: number;
114
+
averageResponseTime: number | null;
118
+
interface ChartData {
119
+
requestsByDay: Array<DayMetrics>;
120
+
latencyOverTime: Array<LatencyOverTimeMetrics>;
123
+
type UserAgentData = Array<UserAgentMetrics>;
126
+
* Discriminated union for all analytics cache data types
128
+
type AnalyticsCacheData =
129
+
| { type: 'analytics'; data: FullAnalyticsData }
130
+
| { type: 'essential'; data: EssentialStatsData }
131
+
| { type: 'charts'; data: ChartData }
132
+
| { type: 'useragents'; data: UserAgentData };
135
+
* Type-safe analytics cache entry
137
+
interface AnalyticsCacheEntry {
138
+
data: AnalyticsCacheData;
143
+
* Type guard functions for cache data
145
+
function isAnalyticsData(data: AnalyticsCacheData): data is { type: 'analytics'; data: FullAnalyticsData } {
146
+
return data.type === 'analytics';
149
+
function isEssentialStatsData(data: AnalyticsCacheData): data is { type: 'essential'; data: EssentialStatsData } {
150
+
return data.type === 'essential';
153
+
function isChartData(data: AnalyticsCacheData): data is { type: 'charts'; data: ChartData } {
154
+
return data.type === 'charts';
157
+
function isUserAgentData(data: AnalyticsCacheData): data is { type: 'useragents'; data: UserAgentData } {
158
+
return data.type === 'useragents';
162
+
* Type-safe cache helper methods
164
+
class AnalyticsCache {
165
+
private cache: Map<string, AnalyticsCacheEntry>;
166
+
private cacheTTL: number;
167
+
private maxCacheSize: number;
169
+
constructor(cacheTTL: number = 30000, maxCacheSize: number = 10) {
170
+
this.cache = new Map();
171
+
this.cacheTTL = cacheTTL;
172
+
this.maxCacheSize = maxCacheSize;
176
+
* Get cached analytics data with type safety
178
+
getAnalyticsData(key: string): FullAnalyticsData | null {
179
+
const cached = this.cache.get(key);
180
+
const now = Date.now();
182
+
if (cached && now - cached.timestamp < this.cacheTTL && isAnalyticsData(cached.data)) {
183
+
return cached.data.data;
189
+
* Get cached essential stats data with type safety
191
+
getEssentialStatsData(key: string): EssentialStatsData | null {
192
+
const cached = this.cache.get(key);
193
+
const now = Date.now();
195
+
if (cached && now - cached.timestamp < this.cacheTTL && isEssentialStatsData(cached.data)) {
196
+
return cached.data.data;
202
+
* Get cached chart data with type safety
204
+
getChartData(key: string): ChartData | null {
205
+
const cached = this.cache.get(key);
206
+
const now = Date.now();
208
+
if (cached && now - cached.timestamp < this.cacheTTL && isChartData(cached.data)) {
209
+
return cached.data.data;
215
+
* Get cached user agent data with type safety
217
+
getUserAgentData(key: string): UserAgentData | null {
218
+
const cached = this.cache.get(key);
219
+
const now = Date.now();
221
+
if (cached && now - cached.timestamp < this.cacheTTL && isUserAgentData(cached.data)) {
222
+
return cached.data.data;
228
+
* Set analytics data in cache with type safety
230
+
setAnalyticsData(key: string, data: FullAnalyticsData): void {
231
+
this.setCacheEntry(key, { type: 'analytics', data });
235
+
* Set essential stats data in cache with type safety
237
+
setEssentialStatsData(key: string, data: EssentialStatsData): void {
238
+
this.setCacheEntry(key, { type: 'essential', data });
242
+
* Set chart data in cache with type safety
244
+
setChartData(key: string, data: ChartData): void {
245
+
this.setCacheEntry(key, { type: 'charts', data });
249
+
* Set user agent data in cache with type safety
251
+
setUserAgentData(key: string, data: UserAgentData): void {
252
+
this.setCacheEntry(key, { type: 'useragents', data });
256
+
* Internal method to set cache entry and manage cache size
258
+
private setCacheEntry(key: string, data: AnalyticsCacheData): void {
259
+
this.cache.set(key, {
261
+
timestamp: Date.now(),
264
+
// Clean up old cache entries
265
+
if (this.cache.size > this.maxCacheSize) {
266
+
const keys = Array.from(this.cache.keys());
267
+
const oldestKey = keys[0];
269
+
this.cache.delete(oldestKey);
* @fileoverview This file contains the Cache class for storing user and emoji data with automatic expiration. To use the module in your project, import the default export and create a new instance of the Cache class. The class provides methods for inserting and retrieving user and emoji data from the cache. The cache automatically purges expired items every hour.
···
private defaultExpiration: number; // in hours
private onEmojiExpired?: () => void;
57
-
private analyticsCache: Map<
61
-
totalRequests: number;
62
-
requestsByEndpoint: Array<{
65
-
averageResponseTime: number;
67
-
requestsByStatus: Array<{
70
-
averageResponseTime: number;
72
-
requestsByDay: Array<{
75
-
averageResponseTime: number;
77
-
averageResponseTime: number | null;
78
-
topUserAgents: Array<{ userAgent: string; count: number }>;
87
-
distribution: Array<{
92
-
slowestEndpoints: Array<{
94
-
averageResponseTime: number;
97
-
latencyOverTime: Array<{
99
-
averageResponseTime: number;
100
-
p95: number | null;
104
-
performanceMetrics: {
107
-
throughput: number;
109
-
cachehitRate: number;
113
-
peakRequests: number;
115
-
peakDayRequests: number;
117
-
dashboardMetrics: {
118
-
statsRequests: number;
119
-
totalWithStats: number;
121
-
trafficOverview: Array<{
123
-
routes: Record<string, number>;
130
-
private analyticsCacheTTL = 30000; // 30 second cache for faster updates
317
+
private typedAnalyticsCache: AnalyticsCache; // Type-safe analytics cache helper
// Background user update queue to avoid Slack API limits
private userUpdateQueue: Set<string> = new Set();
···
this.db = new Database(dbPath);
this.defaultExpiration = defaultExpirationHours;
this.onEmojiExpired = onEmojiExpired;
339
+
// Initialize type-safe analytics cache
340
+
this.typedAnalyticsCache = new AnalyticsCache();
this.setupPurgeSchedule();
···
822
-
cachehitRate: number;
1012
+
cacheHitRate: number;
···
const cacheKey = `analytics_${days}`;
842
-
const cached = this.analyticsCache.get(cacheKey);
843
-
const now = Date.now();
1032
+
const cached = this.typedAnalyticsCache.getAnalyticsData(cacheKey);
845
-
if (cached && now - cached.timestamp < this.analyticsCacheTTL) {
846
-
return cached.data;
const cutoffTime = Date.now() - days * 24 * 60 * 60 * 1000;
···
const dataRequests = requestsByEndpoint
.filter((e) => e.endpoint === "User Data" || e.endpoint === "Emoji Data")
.reduce((sum, e) => sum + e.count, 0);
1341
-
const cachehitRate =
1530
+
const cacheHitRate =
redirectRequests + dataRequests > 0
? (redirectRequests / (redirectRequests + dataRequests)) * 100
···
peakHour: peakHourData?.hour || "N/A",
···
1668
-
this.analyticsCache.set(cacheKey, {
1673
-
// Clean up old cache entries (keep only last 5)
1674
-
if (this.analyticsCache.size > 5) {
1675
-
const keys = Array.from(this.analyticsCache.keys());
1676
-
const oldestKey = keys[0];
1678
-
this.analyticsCache.delete(oldestKey);
1857
+
this.typedAnalyticsCache.setAnalyticsData(cacheKey, result);
···
const cacheKey = `essential_${days}`;
1697
-
const cached = this.analyticsCache.get(cacheKey);
1698
-
const now = Date.now();
1874
+
const cached = this.typedAnalyticsCache.getEssentialStatsData(cacheKey);
1700
-
if (cached && now - cached.timestamp < this.analyticsCacheTTL) {
1701
-
return cached.data;
const cutoffTime = Date.now() - days * 24 * 60 * 60 * 1000;
···
1740
-
this.analyticsCache.set(cacheKey, {
1916
+
this.typedAnalyticsCache.setEssentialStatsData(cacheKey, result);
···
const cacheKey = `charts_${days}`;
1768
-
const cached = this.analyticsCache.get(cacheKey);
1769
-
const now = Date.now();
1941
+
const cached = this.typedAnalyticsCache.getChartData(cacheKey);
1771
-
if (cached && now - cached.timestamp < this.analyticsCacheTTL) {
1772
-
return cached.data;
const cutoffTime = Date.now() - days * 24 * 60 * 60 * 1000;
···
2014
-
this.analyticsCache.set(cacheKey, {
2186
+
this.typedAnalyticsCache.setChartData(cacheKey, result);
···
): Promise<Array<{ userAgent: string; count: number }>> {
const cacheKey = `useragents_${days}`;
2032
-
const cached = this.analyticsCache.get(cacheKey);
2033
-
const now = Date.now();
2201
+
const cached = this.typedAnalyticsCache.getUserAgentData(cacheKey);
2035
-
if (cached && now - cached.timestamp < this.analyticsCacheTTL) {
2036
-
return cached.data;
const cutoffTime = Date.now() - days * 24 * 60 * 60 * 1000;
···
.all(cutoffTime) as Array<{ userAgent: string; count: number }>;
2056
-
this.analyticsCache.set(cacheKey, {
2057
-
data: topUserAgents,
2224
+
this.typedAnalyticsCache.setUserAgentData(cacheKey, topUserAgents);