• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

statuscompliance / status-backend / 19535672019

20 Nov 2025 11:41AM UTC coverage: 66.808% (-21.8%) from 88.591%
19535672019

Pull #246

github

web-flow
Merge c4a1dcf00 into b4c84c6d9
Pull Request #246: feat(databinder): add datasources persistence

1010 of 1684 branches covered (59.98%)

Branch coverage included in aggregate %.

124 of 808 new or added lines in 21 files covered. (15.35%)

2142 of 3034 relevant lines covered (70.6%)

22.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

14.08
/src/utils/databinder/linker/linkerCache.js
1
import redis from '../../../config/redis.js';
2
import logger from '../../../config/logger.js';
3

4
/**
5
 * Cache keys follow the pattern: linker:cache:{linkerId}
6
 * Metadata keys follow the pattern: linker:metadata:{linkerId}
7
 */
8

9
const CACHE_PREFIX = 'linker:cache:';
65✔
10
const METADATA_PREFIX = 'linker:metadata:';
65✔
11
const TWO_WEEKS_IN_SECONDS = 14 * 24 * 60 * 60; // 14 days
65✔
12
const ONE_HOUR_IN_SECONDS = 60 * 60; // 1 hour
65✔
13

14
/**
15
 * Stores linker execution results in cache with 2 weeks expiration
16
 * @param {string} linkerId - The linker ID
17
 * @param {object} data - The execution data to cache
18
 * @param {object} metadata - Metadata about the execution (timestamp, mergeStrategy, etc.)
19
 * @returns {Promise<boolean>} - Success status
20
 */
21
export const cacheLinkerExecution = async (linkerId, data, metadata = {}) => {
65!
NEW
22
  try {
×
NEW
23
    const cacheKey = `${CACHE_PREFIX}${linkerId}`;
×
NEW
24
    const metadataKey = `${METADATA_PREFIX}${linkerId}`;
×
25
    
NEW
26
    const cacheData = {
×
27
      data,
28
      cachedAt: Date.now(),
29
      linkerId
30
    };
31
    
NEW
32
    const metadataWithTimestamp = {
×
33
      ...metadata,
34
      lastExecutedAt: Date.now(),
35
      linkerId
36
    };
37

38
    // Store data with 2 weeks expiration
NEW
39
    await redis.setex(
×
40
      cacheKey, 
41
      TWO_WEEKS_IN_SECONDS, 
42
      JSON.stringify(cacheData)
43
    );
44
    
45
    // Store metadata with 2 weeks expiration
NEW
46
    await redis.setex(
×
47
      metadataKey,
48
      TWO_WEEKS_IN_SECONDS,
49
      JSON.stringify(metadataWithTimestamp)
50
    );
51

NEW
52
    logger.debug(`Cached linker execution for linker ${linkerId}`, {
×
53
      linkerId,
54
      expirationSeconds: TWO_WEEKS_IN_SECONDS,
55
      cachedAt: new Date(cacheData.cachedAt).toISOString()
56
    });
57

NEW
58
    return true;
×
59
  } catch (error) {
NEW
60
    logger.error(`Error caching linker execution for ${linkerId}:`, error);
×
NEW
61
    return false;
×
62
  }
63
};
64

65
/**
66
 * Retrieves cached linker execution data if available and not stale
67
 * @param {string} linkerId - The linker ID
68
 * @returns {Promise<{data: object|null, metadata: object|null, isStale: boolean}>}
69
 */
70
export const getCachedLinkerExecution = async (linkerId) => {
65✔
NEW
71
  try {
×
NEW
72
    const cacheKey = `${CACHE_PREFIX}${linkerId}`;
×
NEW
73
    const metadataKey = `${METADATA_PREFIX}${linkerId}`;
×
74
    
NEW
75
    const [cachedData, cachedMetadata] = await Promise.all([
×
76
      redis.get(cacheKey),
77
      redis.get(metadataKey)
78
    ]);
79

NEW
80
    if (!cachedData || !cachedMetadata) {
×
NEW
81
      logger.debug(`No cached data found for linker ${linkerId}`);
×
NEW
82
      return { data: null, metadata: null, isStale: true };
×
83
    }
84

NEW
85
    const parsedData = JSON.parse(cachedData);
×
NEW
86
    const parsedMetadata = JSON.parse(cachedMetadata);
×
87
    
88
    // Check if cache is stale (more than 1 hour old)
NEW
89
    const cacheAge = Date.now() - parsedMetadata.lastExecutedAt;
×
NEW
90
    const isStale = cacheAge > (ONE_HOUR_IN_SECONDS * 1000);
×
91

NEW
92
    logger.debug(`Retrieved cached data for linker ${linkerId}`, {
×
93
      linkerId,
94
      cachedAt: new Date(parsedData.cachedAt).toISOString(),
95
      cacheAgeMs: cacheAge,
96
      isStale
97
    });
98

NEW
99
    return {
×
100
      data: parsedData.data,
101
      metadata: parsedMetadata,
102
      isStale,
103
      cacheAge: cacheAge
104
    };
105
  } catch (error) {
NEW
106
    logger.error(`Error retrieving cached linker execution for ${linkerId}:`, error);
×
NEW
107
    return { data: null, metadata: null, isStale: true };
×
108
  }
109
};
110

111
/**
112
 * Invalidates (deletes) cached linker execution data
113
 * @param {string} linkerId - The linker ID
114
 * @returns {Promise<boolean>} - Success status
115
 */
116
export const invalidateLinkerCache = async (linkerId) => {
65✔
NEW
117
  try {
×
NEW
118
    const cacheKey = `${CACHE_PREFIX}${linkerId}`;
×
NEW
119
    const metadataKey = `${METADATA_PREFIX}${linkerId}`;
×
120
    
NEW
121
    const deletedCount = await redis.del(cacheKey, metadataKey);
×
122

NEW
123
    logger.debug(`Invalidated cache for linker ${linkerId}`, {
×
124
      linkerId,
125
      keysDeleted: deletedCount
126
    });
127

NEW
128
    return deletedCount > 0;
×
129
  } catch (error) {
NEW
130
    logger.error(`Error invalidating cache for linker ${linkerId}:`, error);
×
NEW
131
    return false;
×
132
  }
133
};
134

135
/**
136
 * Updates the TTL (expiration) of cached linker data
137
 * @param {string} linkerId - The linker ID
138
 * @param {number} seconds - New TTL in seconds
139
 * @returns {Promise<boolean>} - Success status
140
 */
141
export const updateLinkerCacheTTL = async (linkerId, seconds = TWO_WEEKS_IN_SECONDS) => {
65!
NEW
142
  try {
×
NEW
143
    const cacheKey = `${CACHE_PREFIX}${linkerId}`;
×
NEW
144
    const metadataKey = `${METADATA_PREFIX}${linkerId}`;
×
145
    
NEW
146
    await Promise.all([
×
147
      redis.expire(cacheKey, seconds),
148
      redis.expire(metadataKey, seconds)
149
    ]);
150

NEW
151
    logger.debug(`Updated TTL for linker ${linkerId}`, {
×
152
      linkerId,
153
      newTTLSeconds: seconds
154
    });
155

NEW
156
    return true;
×
157
  } catch (error) {
NEW
158
    logger.error(`Error updating TTL for linker ${linkerId}:`, error);
×
NEW
159
    return false;
×
160
  }
161
};
162

163
/**
164
 * Gets the remaining TTL for cached linker data
165
 * @param {string} linkerId - The linker ID
166
 * @returns {Promise<number>} - TTL in seconds, -1 if no expiration, -2 if key doesn't exist
167
 */
168
export const getLinkerCacheTTL = async (linkerId) => {
65✔
NEW
169
  try {
×
NEW
170
    const cacheKey = `${CACHE_PREFIX}${linkerId}`;
×
NEW
171
    const ttl = await redis.ttl(cacheKey);
×
172
    
NEW
173
    return ttl;
×
174
  } catch (error) {
NEW
175
    logger.error(`Error getting TTL for linker ${linkerId}:`, error);
×
NEW
176
    return -2;
×
177
  }
178
};
179

180
/**
181
 * Checks if a linker has valid (non-stale) cached data
182
 * @param {string} linkerId - The linker ID
183
 * @returns {Promise<boolean>} - True if valid cache exists
184
 */
185
export const hasValidCache = async (linkerId) => {
65✔
NEW
186
  try {
×
NEW
187
    const { data, isStale } = await getCachedLinkerExecution(linkerId);
×
NEW
188
    return data !== null && !isStale;
×
189
  } catch (error) {
NEW
190
    logger.error(`Error checking valid cache for linker ${linkerId}:`, error);
×
NEW
191
    return false;
×
192
  }
193
};
194

195
export default {
196
  cacheLinkerExecution,
197
  getCachedLinkerExecution,
198
  invalidateLinkerCache,
199
  updateLinkerCacheTTL,
200
  getLinkerCacheTTL,
201
  hasValidCache
202
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc