bulltrackers-module 1.0.611 → 1.0.612

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.
@@ -15,6 +15,7 @@ const ContractValidator = require('./ContractValidator');
15
15
  const validationOverrides = require('../config/validation_overrides');
16
16
  const pLimit = require('p-limit');
17
17
  const zlib = require('zlib');
18
+ const { commitBatchInChunks, generateDataHash, FieldValue } = require('../utils/utils');
18
19
 
19
20
  const NON_RETRYABLE_ERRORS = [ 'PERMISSION_DENIED', 'DATA_LOSS', 'FAILED_PRECONDITION' ];
20
21
  const SIMHASH_REGISTRY_COLLECTION = 'system_simhash_registry';
@@ -137,6 +138,8 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
137
138
  continue;
138
139
  }
139
140
 
141
+ // [NEW] Page Computation Logic (Fan-Out) with TTL
142
+ // Bypasses standard compression/sharding to write per-user documents
140
143
  // [NEW] Page Computation Logic (Fan-Out) with TTL
141
144
  // Bypasses standard compression/sharding to write per-user documents
142
145
  if (isPageComputation && !isEmpty) {
@@ -145,26 +148,27 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
145
148
  .collection(config.computationsSubcollection).doc(name);
146
149
 
147
150
  // --- CLEANUP START: Remove old storage formats (Sharded/Compressed) ---
148
- // If the computation previously ran in Standard mode, we must remove shards
149
- // and clear the main document data to avoid conflicting flags.
150
- try {
151
- const docSnap = await mainDocRef.get();
152
- if (docSnap.exists) {
153
- const dData = docSnap.data();
154
- if (dData._sharded) {
155
- const shardCol = mainDocRef.collection('_shards');
156
- const shardDocs = await withRetry(() => shardCol.listDocuments());
157
-
158
- if (shardDocs.length > 0) {
159
- const cleanupOps = shardDocs.map(d => ({ type: 'DELETE', ref: d }));
160
- await commitBatchInChunks(config, deps, cleanupOps, `${name}::PageModeCleanup`);
161
- runMetrics.io.deletes += cleanupOps.length;
162
- logger.log('INFO', `[PageMode] ${name}: Cleaned up ${cleanupOps.length} old shard documents.`);
151
+ // Optimization: Only attempt cleanup on the initial write to save reads
152
+ if (isInitialWrite) {
153
+ try {
154
+ const docSnap = await mainDocRef.get();
155
+ if (docSnap.exists) {
156
+ const dData = docSnap.data();
157
+ if (dData._sharded) {
158
+ const shardCol = mainDocRef.collection('_shards');
159
+ const shardDocs = await withRetry(() => shardCol.listDocuments());
160
+
161
+ if (shardDocs.length > 0) {
162
+ const cleanupOps = shardDocs.map(d => ({ type: 'DELETE', ref: d }));
163
+ await commitBatchInChunks(config, deps, cleanupOps, `${name}::PageModeCleanup`);
164
+ runMetrics.io.deletes += cleanupOps.length;
165
+ logger.log('INFO', `[PageMode] ${name}: Cleaned up ${cleanupOps.length} old shard documents.`);
166
+ }
163
167
  }
164
168
  }
169
+ } catch (cleanupErr) {
170
+ logger.log('WARN', `[PageMode] ${name}: Cleanup warning: ${cleanupErr.message}`);
165
171
  }
166
- } catch (cleanupErr) {
167
- logger.log('WARN', `[PageMode] ${name}: Cleanup warning: ${cleanupErr.message}`);
168
172
  }
169
173
  // --- CLEANUP END ---
170
174
 
@@ -198,31 +202,47 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
198
202
  logger.log('INFO', `[PageMode] ${name}: Wrote ${pageWrites.length} user pages. TTL: ${ttlDays}d.`);
199
203
  }
200
204
 
201
- // 3. Write the "Header" document (Important for Status/Metrics/TTL)
202
- if (flushMode !== 'INTERMEDIATE') {
203
- const headerData = {
204
- _completed: true,
205
- _isPageMode: true, // Flag for readers to know where to look
206
- _pageCount: pageWrites.length,
207
- _lastUpdated: new Date().toISOString(),
208
- _expireAt: expireAt // Ensure the header also gets deleted
209
- };
210
-
211
- // CHANGED: Use merge: false to FORCE overwrite.
212
- // This clears old "payload" (compressed), raw data keys, and old flags like "_sharded".
213
- await mainDocRef.set(headerData, { merge: false });
214
- runMetrics.io.writes += 1;
215
-
216
- if (calc.manifest.hash) {
217
- successUpdates[name] = {
218
- hash: calc.manifest.hash,
219
- simHash: simHash,
220
- resultHash: resultHash,
221
- category: calc.manifest.category,
222
- composition: calc.manifest.composition,
223
- metrics: runMetrics
224
- };
225
- }
205
+ // 3. Write or Update the "Header" document
206
+ // FIXED: Now runs on every batch to ensure counts are accumulated correctly.
207
+
208
+ const isFinalFlush = (flushMode !== 'INTERMEDIATE');
209
+
210
+ // Determine Page Count Value: Raw number for initial, Increment for updates
211
+ let pageCountValue = pageWrites.length;
212
+ if (!isInitialWrite) {
213
+ pageCountValue = FieldValue.increment(pageWrites.length);
214
+ }
215
+
216
+ const headerData = {
217
+ _isPageMode: true, // Flag for readers to know where to look
218
+ _pageCount: pageCountValue,
219
+ _lastUpdated: new Date().toISOString(),
220
+ _expireAt: expireAt // Ensure the header also gets deleted
221
+ };
222
+
223
+ // Handle Completion Status
224
+ if (isFinalFlush) {
225
+ headerData._completed = true;
226
+ } else if (isInitialWrite) {
227
+ headerData._completed = false; // Initialize as incomplete
228
+ }
229
+
230
+ // Write Strategy:
231
+ // isInitialWrite = TRUE -> merge: false (Wipes old Standard Mode data/schema)
232
+ // isInitialWrite = FALSE -> merge: true (Updates count and status, preserves data)
233
+ await mainDocRef.set(headerData, { merge: !isInitialWrite });
234
+
235
+ runMetrics.io.writes += 1;
236
+
237
+ if (isFinalFlush && calc.manifest.hash) {
238
+ successUpdates[name] = {
239
+ hash: calc.manifest.hash,
240
+ simHash: simHash,
241
+ resultHash: resultHash,
242
+ category: calc.manifest.category,
243
+ composition: calc.manifest.composition,
244
+ metrics: runMetrics
245
+ };
226
246
  }
227
247
 
228
248
  continue; // Skip the standard writeSingleResult logic
@@ -6,6 +6,7 @@
6
6
  const { FieldValue, FieldPath } = require('@google-cloud/firestore');
7
7
  const crypto = require('crypto');
8
8
 
9
+
9
10
  // [UPDATED] Registry for Data Availability.
10
11
  // Populated dynamically by getEarliestDataDates().
11
12
  const DEFINITIVE_EARLIEST_DATES = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.611",
3
+ "version": "1.0.612",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [