bulltrackers-module 1.0.786 → 1.0.787

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.
@@ -251,6 +251,43 @@ class StateRepository {
251
251
 
252
252
  await batch.commit();
253
253
  }
254
+
255
+ async findZombies(thresholdMinutes) {
256
+ const thresholdDate = new Date(Date.now() - (thresholdMinutes * 60000));
257
+ try {
258
+ // NOTE: Requires Composite Index on (status, lastUpdated)
259
+ const snapshot = await this.firestore.collection(this.collections.checkpoints)
260
+ .where('status', '==', 'running')
261
+ .where('lastUpdated', '<', thresholdDate)
262
+ .limit(50)
263
+ .get();
264
+
265
+ const zombies = [];
266
+ snapshot.forEach(doc => {
267
+ const data = doc.data();
268
+ zombies.push({
269
+ checkpointId: doc.id,
270
+ name: data.computationName,
271
+ date: data.date,
272
+ attempts: data.attempts || 0
273
+ });
274
+ });
275
+ return zombies;
276
+ } catch (e) {
277
+ this.logger.error(`[StateRepo] findZombies failed: ${e.message}`);
278
+ return [];
279
+ }
280
+ }
281
+
282
+ async claimZombie(checkpointId) {
283
+ const docRef = this.firestore.collection(this.collections.checkpoints).doc(checkpointId);
284
+ await docRef.set({
285
+ status: 'recovering',
286
+ lastUpdated: new Date(),
287
+ // Uses atomic increment to track retry count
288
+ attempts: FieldValue.increment(1)
289
+ }, { merge: true });
290
+ }
254
291
  }
255
292
 
256
293
  module.exports = { StateRepository };
@@ -148,7 +148,7 @@ class QueryBuilder {
148
148
  this._validateIdentifier(entityField);
149
149
  conditions.push(`${entityField} IN UNNEST(@entities)`);
150
150
 
151
- // Type safety for entity list
151
+ // [FIX] Ensure _isNumericType exists before calling
152
152
  const entityColumn = schema.columns.find(c => c.name === entityField);
153
153
  if (entityColumn && this._isNumericType(entityColumn.type)) {
154
154
  params.entities = entities.map(e => parseInt(e, 10));
@@ -157,12 +157,10 @@ class QueryBuilder {
157
157
  }
158
158
  }
159
159
 
160
- // 4. Additional WHERE conditions (The Vulnerable Spot)
160
+ // 4. Additional WHERE conditions
161
161
  let whereIndex = 0;
162
162
  for (const [column, value] of Object.entries(where)) {
163
- // 🔒 SECURITY FIX: Validate identifier before injection
164
163
  this._validateIdentifier(column);
165
-
166
164
  const paramName = `where_${whereIndex++}`;
167
165
 
168
166
  if (Array.isArray(value)) {
@@ -188,7 +186,6 @@ class QueryBuilder {
188
186
  let orderClause = '';
189
187
  if (orderBy) {
190
188
  this._validateIdentifier(orderBy);
191
- // Validate order direction strictly
192
189
  const safeDir = ['ASC', 'DESC'].includes(orderDir.toUpperCase()) ? orderDir.toUpperCase() : 'DESC';
193
190
  orderClause = `ORDER BY ${orderBy} ${safeDir}`;
194
191
  }
@@ -207,15 +204,18 @@ class QueryBuilder {
207
204
  return { sql, params, table, spec };
208
205
  }
209
206
 
210
- /**
211
- * Security helper to prevent SQL injection in identifiers.
212
- * Allows alphanumerics, underscores, and dots (for structs).
213
- */
214
207
  _validateIdentifier(id) {
215
208
  if (typeof id !== 'string' || !/^[a-zA-Z0-9_.]+$/.test(id)) {
216
209
  throw new Error(`[QueryBuilder] Security Violation: Invalid identifier format '${id}'`);
217
210
  }
218
211
  }
212
+
213
+ // [FIX] Added missing helper method
214
+ _isNumericType(type) {
215
+ if (!type) return false;
216
+ const t = type.toUpperCase();
217
+ return ['INT64', 'INTEGER', 'FLOAT64', 'FLOAT', 'NUMERIC', 'BIGNUMERIC'].includes(t);
218
+ }
219
219
  }
220
220
 
221
- module.exports = { QueryBuilder };
221
+ module.exports = { QueryBuilder };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.786",
3
+ "version": "1.0.787",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [