bulltrackers-module 1.0.105 → 1.0.107

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.
Files changed (33) hide show
  1. package/README.MD +222 -222
  2. package/functions/appscript-api/helpers/errors.js +19 -19
  3. package/functions/appscript-api/index.js +58 -58
  4. package/functions/computation-system/helpers/orchestration_helpers.js +667 -113
  5. package/functions/computation-system/utils/data_loader.js +191 -191
  6. package/functions/computation-system/utils/utils.js +149 -254
  7. package/functions/core/utils/firestore_utils.js +433 -433
  8. package/functions/core/utils/pubsub_utils.js +53 -53
  9. package/functions/dispatcher/helpers/dispatch_helpers.js +47 -47
  10. package/functions/dispatcher/index.js +52 -52
  11. package/functions/etoro-price-fetcher/helpers/handler_helpers.js +124 -124
  12. package/functions/fetch-insights/helpers/handler_helpers.js +91 -91
  13. package/functions/generic-api/helpers/api_helpers.js +379 -379
  14. package/functions/generic-api/index.js +150 -150
  15. package/functions/invalid-speculator-handler/helpers/handler_helpers.js +75 -75
  16. package/functions/orchestrator/helpers/discovery_helpers.js +226 -226
  17. package/functions/orchestrator/helpers/update_helpers.js +92 -92
  18. package/functions/orchestrator/index.js +147 -147
  19. package/functions/price-backfill/helpers/handler_helpers.js +116 -123
  20. package/functions/social-orchestrator/helpers/orchestrator_helpers.js +61 -61
  21. package/functions/social-task-handler/helpers/handler_helpers.js +288 -288
  22. package/functions/task-engine/handler_creator.js +78 -78
  23. package/functions/task-engine/helpers/discover_helpers.js +125 -125
  24. package/functions/task-engine/helpers/update_helpers.js +118 -118
  25. package/functions/task-engine/helpers/verify_helpers.js +162 -162
  26. package/functions/task-engine/utils/firestore_batch_manager.js +258 -258
  27. package/index.js +105 -113
  28. package/package.json +45 -45
  29. package/functions/computation-system/computation_dependencies.json +0 -120
  30. package/functions/computation-system/helpers/worker_helpers.js +0 -340
  31. package/functions/computation-system/utils/computation_state_manager.js +0 -178
  32. package/functions/computation-system/utils/dependency_graph.js +0 -191
  33. package/functions/speculator-cleanup-orchestrator/helpers/cleanup_helpers.js +0 -160
@@ -1,259 +1,259 @@
1
- /**
2
- * @fileoverview Utility class to manage stateful Firestore write batches.
3
- * REFACTORED: Renamed 'firestore' to 'db' for consistency.
4
- * OPTIMIZED: Added logic to handle speculator timestamp fixes within the batch.
5
- */
6
-
7
- const { FieldValue } = require('@google-cloud/firestore');
8
- // No longer requires logger from sharedsetup, it's passed in.
9
-
10
- class FirestoreBatchManager {
11
- /**
12
- * @param {Firestore} db - A Firestore instance.
13
- * @param {IntelligentHeaderManager} headerManager - An IntelligentHeaderManager instance.
14
- * @param {object} logger - A logger instance.
15
- * @param {object} config - Configuration object.
16
- */
17
- constructor(db, headerManager, logger, config) {
18
- this.db = db; // Renamed from firestore
19
- this.headerManager = headerManager;
20
- this.logger = logger; // Added logger
21
- this.config = config;
22
-
23
- this.portfolioBatch = {};
24
- this.timestampBatch = {};
25
- this.processedSpeculatorCids = new Set();
26
- this.speculatorTimestampFixBatch = {}; // <-- OPTIMIZATION: ADD THIS
27
- this.batchTimeout = null;
28
-
29
- this.logger.log('INFO', 'FirestoreBatchManager initialized.');
30
- }
31
-
32
- /**
33
- * Schedules a flush if one isn't already scheduled.
34
- */
35
- _scheduleFlush() {
36
- if (!this.batchTimeout) {
37
- this.batchTimeout = setTimeout(
38
- () => this.flushBatches(),
39
- this.config.TASK_ENGINE_FLUSH_INTERVAL_MS
40
- );
41
- }
42
- }
43
-
44
- /**
45
- * Adds a portfolio to the batch.
46
- * @param {string} userId
47
- * @param {string} blockId
48
- * @param {string} date
49
- * @param {object} portfolioData
50
- * @param {string} userType
51
- * @param {string|null} instrumentId
52
- */
53
- async addToPortfolioBatch(userId, blockId, date, portfolioData, userType, instrumentId = null) {
54
- const collection = userType === 'speculator'
55
- ? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
56
- : this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
57
- const basePath = `${collection}/${blockId}/snapshots/${date}`;
58
-
59
- if (!this.portfolioBatch[basePath]) {
60
- this.portfolioBatch[basePath] = {};
61
- }
62
-
63
- this.portfolioBatch[basePath][userId] = portfolioData;
64
-
65
- const totalUsersInBatch = Object.values(this.portfolioBatch).reduce((sum, users) => sum + Object.keys(users).length, 0);
66
-
67
- if (totalUsersInBatch >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
68
- await this.flushBatches();
69
- } else {
70
- this._scheduleFlush();
71
- }
72
- }
73
-
74
- /**
75
- * Adds a user timestamp update to the batch.
76
- * @param {string} userId
77
- * @param {string} userType
78
- * @param {string|null} instrumentId
79
- */
80
- async updateUserTimestamp(userId, userType, instrumentId = null) {
81
- const collection = userType === 'speculator'
82
- ? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
83
- : this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
84
- const docId = userType === 'speculator' ? 'speculators' : 'normal';
85
- const docPath = `${collection}/${docId}`;
86
-
87
- if (!this.timestampBatch[docPath]) {
88
- this.timestampBatch[docPath] = {};
89
- }
90
-
91
- const timestampKey = userType === 'speculator' ? `${userId}_${instrumentId}` : userId;
92
- this.timestampBatch[docPath][timestampKey] = new Date();
93
-
94
- if (Object.keys(this.timestampBatch[docPath]).length >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
95
- await this.flushBatches();
96
- } else {
97
- this._scheduleFlush();
98
- }
99
- }
100
-
101
- /**
102
- * Removes a user timestamp from the batch (e.g., if user is private).
103
- * @param {string} userId
104
- * @param {string} userType
105
- * @param {string|null} instrumentId
106
- */
107
- deleteFromTimestampBatch(userId, userType, instrumentId) {
108
- const collection = userType === 'speculator'
109
- ? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
110
- : this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
111
- const docId = userType === 'speculator' ? 'speculators' : 'normal';
112
- const docPath = `${collection}/${docId}`;
113
-
114
- if (this.timestampBatch[docPath]) {
115
- const timestampKey = userType === 'speculator' ? `${userId}_${instrumentId}` : userId;
116
- delete this.timestampBatch[docPath][timestampKey];
117
- }
118
- }
119
-
120
- /**
121
- * Adds discovered speculator CIDs to the in-memory set for later deletion.
122
- * @param {Array<string>} cids
123
- */
124
- addProcessedSpeculatorCids(cids) {
125
- cids.forEach(cid => this.processedSpeculatorCids.add(cid));
126
- }
127
-
128
- /**
129
- * --- OPTIMIZATION: ADD THIS NEW METHOD ---
130
- * Adds a speculator timestamp fix to the batch.
131
- * @param {string} userId
132
- * @param {string} orchestratorBlockId (e.g., "1000000")
133
- */
134
- async addSpeculatorTimestampFix(userId, orchestratorBlockId) {
135
- const docPath = `${this.config.FIRESTORE_COLLECTION_SPECULATOR_BLOCKS}/${orchestratorBlockId}`;
136
-
137
- if (!this.speculatorTimestampFixBatch[docPath]) {
138
- this.speculatorTimestampFixBatch[docPath] = {};
139
- }
140
-
141
- // These are the two fields the orchestrator reads
142
- this.speculatorTimestampFixBatch[docPath][`users.${userId}.lastVerified`] = new Date();
143
- this.speculatorTimestampFixBatch[docPath][`users.${userId}.lastHeldSpeculatorAsset`] = new Date();
144
-
145
- this.logger.log('TRACE', `[BATCH] Queued speculator timestamp fix for user ${userId} in block ${orchestratorBlockId}`);
146
- this._scheduleFlush(); // Schedule a flush
147
- }
148
- // --- END NEW METHOD ---
149
-
150
- /**
151
- * Flushes all pending writes to Firestore and updates header performance.
152
- */
153
- async flushBatches() {
154
- if (this.batchTimeout) {
155
- clearTimeout(this.batchTimeout);
156
- this.batchTimeout = null;
157
- }
158
-
159
- const promises = [];
160
- const firestoreBatch = this.db.batch(); // Use this.db
161
- let batchOperationCount = 0;
162
-
163
- for (const basePath in this.portfolioBatch) {
164
- const userPortfolios = this.portfolioBatch[basePath];
165
- const userIds = Object.keys(userPortfolios);
166
-
167
- if (userIds.length > 0) {
168
- for (let i = 0; i < userIds.length; i += this.config.TASK_ENGINE_MAX_USERS_PER_SHARD) {
169
- const chunkUserIds = userIds.slice(i, i + this.config.TASK_ENGINE_MAX_USERS_PER_SHARD);
170
- const shardIndex = Math.floor(i / this.config.TASK_ENGINE_MAX_USERS_PER_SHARD);
171
-
172
- const chunkData = {};
173
- chunkUserIds.forEach(userId => {
174
- chunkData[userId] = userPortfolios[userId];
175
- });
176
-
177
- const docRef = this.db.collection(`${basePath}/parts`).doc(); // Now uses a reandomised ID from firestore to solve an overwrite issue with contention
178
- firestoreBatch.set(docRef, chunkData); // No merge: true needed, it's a new doc
179
- batchOperationCount++;
180
- }
181
- // (Optional) Update the log message to reflect the path used for the data write
182
- this.logger.log('INFO', `[BATCH] Staged ${userIds.length} users into ${Math.ceil(userIds.length / this.config.TASK_ENGINE_MAX_USERS_PER_SHARD)} unique shard documents for ${basePath}.`);
183
- }
184
- delete this.portfolioBatch[basePath];
185
- }
186
-
187
- for (const docPath in this.timestampBatch) {
188
- if (Object.keys(this.timestampBatch[docPath]).length > 0) {
189
- const [collection] = docPath.split('/');
190
- const docRef = this.db.collection(collection).doc('timestamps').collection('users').doc('normal'); // Use this.db
191
-
192
- if (batchOperationCount < 450) {
193
- firestoreBatch.set(docRef, { users: this.timestampBatch[docPath] }, { merge: true });
194
- batchOperationCount++;
195
- } else {
196
- promises.push(firestoreBatch.commit());
197
- const newBatch = this.db.batch(); // Use this.db
198
- newBatch.set(docRef, { users: this.timestampBatch[docPath] }, { merge: true });
199
- promises.push(newBatch.commit());
200
- batchOperationCount = 1;
201
- }
202
- delete this.timestampBatch[docPath];
203
- }
204
- }
205
-
206
- if (this.processedSpeculatorCids.size > 0) {
207
- this.logger.log('INFO', `[BATCH] Flushing ${this.processedSpeculatorCids.size} processed speculator CIDs from pending list documents.`);
208
- const cidsToDelete = Array.from(this.processedSpeculatorCids);
209
- this.processedSpeculatorCids.clear();
210
-
211
- const pendingDocsSnapshot = await this.db.collection(this.config.PENDING_SPECULATORS_COLLECTION).get(); // Use this.db
212
-
213
- if (!pendingDocsSnapshot.empty) {
214
- const deletePromises = [];
215
- pendingDocsSnapshot.forEach(doc => {
216
- const docData = doc.data().users || {};
217
- const cidsInThisDoc = cidsToDelete.filter(cid => docData.hasOwnProperty(cid));
218
-
219
- if (cidsInThisDoc.length > 0) {
220
- const deleteBatch = this.db.batch(); // Use this.db
221
- const updates = {};
222
- cidsInThisDoc.forEach(cid => {
223
- updates[`users.${cid}`] = FieldValue.delete();
224
- });
225
- deleteBatch.update(doc.ref, updates);
226
- deletePromises.push(deleteBatch.commit());
227
- this.logger.log('INFO', `[BATCH] Staged deletion of ${cidsInThisDoc.length} CIDs from document: ${doc.id}`);
228
- }
229
- });
230
- promises.push(...deletePromises);
231
- }
232
- }
233
-
234
- // --- OPTIMIZATION: ADD THIS LOOP ---
235
- for (const docPath in this.speculatorTimestampFixBatch) {
236
- const updates = this.speculatorTimestampFixBatch[docPath];
237
- if (Object.keys(updates).length > 0) {
238
- const docRef = this.db.doc(docPath);
239
-
240
- // Add to the existing batch
241
- firestoreBatch.set(docRef, updates, { merge: true });
242
- batchOperationCount++;
243
- }
244
- delete this.speculatorTimestampFixBatch[docPath];
245
- }
246
- // --- END OPTIMIZATION ---
247
-
248
- if (batchOperationCount > 0) {
249
- promises.push(firestoreBatch.commit());
250
- }
251
-
252
- promises.push(this.headerManager.flushPerformanceUpdates());
253
-
254
- await Promise.all(promises);
255
- this.logger.log('INFO', '[BATCH] All batches flushed successfully.');
256
- }
257
- }
258
-
1
+ /**
2
+ * @fileoverview Utility class to manage stateful Firestore write batches.
3
+ * REFACTORED: Renamed 'firestore' to 'db' for consistency.
4
+ * OPTIMIZED: Added logic to handle speculator timestamp fixes within the batch.
5
+ */
6
+
7
+ const { FieldValue } = require('@google-cloud/firestore');
8
+ // No longer requires logger from sharedsetup, it's passed in.
9
+
10
+ class FirestoreBatchManager {
11
+ /**
12
+ * @param {Firestore} db - A Firestore instance.
13
+ * @param {IntelligentHeaderManager} headerManager - An IntelligentHeaderManager instance.
14
+ * @param {object} logger - A logger instance.
15
+ * @param {object} config - Configuration object.
16
+ */
17
+ constructor(db, headerManager, logger, config) {
18
+ this.db = db; // Renamed from firestore
19
+ this.headerManager = headerManager;
20
+ this.logger = logger; // Added logger
21
+ this.config = config;
22
+
23
+ this.portfolioBatch = {};
24
+ this.timestampBatch = {};
25
+ this.processedSpeculatorCids = new Set();
26
+ this.speculatorTimestampFixBatch = {}; // <-- OPTIMIZATION: ADD THIS
27
+ this.batchTimeout = null;
28
+
29
+ this.logger.log('INFO', 'FirestoreBatchManager initialized.');
30
+ }
31
+
32
+ /**
33
+ * Schedules a flush if one isn't already scheduled.
34
+ */
35
+ _scheduleFlush() {
36
+ if (!this.batchTimeout) {
37
+ this.batchTimeout = setTimeout(
38
+ () => this.flushBatches(),
39
+ this.config.TASK_ENGINE_FLUSH_INTERVAL_MS
40
+ );
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Adds a portfolio to the batch.
46
+ * @param {string} userId
47
+ * @param {string} blockId
48
+ * @param {string} date
49
+ * @param {object} portfolioData
50
+ * @param {string} userType
51
+ * @param {string|null} instrumentId
52
+ */
53
+ async addToPortfolioBatch(userId, blockId, date, portfolioData, userType, instrumentId = null) {
54
+ const collection = userType === 'speculator'
55
+ ? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
56
+ : this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
57
+ const basePath = `${collection}/${blockId}/snapshots/${date}`;
58
+
59
+ if (!this.portfolioBatch[basePath]) {
60
+ this.portfolioBatch[basePath] = {};
61
+ }
62
+
63
+ this.portfolioBatch[basePath][userId] = portfolioData;
64
+
65
+ const totalUsersInBatch = Object.values(this.portfolioBatch).reduce((sum, users) => sum + Object.keys(users).length, 0);
66
+
67
+ if (totalUsersInBatch >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
68
+ await this.flushBatches();
69
+ } else {
70
+ this._scheduleFlush();
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Adds a user timestamp update to the batch.
76
+ * @param {string} userId
77
+ * @param {string} userType
78
+ * @param {string|null} instrumentId
79
+ */
80
+ async updateUserTimestamp(userId, userType, instrumentId = null) {
81
+ const collection = userType === 'speculator'
82
+ ? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
83
+ : this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
84
+ const docId = userType === 'speculator' ? 'speculators' : 'normal';
85
+ const docPath = `${collection}/${docId}`;
86
+
87
+ if (!this.timestampBatch[docPath]) {
88
+ this.timestampBatch[docPath] = {};
89
+ }
90
+
91
+ const timestampKey = userType === 'speculator' ? `${userId}_${instrumentId}` : userId;
92
+ this.timestampBatch[docPath][timestampKey] = new Date();
93
+
94
+ if (Object.keys(this.timestampBatch[docPath]).length >= this.config.TASK_ENGINE_MAX_BATCH_SIZE) {
95
+ await this.flushBatches();
96
+ } else {
97
+ this._scheduleFlush();
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Removes a user timestamp from the batch (e.g., if user is private).
103
+ * @param {string} userId
104
+ * @param {string} userType
105
+ * @param {string|null} instrumentId
106
+ */
107
+ deleteFromTimestampBatch(userId, userType, instrumentId) {
108
+ const collection = userType === 'speculator'
109
+ ? this.config.FIRESTORE_COLLECTION_SPECULATOR_PORTFOLIOS
110
+ : this.config.FIRESTORE_COLLECTION_NORMAL_PORTFOLIOS;
111
+ const docId = userType === 'speculator' ? 'speculators' : 'normal';
112
+ const docPath = `${collection}/${docId}`;
113
+
114
+ if (this.timestampBatch[docPath]) {
115
+ const timestampKey = userType === 'speculator' ? `${userId}_${instrumentId}` : userId;
116
+ delete this.timestampBatch[docPath][timestampKey];
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Adds discovered speculator CIDs to the in-memory set for later deletion.
122
+ * @param {Array<string>} cids
123
+ */
124
+ addProcessedSpeculatorCids(cids) {
125
+ cids.forEach(cid => this.processedSpeculatorCids.add(cid));
126
+ }
127
+
128
+ /**
129
+ * --- OPTIMIZATION: ADD THIS NEW METHOD ---
130
+ * Adds a speculator timestamp fix to the batch.
131
+ * @param {string} userId
132
+ * @param {string} orchestratorBlockId (e.g., "1000000")
133
+ */
134
+ async addSpeculatorTimestampFix(userId, orchestratorBlockId) {
135
+ const docPath = `${this.config.FIRESTORE_COLLECTION_SPECULATOR_BLOCKS}/${orchestratorBlockId}`;
136
+
137
+ if (!this.speculatorTimestampFixBatch[docPath]) {
138
+ this.speculatorTimestampFixBatch[docPath] = {};
139
+ }
140
+
141
+ // These are the two fields the orchestrator reads
142
+ this.speculatorTimestampFixBatch[docPath][`users.${userId}.lastVerified`] = new Date();
143
+ this.speculatorTimestampFixBatch[docPath][`users.${userId}.lastHeldSpeculatorAsset`] = new Date();
144
+
145
+ this.logger.log('TRACE', `[BATCH] Queued speculator timestamp fix for user ${userId} in block ${orchestratorBlockId}`);
146
+ this._scheduleFlush(); // Schedule a flush
147
+ }
148
+ // --- END NEW METHOD ---
149
+
150
+ /**
151
+ * Flushes all pending writes to Firestore and updates header performance.
152
+ */
153
+ async flushBatches() {
154
+ if (this.batchTimeout) {
155
+ clearTimeout(this.batchTimeout);
156
+ this.batchTimeout = null;
157
+ }
158
+
159
+ const promises = [];
160
+ const firestoreBatch = this.db.batch(); // Use this.db
161
+ let batchOperationCount = 0;
162
+
163
+ for (const basePath in this.portfolioBatch) {
164
+ const userPortfolios = this.portfolioBatch[basePath];
165
+ const userIds = Object.keys(userPortfolios);
166
+
167
+ if (userIds.length > 0) {
168
+ for (let i = 0; i < userIds.length; i += this.config.TASK_ENGINE_MAX_USERS_PER_SHARD) {
169
+ const chunkUserIds = userIds.slice(i, i + this.config.TASK_ENGINE_MAX_USERS_PER_SHARD);
170
+ const shardIndex = Math.floor(i / this.config.TASK_ENGINE_MAX_USERS_PER_SHARD);
171
+
172
+ const chunkData = {};
173
+ chunkUserIds.forEach(userId => {
174
+ chunkData[userId] = userPortfolios[userId];
175
+ });
176
+
177
+ const docRef = this.db.collection(`${basePath}/parts`).doc(); // Now uses a reandomised ID from firestore to solve an overwrite issue with contention
178
+ firestoreBatch.set(docRef, chunkData); // No merge: true needed, it's a new doc
179
+ batchOperationCount++;
180
+ }
181
+ // (Optional) Update the log message to reflect the path used for the data write
182
+ this.logger.log('INFO', `[BATCH] Staged ${userIds.length} users into ${Math.ceil(userIds.length / this.config.TASK_ENGINE_MAX_USERS_PER_SHARD)} unique shard documents for ${basePath}.`);
183
+ }
184
+ delete this.portfolioBatch[basePath];
185
+ }
186
+
187
+ for (const docPath in this.timestampBatch) {
188
+ if (Object.keys(this.timestampBatch[docPath]).length > 0) {
189
+ const [collection] = docPath.split('/');
190
+ const docRef = this.db.collection(collection).doc('timestamps').collection('users').doc('normal'); // Use this.db
191
+
192
+ if (batchOperationCount < 450) {
193
+ firestoreBatch.set(docRef, { users: this.timestampBatch[docPath] }, { merge: true });
194
+ batchOperationCount++;
195
+ } else {
196
+ promises.push(firestoreBatch.commit());
197
+ const newBatch = this.db.batch(); // Use this.db
198
+ newBatch.set(docRef, { users: this.timestampBatch[docPath] }, { merge: true });
199
+ promises.push(newBatch.commit());
200
+ batchOperationCount = 1;
201
+ }
202
+ delete this.timestampBatch[docPath];
203
+ }
204
+ }
205
+
206
+ if (this.processedSpeculatorCids.size > 0) {
207
+ this.logger.log('INFO', `[BATCH] Flushing ${this.processedSpeculatorCids.size} processed speculator CIDs from pending list documents.`);
208
+ const cidsToDelete = Array.from(this.processedSpeculatorCids);
209
+ this.processedSpeculatorCids.clear();
210
+
211
+ const pendingDocsSnapshot = await this.db.collection(this.config.PENDING_SPECULATORS_COLLECTION).get(); // Use this.db
212
+
213
+ if (!pendingDocsSnapshot.empty) {
214
+ const deletePromises = [];
215
+ pendingDocsSnapshot.forEach(doc => {
216
+ const docData = doc.data().users || {};
217
+ const cidsInThisDoc = cidsToDelete.filter(cid => docData.hasOwnProperty(cid));
218
+
219
+ if (cidsInThisDoc.length > 0) {
220
+ const deleteBatch = this.db.batch(); // Use this.db
221
+ const updates = {};
222
+ cidsInThisDoc.forEach(cid => {
223
+ updates[`users.${cid}`] = FieldValue.delete();
224
+ });
225
+ deleteBatch.update(doc.ref, updates);
226
+ deletePromises.push(deleteBatch.commit());
227
+ this.logger.log('INFO', `[BATCH] Staged deletion of ${cidsInThisDoc.length} CIDs from document: ${doc.id}`);
228
+ }
229
+ });
230
+ promises.push(...deletePromises);
231
+ }
232
+ }
233
+
234
+ // --- OPTIMIZATION: ADD THIS LOOP ---
235
+ for (const docPath in this.speculatorTimestampFixBatch) {
236
+ const updates = this.speculatorTimestampFixBatch[docPath];
237
+ if (Object.keys(updates).length > 0) {
238
+ const docRef = this.db.doc(docPath);
239
+
240
+ // Add to the existing batch
241
+ firestoreBatch.set(docRef, updates, { merge: true });
242
+ batchOperationCount++;
243
+ }
244
+ delete this.speculatorTimestampFixBatch[docPath];
245
+ }
246
+ // --- END OPTIMIZATION ---
247
+
248
+ if (batchOperationCount > 0) {
249
+ promises.push(firestoreBatch.commit());
250
+ }
251
+
252
+ promises.push(this.headerManager.flushPerformanceUpdates());
253
+
254
+ await Promise.all(promises);
255
+ this.logger.log('INFO', '[BATCH] All batches flushed successfully.');
256
+ }
257
+ }
258
+
259
259
  module.exports = { FirestoreBatchManager };